名稱 值 描述 SIGHUP 1 控制終端發現被掛起或控制進程死亡 SIGINT 2 鍵盤終端 SIGQUIT 3 來自鍵盤的退出信號 SIGKILL 9 殺死進程的信號 SIGALRM 14 定時時鐘中斷 SIGTERM 15 終止信號
trap 'command_list' signals
trap 'echo you hit Ctrl-C\, now exiting..; exit' SIGINT
捕獲 SIGINT, 什麼也不作,即忽略該信號,使用html
trap '' SIGINT
重置 SIGINT,即便用系統默認的信號處理函數shell
trap - SIGINT
本例中,能夠捕獲 SIGTERM 信號,並指定信號處理函數來實現資源的清理工做,資源清理乾淨後再退出。編程
horen@heart$~> cat signal.sh #!/bin/bash trap 'TaskClean; exit' SIGTERM function TaskOne() { echo "Now do task one..." sleep 10 echo "TaskOne is done" } function TaskTwo() { echo "Now do task two..." sleep 10 echo "TaskTwo is done" } function TaskClean() { echo "Now do task clean..." sleep 2 echo "TaskClean is done" } TaskOne TaskTwo
運行
bash
horen@heart$~> sh signal.sh Now do task one... Now do task clean... TaskClean is done
在執行 TaskOne 時給腳本發送信號 SIGTERM,腳本當時在執行 「sleep」,sleep 結束時轉而去執行信號處理函數。注意,並非在 TaskOne 結束後再去執行信號處理函數。 ide
trap的一些使用說明:函數
它有三種形式分別對應三種不一樣的信號迴應方式.
第一種:
trap 'commands' signal-list
當腳本收到 signal-list 清單內列出的信號時, trap 命令執行雙引號中的命令.
第二種:
trap signal-list
trap 不指定任何命令, 接受信號的默認操做. 默認操做是結束進程的運行.
第三種:
trap ' ' signal-list
trap 命令指定一個空命令串, 容許忽視信號.工具
NOTE:trap 對同種 signal 只能相應一種設定,若是在一個 shell 裏面設置多個 trap,如:ui
trap 'echo 「aaaaaaaaaaa」' INT trap 'echo 「bbbbbbbbbbb」' INT
那麼它只會響應最後一個信號設定。spa
信號處理(Signal Handling)在 Linux 編程中一直扮演者重要的角色,幾乎每一個系統工具都要用到它,最多見的功能莫過於用信號進行進程間通訊(尤爲是父子進程)以及捕捉 SIGINT、SIGTERM 之類的退出信號以作一些善後處理(cleanup)。C 中自沒必要多說,可使用 wait 族函數;而 shell 腳本中也有捕捉信號的 trap 功能——然而許多人在使用 trap 功能的時候卻存在着這樣那樣的誤解,這些看似可有可無的小細節最後有可能使得你的腳本與你預想的行爲徹底不一樣。.net
如無特殊說明,下文所指 shell 均以 Bash 爲例。
雖然我很想說這些應當要本身看 manpage ,但考慮到也許正在讀文章的你手邊沒有 Linux ,仍是簡單說一下吧。
1
|
USAGE:
trap [action condition ...]
|
即當捕捉到 condition 列表所對應的任何一個信號時,執行 action 動做(使用 eval action
來執行,故 action 能夠是 shell 內建指令、外部命令及腳本中的函數等)。action 還但是」"(空)、’-'等,分別表明忽略相應信號及重置相應信號爲默認行爲。
condition 中的信號到底應該如何書寫?好比終端中斷信號(通常用 CTRL-C 發出),究竟是寫 SIGINT 、 INT 仍是 2(大部分系統上該信號對應的信號數)?是大寫仍是小寫?
若是你使用最新版的 Bash ,那麼這幾種寫法均可以。而若是你須要在不一樣 shell 中保持可移植性,請使用大寫、不帶前綴的 INT !根據 POSIX 標準, trap 的 condition 不該當加上 SIG 前綴,且必須全大寫,容許帶 SIG 前綴或小寫是某些 shell 的擴展功能。而信號數在不一樣的系統上可能不一樣,因此也不是一個好主意。
許多資料,尤爲是中文資料中不容申辯地指明—— trap 必須放在腳本中第一個非註釋行。事實果然如此麼?
不管是 manpage 仍是 POSIX 文檔中,我都沒有找到任何與之相關的說明。甚至在 TLDP 的 Bash Guide for Beginners 中,多個例子都分明把 trap 放在了腳本的中間。最後我在這篇文檔中找到了下面這句常常被誤讀的話:
Normally, all traps are set before other executable code in the shell script is encountered, i.e., at the beginning of the shell script.
果真,這只是一個爲了保證信號鉤子儘早被設立的一個設計慣例罷了。事實上, trap 能夠根據你的須要放在腳本中的任何位置。腳本中也能夠有多個 trap ,能夠爲不一樣的信號定義不一樣的行爲,或是修改、刪除已定義的 trap 。更進一步地, trap 也有做用範圍,你能夠把它放在函數中,它將只在這個函數裏起效!你看,其實 trap 的行爲是很符合 UNIX 的慣例的。
這是本文最重要的一點。信號究竟是何時被處理的?更準確地說,好比腳本正在執行某個命令時收到了某個信號,那麼它會被當即處理,仍是要等待當前命令完成?
我不打算直接說明答案。爲了讓咱們對這個問題有更透徹的理解,讓咱們來作一下實驗。看下面這個時常被用來說解 trap 的腳本:
1
2 3 |
#!/bin/bash trap 'echo"INTERRUPTED!"; exit' INT sleep 100 |
大多數教程都是這麼作的,運行這個腳本,按下 CTRL-C 。你看到了什麼?腳本打出了 「INTERRUPTED!」 並中止了運行。這看起來彷佛很正常、很直覺——以此看來, trap 會當即捕捉到信號並執行,無論當前正在執行的命令。許多腳本也正是在這個假設下寫的。
然而真的是這樣麼?讓咱們作另外一個實驗——在一個終端執行這個腳本,並打開另外一個終端,用ps-ef|grepbash
找到這個腳本的進程號,而後用kill-SIGINT pid
向這個進程發送 SIGINT 信號。你在原先的終端中看到了什麼?沒有任何反應!若是你願意等上 100 秒,你最終會看到 「INTERRUPTED!」 被輸出。這樣看來 trap 是等到當前命令結束之後再處理信號。
這樣的矛盾到底是爲何?問題其實出在 CTRL-C 身上。 Bash 等終端的默認行爲是這樣的:當按下 CTRL-C 以後,它會向當前的整個進程組發出 SIGINT 信號。而 sleep 是由當前腳本調用的,是這個腳本的子進程,默認是在同一個進程組的,因此也會收到 SIGINT 並中止執行,返回主進程之後 trap 捕捉到了信號。
這篇文檔給了咱們一個更準確的說明——若是當前正有一個外部命令在前臺執行,那麼 trap 會等待當前命令結束之後再處理信號隊列中的信號。(而許多教程出錯的另外一個緣由就是——某些 shell 中 sleep 是內建命令,會被打斷。)
那麼上文的例子應當要如何寫才能達到想要的效果呢?有兩種方法:1、把 sleep 放到後臺進行,再用內建的 wait 去等待其執行結束(詳見上一段提到的那篇文檔);2、暴力一點,把一長段 sleep 拆成一秒的小 sleep 的循環,這在對精度要求不高的狀況下也是一個可行的辦法。