1. 程式人生 > >Shell Script 控制指令碼

Shell Script 控制指令碼

程序訊號

Linux利用訊號與執行在系統中的程序進行通訊。可以通過對指令碼進行程式設計,使其在收到特定訊號時執行某些命令,從而控制shell指令碼的操作。

Linux常見訊號
1 SIGHUP 掛起程序
2 SIGINT 終止程序
3 SIGQUIT 停止程序(會讓程式繼續保留在記憶體中,並能從上次停止的位置繼續執行)
9 SIGKILL 無條件終止程序
15 SIGTERM 儘可能終止程序
17 SIGSTOP 無條件停止程序,但不是終止程序
18 SIGTSTP 停止或暫停程序,但不終止程序
19 SIGCONT 繼續執行停止的程序

預設情況下,bash shell會忽略收到的任何SIGQUIT (3)和SIGTERM (5)訊號。但是bash shell會處理收到的SIGHUP (1)和SIGINT (2)訊號。如果bash shell收到了SIGHUP訊號,比如當你要離開一個互動式shell,它就會退出。但在退出之前,它會將SIGHUP訊號傳給所有由該shell所啟動的程序(包括正在執行的shell指令碼)。通過SIGINT訊號,可以中斷shell。Linux核心會停止為shell分配CPU處理時間。這種情況發生時,shell會將SIGINT訊號傳給所有由它所啟動的程序,以此告知出現的狀況。

而shell指令碼的預設行為是忽略這些訊號。它們可能會不利於指令碼的執行。要避免這種情況,你可以指令碼中加入識別訊號的程式碼,並執行命令來處理訊號。

  • 生成訊號

中斷程序:Ctrl+C組合鍵會生成SIGINT訊號,並將其傳送給當前在shell中執行的所有程序。

暫停程序:Ctrl+Z組合鍵會生成一個SIGTSTP訊號,停止shell中執行的任何程序

如果你的shell會話中有一個已停止的作業,在退出shell時,bash會提醒你,用ps命令來檢視已停止的作業。在S列中(程序狀態),ps命令將已停止作業的狀態為顯示為T。仍舊想退出shell,只要再輸入一遍exit命令就行了。

$ sleep 100
^Z
[1]+ Stopped sleep 100
$
$ ps -l
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 501 2431 2430 0 80 0 - 27118 wait pts/0 00:00:00 bash
0 T 501 2456 2431 0 80 0 - 25227 signal pts/0 00:00:00 sleep
0 R 501 2458 2431 0 80 0 - 27034 - pts/0 00:00:00 ps
$

或者,既然你已經知道了已停止作業的PID,就可以用kill命令來發送一個SIGKILL訊號來終止它。

$ kill -9 2456
$
[1]+ Killed sleep 100
$
  • 捕獲訊號

在訊號出現時捕獲它們並執行其他命令,trap命令允許你來指定shell指令碼要監看並從shell中攔截的Linux訊號

trap commands signals

在trap命令列上,你只要列出想要shell執行的命令,以及一組用空格分開的待捕獲的訊號。你可以用數值或Linux訊號名來指定訊號。每次使用Ctrl+C組合鍵,指令碼都會執行trap命令中指定的echo語句,而不是處理該訊號並允許shell停止該指令碼

$ cat test1.sh
#!/bin/bash
trap "echo ' Sorry! I have trapped Ctrl-C'" SIGINT
count=1
while [ $count -le 10 ]
do
    echo "Loop #$count"
    sleep 1
    count=$[ $count + 1 ]
done
$ ./test1.sh
Loop #1
^C Sorry! I have trapped Ctrl-C
Loop #2
Loop #3
^C Sorry! I have trapped Ctrl-C
Loop #4
  • 捕獲指令碼退出

除了在shell指令碼中捕獲訊號,你也可以在shell指令碼退出時進行捕獲,只要在trap命令後加上EXIT訊號就行

$ cat test2.sh
#!/bin/bash
trap "echo Goodbye..." EXIT
count=1
while [ $count -le 5 ]
do
    echo "Loop #$count"
    sleep 1
    count=$[ $count + 1 ]
done
$ ./test2.sh
Loop #1
Loop #2
Loop #3
Loop #4
Loop #5
Goodbye...
$

如果提前退出指令碼,同樣能夠捕獲到EXIT

$ ./test2.sh
Loop #1
Loop #2
Loop #3
^CGoodbye...
$
  • 修改或移除捕獲

要想在指令碼中的不同位置進行不同的捕獲處理,只需重新使用帶有新選項的trap命令。

$ cat test3.sh
#!/bin/bash
trap "echo ' Sorry... Ctrl-C is trapped.'" SIGINT
[...]
trap "echo ' I modified the trap!'" SIGINT
[...]
$ ./test3.sh
Loop #1
Loop #2
Loop #3
^C Sorry... Ctrl-C is trapped.
Loop #4
Loop #5
Second Loop #1
Second Loop #2
^C I modified the trap!
Second Loop #3
Second Loop #4
Second Loop #5
$

