检测 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
。shell 响应许多其他信号,你可以使用 trap -l
(如“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
预期这些信号中的任何一个。除了这些之外,trap
还识别
EXIT
:当 shell 进程本身退出时发生ERR
:当命令(例如 tar 或 mkdir)或内置命令(例如 pushd 或 cd)以非零状态完成时发生DEBUG
:表示调试模式的布尔值
要在 Bash 中设置 trap,请使用 trap
,后跟要执行的命令列表,然后是要触发它的信号列表。
例如,此 trap 检测到 SIGINT
,即用户在进程运行时按下 Ctrl+C 时发送的信号
trap "{ echo 'Terminated with Ctrl+C'; }" SIGINT
具有临时目录问题的示例脚本可以使用检测 SIGINT
、错误和成功退出的 trap 来修复
#!/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 中的 Traps
Traps 对于确保你的脚本干净地结束非常有用,无论它们是否成功运行。完全依赖自动垃圾回收永远是不安全的,因此这是一个通常应该养成的良好习惯。尝试在你的脚本中使用它们,看看它们能做什么!
13 条评论