linux shell程式設計 trap指令
trap指令用于指定在接收到信号後将要采取 的行 動,我們将在本書後面的内容中詳細介紹信号。trap指令的一種常見用途是在腳本程式被中斷時完成清理工作。曆史上,shell總是用數字來代表信号,而 新的腳本程式應該使用信号的名字,它們儲存在用#include指令包含進來的signal.h頭檔案中,在使用信号名時需要省略SIG字首。你可以在命 令提示符下輸入指令trap -l來檢視信号編号及其關聯的名稱。
對于那些不熟悉信号的人們來說,“信号”是指那些被異步發送到一個程式的事件。預設情況下,它們通常會終止一個程式的運作。
trap指令的參數分為兩部分,前一部分是接收到指定信号時将要采取的行動,後一部分是要處理的信号名。
請記住,腳本程式通常是以從上到下的順序解釋執行的,是以必須在你想保護的那部分代碼以前指定trap指令。
如果要重置某個信号的處理條件到其預設值,隻需簡單的将command設定為-。如果要忽略某個信号,就把command設定為空字元串‘’。一個不帶參數的trap指令将列出目前設定的信号及其行動的清單。
表2-11列出了X/Open規範裡面規定的能夠被捕獲的比較重要的一些信号(括号裡面的數字是傳統的信号編号)。更多細節請參考signal線上手冊的第七部分(man 7 signal)。
表 2-11
信 号說 明
HUP(1)挂起,通常因終端掉線或使用者退出而引發
INT(2)中斷,通常因按下Ctrl+C組合鍵而引發
QUIT(3)退出,通常因按下Ctrl+組合鍵而引發
ABRT(6)中止,通常因某些嚴重的執行錯誤而引發
ALRM(14)報警,通常用來處理逾時
TERM(15)終止,通常在系統關機時發送
實驗:信号處理
下面的腳本示範了一些簡單的信号處理方法:
運作這個腳本,在每次循環時按下Ctrl+C組合鍵(或任何你系統上設定的中斷鍵),我們将得到如下所示的輸出:
實驗解析
在這個腳本程式中,我們先用trap指令安排它在出 現一個INT(中斷)信号時執行rm –f /tmp/my_tmp_file_$$指令删除臨時檔案。腳本程式然後進入一個while循環,隻要臨時檔案存在,循環就一直持續下去。當使用者按下 Ctrl+C組合鍵時,就會執行rm –f /tmp/my_tmp_file_$$語句,然後繼續下一個循環。因為臨時檔案現在已經被删除了,是以第一個while循環将正常退出。
接下來,腳本程式再次調用trap指令,這次是指定 當一個INT信号出現時不執行任何指令。腳本程式然後重新建立臨時檔案并進入第二個while循環。這次當使用者按下Ctrl+C組合鍵時,沒有語句被指定 執行,是以采取預設處理方式,即立即終止腳本程式。因為腳本程式被立即終止了,是以永遠也不會執行最後的echo和exit語句。
16.unset指令
unset指令的作用是從環境中删除變量或函數。這個指令不能删除shell本身定義的隻讀變量(如IFS)。這個指令并不常用。
下面的腳本第一次輸出字元串“Hello World”,但第二次隻輸出一個換行符:
使用foo=語句産生的效果與上面腳本中的unset指令産生的效果差不多,但并不等同。foo=語句将變量foo設定為空,但變量foo仍然存在。而使用unset foo語句的效果是把變量foo從環境中删除
ctrl+c,涉及到前台程序組,和終端設定屬性有關。其意義就是說,當按下ctrl+c時候,(前提是stty -a看到,intr=^C,很多系統預設是del),終端驅動程式檢測到,并且根據這個stty的設定,給每個屬于前台程序組(程序組可以有很多,但是任一時刻,隻能有一個前台程序組),給該組的每個程序都發送SIGINT。首先,要明确,一般的shell實作而言,如果是用互動式shell來運作,為了友善問題叙述,假定我們現在的shell是bash那麼ksh -i -c "cmd"和ksh -c "cmd"在ksh -i 模式下,cmd和ksh不是一個程序組,ksh和bash又不是一個程序組。在ksh直接-c沒有-i模式下,一般的實作而言,ksh和cmd是一個程序組,而ksh和bash不是一個程序組。為什麼是這個結果? 想想看?這樣我們可以分析,有個x.sh檔案trap "cmd" sigxxxxxx我們會sh x.sh 或者直接x.sh不過不論什麼模式,都會有目前的登入shell啟動一個子shell來執行這個x.sh。而且,該shell和x.sh中需要啟動的子程序都是一個程序組(觀察到這點很重要)。因為如果不是互動模式的shell來執行,則該子shell和其子程序都會屬于一個程序組(除非被調用的cmd又重新設定了其組id),否則,子shell和其子shell屬于不同的程序組。為什麼ctrl+c會中斷該子shell和其子程序呢(不論是否互動shell啟動),原因就在于,當非互動式啟動,他們自然是一個組,都屬于同一個前台程序組。當互動式啟動時候,他們不屬于同一個程序組,但是請注意,一個程序隻要有控制終端(ps -f時候,能看到tty不是?的就是),就能讓自己參加該前台程序組(注意,參加前台程序組,不會改變自己的組id)。事實上,我們也可以看到,在互動模式啟動下,子shell和其子程序不是一個組,但是他們也屬于同一個前台程序組。并且該前台程序組号就是子shell的子程序的組号。是以我們可以推想,子shell,為了一直保持和其子程序在同一個前台程序組,不斷的調用了tcsetpgrp,來參加其子程序所在的組。否則,按照現在unix的規則,就會發現,ctrl+c後,子shell不會終止,但是其子程序會終止!這當然不是我們想要的。以上的闡述,綜合起來就是三句話:1,ctrl+c(如果就是終端産生SIGINT設定的話),會送到目前的前台程序組的每一個程序。2,前台程序組裡的程序,可以都是同一個組,也可以不是同一個組。3,前台程序組是一個終端屬性,同一終端,任一時刻,最多隻能有一個前台程序組。事實上,shell trap一個信号,并非是wait子程序的退出态,如果不是正常結束(128+信号數值),就調用trap。恰恰相反,是shell為自己設定了一個信号處理,因為他和子程序是同一個前台程序組,是以,可以得到從終端驅動發送到該組每個成員的一個信号(并非shell無法直接獲得SIGINT),在自己收到SIGINT時候,即觸發自己設定的動作。這一點,其實可以通過一個簡單的實驗證明:x.sh在trap 後,執行調用一個很小的C程式編譯後的binary,該C檔案實作為一直等待,捕捉SIGINT後exit(0)。這樣x.sh就會認為該子程序是正常結束?按照cjaizss 的說法,就會得出,x.sh本身不會觸發trap的動作。實際上,肯定會的。其實有個更簡單的測試。
sh -c "trap 'echo ok' 2; sh -c \"trap 'exit 0' 2; read\""