也可以刪除已設定好的捕獲。只需要在trap命令與希望恢復預設行為的訊號列表之間加上兩個破折號就行了。

$ cat test3.sh
#!/bin/bash
trap "echo ' Sorry... Ctrl-C is trapped.'" SIGINT
[...]
trap -- SIGINT
echo "I just removed the trap"
[...]
$ ./test3b.sh
Loop #1
Loop #2
Loop #3
^C Sorry... Ctrl-C is trapped.
Loop #4
Loop #5
I just removed the trap
Second Loop #1
Second Loop #2
^C
$

以後臺模式執行指令碼

在後臺模式中,程序執行時不會和終端會話上的STDIN、STDOUT以及STDERR關聯。但仍然會使用終端顯示器來顯示STDOUT和STDERR訊息。

  • 後臺執行指令碼

以後臺模式執行shell指令碼非常簡單。只要在命令後加個&符就行了,方括號中的數字是shell分配給後臺程序的作業號。下一個數是Linux系統分配給程序的程序ID(PID)

$ ./test4.sh &
[1] 3231
$

在非控制檯下執行指令碼

有時你會想在終端會話中啟動shell指令碼,然後讓指令碼一直以後臺模式執行到結束,即使你退出了終端會話。nohup命令運行了另外一個命令來阻斷所有傳送給該程序的SIGHUP訊號。這會在退出終端會話時阻止程序退出。

$ nohup ./test1.sh &
[1] 3856
$ nohup: ignoring input and appending output to 'nohup.out'
$

由於nohup命令會解除終端與程序的關聯,程序也就不再同STDOUT和STDERR聯絡在一起。為了儲存該命令產生的輸出,nohup命令會自動將STDOUT和STDERR的訊息重定向到一個名為nohup.out的檔案中。

作業控制

啟動、停止、終止以及恢復作業的這些功能統稱為作業控制

  • 檢視作業

jobs命令允許檢視shell當前正在處理的作業,要想檢視作業的PID,可以在jobs命令中加入-l選項

$ jobs -l
[1]+ 1897 Stopped ./test10.sh
[2]- 1917 Running ./test10.sh > test10.out &
$
jobs命令引數
-l

列出程序的PID以及作業號

-n 只列出上次shell發出的通知後改變了狀態的作業
-p 只列出作業的PID
-r 只列出執行中作業
-s 只列出已停止作業

帶加號的作業會被當做預設作業。當前的預設作業完成處理後,帶減號的作業成為下一個預設作業。任何時候都只有一個帶加號的作業和一個帶減號的作業,

  • 重啟停止的作業

要以後臺模式重啟一個作業,可用bg命令加上作業號。

$ ./test11.sh
^Z
[1]+ Stopped ./test11.sh
$
$ ./test12.sh
^Z
[2]+ Stopped ./test12.sh
$
$ bg 2
[2]+ ./test12.sh &
$
$ jobs
[1]+ Stopped ./test11.sh
[2]- Running ./test12.sh &
$

要以前臺模式重啟作業,可用帶有作業號的fg命令。

$ fg 2
./test12.sh
This is the script's end...
$
  • 調整優先順序

排程優先順序是核心分配給程序的CPU時間(相對於其他程序)。在Linux系統中,由shell啟動的所有程序的排程優先順序預設都是相同的。

排程優先順序是個整數值,從-20(最高優先順序)到+19(最低優先順序)。預設情況下,bash shell以優先順序0來啟動所有程序。

nice命令允許你設定命令啟動時的排程優先順序。要讓命令以更低的優先順序執行,只要用nice的-n命令列來指定新的優先順序級別。必須將nice命令和要啟動的命令放在同一行中。nice命令阻止普通系統使用者來提高命令的優先順序。

$ nice -n 10 ./test4.sh > test4.out &
[1] 4973
$
$ ps -p 4973 -o pid,ppid,ni,cmd
PID PPID NI CMD
4973 4721 10 /bin/bash ./test4.sh
$

nice命令的-n選項並不是必須的,只需要在破折號後面跟上優先順序就行了。

$ nice -10 ./test4.sh > test4.out &
[1] 4993
$ ps -p 4993 -o pid,ppid,ni,cmd
PID PPID NI CMD
4993 4721 10 /bin/bash ./test4.sh
$

有時你想改變系統上已執行命令的優先順序。這正是renice命令可以做到的。它允許你指定執行程序的PID來改變它的優先順序。renice命令會自動更新當前執行程序的排程優先順序。和nice命令一樣,renice命令也有一些限制:

  • 只能對屬於你的程序執行renice;
  • 只能通過renice降低程序的優先順序;
  • root使用者可以通過renice來任意調整程序的優先順序。

如果想完全控制執行程序,必須以root賬戶身份登入或使用sudo命令。

$ ./test11.sh &
[1] 5055
$
$ ps -p 5055 -o pid,ppid,ni,cmd
PID PPID NI CMD
5055 4721 0 /bin/bash ./test11.sh
$
$ renice -n 10 -p 5055
5055: old priority 0, new priority 10
$
$ ps -p 5055 -o pid,ppid,ni,cmd
PID PPID NI CMD
5055 4721 10 /bin/bash ./test11.sh
$

