第二部分 shell腳本編程基礎
第11章構建基礎腳本
第12章結構化命令
第13章更多的結構化命令
第14章處理用戶輸入
第15章呈現數據
第16章腳本控制
第15章 呈現數據
15.1 理解輸入和輸出
15.1.1 標準文件描述符
Linux 系統會將每個對象當作文件來處理,這包括輸入和輸出。Linux 用文件描述符來標識
每個文件對象。
15.1.2 重定向錯誤 #
15.2 在腳本中重定向輸出
15.2.1 臨時重定向
如果你有意在腳本中生成錯誤消息,可以將單獨的一行輸出重定向到STDERR。這只需要使
用輸出重定向符號將輸出重定向到STDERR 文件描述符符。在重定向到文件描述符時,必須在文件描述符索引值之前加一個&:
echo "This is an error message" >&2
這行會在腳本的STDERR 文件描述符所指向的位置顯示文本。
$ cat test10
#!/bin/bash
# redirecting all output to a file
exec 1>testout
echo "This is a test of redirecting all output"
echo "from a script to another file."
echo "without having to redirect every individual line"
$ ./test10
$ cat testout
This is a test of redirecting all output
from a script to another file.
without having to redirect every individual line
$
15.2.2 永久重定向:?exec 命令
如果腳本中有大量數據需要重定向,那么逐條重定向所有的echo 語句會很煩瑣。這時可以
用exec 命令,exec 命令會啟動一個新shell 并將STDOUT 文件描述符重定向到指定文件。腳本中送往STDOUT 的所有輸出都會被重定向
15.3 在腳本中重定向輸入
在Linux 系統中,exec 命令允許將STDIN 重定向為文件:
exec 0< testfile
15.4 創建自己的重定向
15.4.1 創建輸出文件描述符
可以用exec 命令分配用于輸出的文件描述符
exec 3 > test13out
15.4.2 重定向文件描述符
exec 3>&1? ?#?將文件描述符3 重定向到了文件描述符1(STDOUT)的當前位置,
將發送給STDOUT 的輸出直接送
往該文件。
$ cat test14
#!/bin/bash
# storing STDOUT, then coming back to it
exec 3>&1 # 將文件描述符3 重定向到了文件描述符1(STDOUT)的當前位置
exec 1>test14out # 將STDOUT 重定向到了test14out文件
echo "This should store in the output file"
echo "along with this line."
exec 1>&3 #令將STDOUT 重定向到了文件描述符3 的當前位置
echo "Now things should be back to normal"
$
$ ./test14
Now things should be back to normal
$ cat test14out
This should store in the output file
along with this line.
$
15.4.3 創建輸入文件描述符 #
$ cat test15
#!/bin/bash
# redirecting input file descriptors
exec 6<&0 #文件描述符6 用于保存STDIN 指向的位置
exec 0< testfile #將STDIN 重定向到一個文件
count=1
while read line # read 命令的所有輸入都來自輸入文件
do
echo "Line #$count: $line"
count=$[ $count + 1 ]
done
exec 0<&6
read -p "Are you done now? " answer
case $answer in
Y|y) echo "Goodbye";;
N|n) echo "Sorry, this is the end.";;
esac
$ ./test15
Line #1: This is the first line.
Line #2: This is the second line.
Line #3: This is the third line.
Are you done now? y
Goodbye
$
15.4.4 創建讀/寫文件描述符 #
打開單個文件描述符兼做輸入和輸出,這樣就能用同一個文件描述符對文件進行讀和寫兩種操作
15.4.5關閉文件描述符 #?
要關閉文件描述符,只需將其重定向到特殊符號&-即可。在腳本中如下所示:
exec 3>&-
15.5 列出打開的文件描述符
bash shell 提供了lsof 命令
$ /usr/sbin/lsof -a -p $$ -d 0,1,2
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
bash 3344 rich 0u CHR 136,0 2 /dev/pts/0
bash 3344 rich 1u CHR 136,0 2 /dev/pts/0
bash 3344 rich 2u CHR 136,0 2 /dev/pts/0
$
15.6 抑制命令輸出
不想顯示腳本輸出。將腳本作為后臺進程運行時這很常見。要解決這個問題,可以將STDERR 重定向到一個名為null 文件的特殊文件。跟它的名字很像,null 文件里什么都沒有。shell 輸出到null 文件的任何數據都不會被保存,全部會被丟棄。
$ ls -al > /dev/null
$ cat /dev/null
$
15.7 使用臨時文件
15.7.1 創建本地臨時文件
在默認情況下,mktemp 會在本地目錄中創建一個文件。在使用mktemp 命令時,只需指定
一個文件名模板即可。模板可以包含任意文本字符,同時在文件名末尾要加上6 個X:
$ mktemp testing.XXXXXX
?
15.7.2 在/tmp目錄中創建臨時文件
-t 選項會強制mktemp 命令在系統的臨時目錄中創建文件。在使用這個特性時,mktemp
命令會返回所創建的臨時文件的完整路徑名,而不只是文件名:
$ mktemp -t test.XXXXXX
15.7.3 創建臨時目錄
-d 選項會告訴mktemp 命令創建一個臨時目錄。你可以根據需要使用該目錄,比如在其中
創建其他的臨時文件:
$ cat test21
#!/bin/bash
# using a temporary directory
tempdir=$(mktemp -d dir.XXXXXX) # 創建了一個臨時目錄
cd $tempdir
tempfile1=$(mktemp temp.XXXXXX) # 創建了兩個臨時文件
tempfile2=$(mktemp temp.XXXXXX)
exec 7> $tempfile1 # 這兩個臨時文件又被分配給了文件描述符以用來保存腳本的輸出
exec 8> $tempfile2
echo "Sending data to directory $tempdir"
echo "This is a test line of data for $tempfile1" >&7
echo "This is a test line of data for $tempfile2" >&8
15.8 記錄消息
確實需要將輸出同時送往顯示器和文件。與其對輸出進行兩次重定向,不如改用特殊的tee 命令。
tee 命令就像是連接管道的T 型接頭
$ date | tee testfile
Sun Jun 21 18:56:21 EDT 2020
$ cat testfile
Sun Jun 21 18:56:21 EDT 2020
$
第16章 腳本控制
16.1 處理信號
16.1.1 重溫Linux信號
16.1.2 產生信號
1. 中斷進程:Ctrl+C 組合
2. 暫停進程:?Ctrl+Z 組合
16.1.3 捕獲信號:trap 命令
trap 命令可以指定shell腳本需要偵測并攔截的Linux 信號。如果腳本收到了trap 命令中列出的信號,則該信號不再由shell 處理,而是由本地處理。
trap 命令的格式如下:
trap commands signals
$ cat trapsignal.sh
#!/bin/bash
#Testing signal trapping
#
trap "echo ' Sorry! I have trapped Ctrl-C'" SIGINT # 捕獲信號
#
echo This is a test script.
#
count=1
while [ $count -le 5 ]
do
echo "Loop #$count"
sleep 1
count=$[ $count + 1 ]
done
#
echo "This is the end of test script."
exit
$$ ./trapsignal.sh
This is a test script.
Loop #1
Loop #2
^C Sorry! I have trapped Ctrl-C
Loop #3
^C Sorry! I have trapped Ctrl-C
Loop #4
Loop #5
This is the end of test script.
$
16.1.4 捕獲腳本退出
在shell 腳本中捕獲信號,也可以在shell 腳本退出時捕獲信號。這是在shell 完成任務時執行命令的一種簡便方法。要捕獲shell 腳本的退出,只需在trap 命令后加上EXIT 信號即可
$ cat trapexit.sh
#!/bin/bash
#Testing exit trapping
#
trap "echo Goodbye..." EXIT # 捕獲腳本退出
#
count=1
while [ $count -le 5 ]
do
echo "Loop #$count"
sleep 1
count=$[ $count + 1 ]
done
#
exit
$
$ ./trapexit.sh
Loop #1
Loop #2
Loop #3
Loop #4
Loop #5
Goodbye...
$
16.1.5 修改或移除信號捕獲
想在腳本中的不同位置進行不同的信號捕獲處理,只需重新使用帶有新選項的trap 命令即可
16.2 以后臺模式運行腳本
使用ps -e 命令,可以看到Linux 系統中運行的多個進程
16.2.1 后臺運行腳本
以后臺模式運行shell 腳本非常簡單,只需在腳本名后面加上&即可
$ cat backgroundscript.sh
#!/bin/bash
#Test running in the background
#
count=1
while [ $count -le 5 ]
do
sleep 1
count=$[ $count + 1 ]
done
#
exit
$
$ ./backgroundscript.sh &
[1] 2595
$
16.2.2 運行多個后臺作業
在使用命令行提示符的情況下,可以同時啟動多個后臺作業
$ ./testAscript.sh &
[1] 2753
$ This is Test Script #1.
$ ./testBscript.sh &
[2] 2755
$ This is Test Script #2.
$ ./testCscript.sh &
[3] 2757
$ And... another Test script.
$ ./testDscript.sh &
[4] 2759
$ Then...there was one more Test script.
16.3 在非控制臺下運行腳本:?nohup
即便退出了終端會話,你也想在終端會話中啟動shell 腳本,讓腳本一直以后臺模式運行到結束。這可以用nohup 命令來實現
nohup 命令的格式如下:
nohup command
下面的例子使用一個后臺腳本作為command:
$ nohup ./testAscript.sh &
[1] 1828
$ nohup: ignoring input and appending output to 'nohup.out'
注意 nohup.out 文件一般在當前工作目錄中創建,否則會在$HOME 目錄中創建
16.4 作業控制
在作業停止后,Linux 系統會讓你選擇是“殺死”該作業還是重啟該作業。用kill 命令可以“殺死”該作業。要重啟停止的進程,則需要向其發送SIGCONT 信號。
16.4.1 查看作業
jobs 是作業控制中的關鍵命令,通過jobs 命令可以查看分配給shell 的作業
jobs 命令的-l 選項(小寫字母l)查看作業的PID。
16.4.2 重啟已停止的作業
要以后臺模式重啟作業,可以使用bg 命令
$ ./restartjob.sh
^Z
[1]+ Stopped ./restartjob.sh
$
$ bg
[1]+ ./restartjob.sh &
$
$ jobs
[1]+ Running ./restartjob.sh &
$
16.5 調整謙讓度
在多任務操作系統(比如Linux)中,內核負責為每個運行的進程分配CPU 時間。調度優先
級[也稱為謙讓度(nice value)]是指內核為進程分配的CPU 時間(相對于其他進程)
調度優先級是一個整數值,取值范圍從-20(最高優先級)到+19(最低優先級)。
只要記住那句俗話“好人難做。”(Nice guys finish last.)即可。越是“謙讓”(nice)或是值越
大,獲得CPU 的機會就越低。
16.5.1 nice命令
nice 命令允許在啟動命令時設置其調度優先級。要想讓命令以更低的優先級運行,只需用
nice 命令的-n 選項指定新的優先級即可:
$ nice -n 10 ./jobcontrol.sh > jobcontrol.out &
注意,nice 命令和要啟動的命令必須出現在同一行中。ps 命令的輸出證實,謙讓度(NI列)已經調整到了10。
16.5.2 renice命令
修改系統中已運行命令的優先級。可以使用renice 命令
renice 命令對于非特權用戶也有一些限制:只能對屬主為自己的進程使用renice 且只能降低調度優先級。
16.6 定時運行作業
Linux 系統提供了多個在預選時間運行腳本的方法:at 命令、cron 表以及anacron。
16.6.1 使用at命令調度作業
at 命令允許指定Linux 系統何時運行腳本。
1. at 命令的格式
at 命令的基本格式非常簡單:
at [-f filename] time
在使用at 命令時,該作業會被提交至作業隊列。作業隊列保存著通過at 命令提交的待處
理作業。針對不同優先級,有52 種作業隊列。作業隊列通常用小寫字母a~z 和大寫字母A~Z 來
指代,A 隊列和a 隊列是兩個不同的隊列。
2. 獲取作業的輸出
at 命令會顯示分配給作業的作業號以及為作業安排的運行時間。-f 選項指明使用哪個腳本
文件。now 指示at 命令立刻執行該腳本。
3. 列出等待的作業
atq 命令可以查看系統中有哪些作業在等待
4. 刪除作業
就可以用atrm 命令刪除等待中的作業。指定要刪除的作業號即可
$ atq
1 Thu Jun 18 16:11:00 2020 a christine
5 Fri Jun 19 16:00:00 2020 a christine
6 Fri Jun 19 16:53:00 2020 a christine
7 Thu Jun 18 20:30:00 2020 a christine
8 Thu Jun 18 17:54:00 2020 a christine
$
$ atrm 5
16.6.2 調度需要定期運行的腳本
使用cron 程序調度需要定期執行的作業。cron 在后臺運行,并會檢查一個特殊的表(cron 時間表),從中獲知已安排執行的作業。
1. cron 時間表
cron 時間表通過一種特別的格式指定作業何時運行,其格式如下:
minutepasthour hourofday dayofmonth month dayofweek command
2. 構建cron 時間表
要列出已有的cron 時間表,可以用-l 選項:
$ crontab -l
3. 瀏覽cron 目錄
創建的腳本對于執行時間的精確性要求不高,則用預配置的cron 腳本目錄會更方便。
預配置的基礎目錄共有4 個:hourly、daily、monthly 和weekly
$ ls /etc/cron.*ly
/etc/cron.daily:
0anacron apt-compat cracklib-runtime logrotate [...]
apport bsdmainutils dpkg man-db [...]
4. anacron 程序
使用場景:如果某個作業在cron 時間表中設置的運行時間已到,但這時候Linux 系統處于關閉狀態,那么該作業就不會運行。當再次啟動系統時,cron 程序不會再去運行那些錯過的作業。
anacron 判斷出某個作業錯過了設置的運行時間,它會盡快運行該作業
16.7 使用新shell啟動腳本
每次啟動新shell,bash shell 都會運行.bashrc 文件。