检测 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
具有临时目录问题的示例脚本可以使用 trap 检测 SIGINT
、错误和成功退出来修复
#!/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 条评论