定時執行作業

Linux系統提供了多個在預選時間執行指令碼的方法:at命令和cron表。每個方法都使用不同的技術來安排指令碼的執行時間和頻率

  • 用at 命令來計劃執行作業

at命令允許指定Linux系統何時執行指令碼。at命令會將作業提交到佇列中,指定shell何時執行該作業。at的守護程序atd會以後臺模式執行,檢查作業佇列來執行作業

at [-f filename] time

at命令會將STDIN的輸入放到佇列中。你可以用-f引數來指定用於讀取命令(指令碼檔案)的檔名。time引數指定了Linux系統何時執行該作業。如果你指定的時間已經錯過,at命令會在第二天的那個時間執行指定的作業。

  • 標準的小時和分鐘格式,比如10:15。
  • AM/PM指示符,比如10:15 PM。
  • 特定可命名時間,比如now、noon、midnight或者teatime(4 PM)。
  • 標準日期格式,比如MMDDYY、MM/DD/YY或DD.MM.YY。
  • 文字日期,比如Jul 4或Dec 25,加不加年份均可。
  • 你也可以指定時間增量:當前時間+25 min; 明天10:15 PM;10:15+7天

在你使用at命令時,該作業會被提交到作業佇列(job queue)。作業佇列會儲存通過at命令提交的待處理的作業。針對不同優先順序,存在26種不同的作業佇列。作業佇列通常用小寫字母a~z和大寫字母A~Z來指代。如果想以更高優先順序執行作業,可以用-q引數指定不同的佇列字母

at命令利用sendmail應用程式來發送郵件。如果你的系統中沒有安裝sendmail,那就無法獲得任何輸出!因此在使用at命令時,最好在指令碼中對STDOUT和STDERR進行重定向。如果不想在at命令中使用郵件或重定向,最好加上-M選項來遮蔽作業產生的輸出資訊

$ cat test13b.sh
#!/bin/bash
echo "This script ran at $(date +%B%d,%T)" > test13b.out
echo >> test13b.out
sleep 5
echo "This is the script's end..." >> test13b.out
$ at -M -f test13b.sh now
job 8 at 2015-07-14 12:48
$ cat test13b.out
This script ran at July14,12:48:18
This is the script's end...
$

atq命令可以檢視系統中有哪些作業在等待 

$ at -M -f test13b.sh teatime
job 17 at 2015-07-14 16:00
$
$ at -M -f test13b.sh tomorrow
job 18 at 2015-07-15 13:03
$
$ at -M -f test13b.sh 13:30
job 19 at 2015-07-14 13:30
$
$ at -M -f test13b.sh now
job 20 at 2015-07-14 13:03
$
$ atq
20 2015-07-14 13:03 = Christine
18 2015-07-15 13:03 a Christine
17 2015-07-14 16:00 a Christine
19 2015-07-14 13:30 a Christine
$

atrm命令來刪除等待中的作業

$ atq
18 2015-07-15 13:03 a Christine
17 2015-07-14 16:00 a Christine
19 2015-07-14 13:30 a Christine
$
$ atrm 18
$
$ atq
17 2015-07-14 16:00 a Christine
19 2015-07-14 13:30 a Christine
$
  • 安排需要定期執行的指令碼

Linux系統使用cron程式來安排要定期執行的作業。cron程式會在後臺執行並檢查一個特殊的表(被稱作cron時間表),以獲知已安排執行的作業。cron時間表採用一種特別的格式來指定作業何時執行。

min hour dayofmonth month dayofweek command

cron時間表允許你用特定值、取值範圍(比如1~5)或者是萬用字元(星號)來指定條目。例如,如果想在每天的10:15執行一個命令,可以用cron時間表條目:

15 10 * * * command

要指定在每週一4:15 PM執行的命令,可以用下面的條目:可以用三字元的文字值(mon、tue、wed、thu、fri、sat、sun)或數值(0為週日,6為週六)來指定dayofweek表項

15 16 * * 1 command

在每個月的第一天中午12點執行命令。可以用下面的格式:dayofmonth表項指定月份中的日期值(1~31)。

00 12 1 * * command

命令列表必須指定要執行的命令或指令碼的全路徑名。你可以像在普通的命令列中那樣,新增任何想要的命令列引數和重定向符號。cron程式會用提交作業的使用者賬戶執行該指令碼。因此,你必須有訪問該命令和命令中指定的輸出檔案的許可權

15 10 * * * /home/rich/test4.sh > test4out
  • 構建cron時間表

每個系統使用者(包括root使用者)都可以用自己的cron時間表來執行安排好的任務。Linux提供了crontab命令來處理cron時間表。要列出已有的cron時間表,可以用-l選項。要為cron時間表新增條目,可以用-e選項

$ crontab -l
no crontab for rich
$

如果你建立的指令碼對精確的執行時間要求不高,用預配置的cron指令碼目錄會更方便。有4個基本目錄:hourlydailymonthlyweekly。如果指令碼需要每天執行一次,只要將指令碼複製到daily目錄,cron就會每天執行它。