在腳本中使用Bash信號捕獲
無論你的腳本是否成功運(yùn)行,信號捕獲都能讓它平穩(wěn)結(jié)束。
Shell 腳本的啟動(dòng)并不難被檢測到,但 Shell 腳本的終止檢測卻并不容易,因?yàn)槲覀儫o法確定腳本會(huì)按照預(yù)期地正常結(jié)束,還是由于意外的錯(cuò)誤導(dǎo)致失敗。當(dāng)腳本執(zhí)行失敗時(shí),將正在處理的內(nèi)容記錄下來是非常有用的做法,但有時(shí)候這樣做起來并不方便。而 Bash 中 trap
命令的存在正是為了解決這個(gè)問題,它可以捕獲到腳本的終止信號,并以某種預(yù)設(shè)的方式作出應(yīng)對。
響應(yīng)失敗
如果出現(xiàn)了一個(gè)錯(cuò)誤,可能導(dǎo)致發(fā)生一連串錯(cuò)誤。下面示例腳本中,首先在 /tmp
中創(chuàng)建一個(gè)臨時(shí)目錄,這樣可以在臨時(shí)目錄中執(zhí)行解包、文件處理等操作,然后再以另一種壓縮格式進(jìn)行打包:
#!/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
一般情況下,這個(gè)腳本都可以按照預(yù)期執(zhí)行。但如果歸檔文件中的文件是 PNG 文件而不是期望的 JPEG 文件,腳本就會(huì)在中途失敗,這時(shí)候另一個(gè)問題就出現(xiàn)了:最后一步刪除臨時(shí)目錄的操作沒有被正常執(zhí)行。如果你手動(dòng)把臨時(shí)目錄刪掉,倒是不會(huì)造成什么影響,但是如果沒有手動(dòng)把臨時(shí)目錄刪掉,在下一次執(zhí)行這個(gè)腳本的時(shí)候,它必須處理一個(gè)現(xiàn)有的臨時(shí)目錄,里面充滿了不可預(yù)知的剩余文件。
其中一個(gè)解決方案是在腳本開頭增加一個(gè)預(yù)防性刪除邏輯用來處理這種情況。但這種做法顯得有些暴力,而我們更應(yīng)該從結(jié)構(gòu)上解決這個(gè)問題。使用 trap
是一個(gè)優(yōu)雅的方法。
使用 trap 捕獲信號
我們可以通過 trap
捕捉程序運(yùn)行時(shí)的信號。如果你使用過 kill
或者 killall
命令,那你就已經(jīng)使用過名為 SIGTERM
的信號了。除此以外,還可以執(zhí)行 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
:進(jìn)程退出時(shí)發(fā)出的信號ERR
:進(jìn)程以非 0 狀態(tài)碼退出時(shí)發(fā)出的信號DEBUG
:表示調(diào)試模式的布爾值
如果要在 Bash 中實(shí)現(xiàn)信號捕獲,只需要在 trap
后加上需要執(zhí)行的命令,再加上需要捕獲的信號列表就可以了。
例如,下面的這行語句可以捕獲到在進(jìn)程運(yùn)行時(shí)用戶按下 Ctrl + C
組合鍵發(fā)出的 SIGINT
信號:
trap "{ echo 'Terminated with Ctrl+C'; }" SIGINT
因此,上文中腳本的缺陷可以通過使用 trap
捕獲 SIGINT
、SIGTERM
、進(jìn)程錯(cuò)誤退出、進(jìn)程正常退出等信號,并正確處理臨時(shí)目錄的方式來修復(fù):
#!/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
對于更復(fù)雜的功能,還可以用 Bash 函數(shù)來簡化 trap
語句。
Bash 中的信號捕獲
信號捕獲可以讓腳本在無論是否成功執(zhí)行所有任務(wù)的情況下都能夠正確完成清理工作,能讓你的腳本更加可靠,這是一個(gè)很好的習(xí)慣。盡管嘗試把信號捕獲加入到你的腳本里看看能夠起到什么作用吧。