在腳本中使用 Bash 信號捕獲
Shell 腳本的啟動並不難被檢測到,但 Shell 腳本的終止檢測卻並不容易,因為我們無法確定腳本會按照預期地正常結束,還是由於意外的錯誤導致失敗。當腳本執行失敗時,將正在處理的內容記錄下來是非常有用的做法,但有時候這樣做起來並不方便。而 Bash 中 trap
命令的存在正是為了解決這個問題,它可以捕獲到腳本的終止信號,並以某種預設的方式作出應對。
響應失敗
如果出現了一個錯誤,可能導致發生一連串錯誤。下面示例腳本中,首先在 /tmp
中創建一個臨時目錄,這樣可以在臨時目錄中執行解包、文件處理等操作,然後再以另一種壓縮格式進行打包:
#!/usr/bin/env bash
CWD=`pwd`
TMP=${TMP:-/tmp/tmpdir}
## create tmp dir
mkdir "${TMP}"
## extract files to tmp
tar xf "${1}" --directory "${TMP}"
## move to tmpdir and run commands
pushd "${TMP}"
for IMG in *.jpg; do
mogrify -verbose -flip -flop "${IMG}"
done
tar --create --file "${1%.*}".tar *.jpg
## move back to origin
popd
## bundle with bzip2
bzip2 --compress "${TMP}"/"${1%.*}".tar
--stdout > "${1%.*}".tbz
## clean up
/usr/bin/rm -r /tmp/tmpdir
一般情況下,這個腳本都可以按照預期執行。但如果歸檔文件中的文件是 PNG 文件而不是期望的 JPEG 文件,腳本就會在中途失敗,這時候另一個問題就出現了:最後一步刪除臨時目錄的操作沒有被正常執行。如果你手動把臨時目錄刪掉,倒是不會造成什麼影響,但是如果沒有手動把臨時目錄刪掉,在下一次執行這個腳本的時候,它必須處理一個現有的臨時目錄,裡面充滿了不可預知的剩餘文件。
其中一個解決方案是在腳本開頭增加一個預防性刪除邏輯用來處理這種情況。但這種做法顯得有些暴力,而我們更應該從結構上解決這個問題。使用 trap
是一個優雅的方法。
使用 trap 捕獲信號
我們可以通過 trap
捕捉程序運行時的信號。如果你使用過 kill
或者 killall
命令,那你就已經使用過名為 SIGTERM
的信號了。除此以外,還可以執行 trap -l
或 trap --list
命令列出其它更多的信號:
$ trap --list
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
可以被 trap
識別的信號除了以上這些,還包括:
EXIT
:進程退出時發出的信號ERR
:進程以非 0 狀態碼退出時發出的信號DEBUG
:表示調試模式的布爾值
如果要在 Bash 中實現信號捕獲,只需要在 trap
後加上需要執行的命令,再加上需要捕獲的信號列表就可以了。
例如,下面的這行語句可以捕獲到在進程運行時用戶按下 Ctrl + C
組合鍵發出的 SIGINT
信號:
trap "{ echo 'Terminated with Ctrl+C'; }" SIGINT
因此,上文中腳本的缺陷可以通過使用 trap
捕獲 SIGINT
、SIGTERM
、進程錯誤退出、進程正常退出等信號,並正確處理臨時目錄的方式來修復:
#!/usr/bin/env bash
CWD=`pwd`
TMP=${TMP:-/tmp/tmpdir}
trap
"{ /usr/bin/rm -r "${TMP}" ; exit 255; }"
SIGINT SIGTERM ERR EXIT
## create tmp dir
mkdir "${TMP}"
tar xf "${1}" --directory "${TMP}"
## move to tmp and run commands
pushd "${TMP}"
for IMG in *.jpg; do
mogrify -verbose -flip -flop "${IMG}"
done
tar --create --file "${1%.*}".tar *.jpg
## move back to origin
popd
## zip tar
bzip2 --compress $TMP/"${1%.*}".tar
--stdout > "${1%.*}".tbz
對於更複雜的功能,還可以用 Bash 函數來簡化 trap
語句。
Bash 中的信號捕獲
信號捕獲可以讓腳本在無論是否成功執行所有任務的情況下都能夠正確完成清理工作,能讓你的腳本更加可靠,這是一個很好的習慣。儘管嘗試把信號捕獲加入到你的腳本里看看能夠起到什麼作用吧。
via: https://opensource.com/article/20/6/bash-trap
作者:Seth Kenlon 選題:lujun9972 譯者:HankChow 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive