在你的脚本中使用 Bash traps

Traps 帮助你的脚本干净地结束,无论它们是否成功运行。
126 位读者喜欢这篇文章。
hands programming

WOCinTech Chat。由 Opensource.com 修改。CC BY-SA 4.0

检测 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 关键字捕获执行期间可能发生的信号。如果你曾经使用过 killkillall 命令,那么你已经使用过其中一个信号,这些命令默认调用 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:当命令(例如 tarmkdir)或内置命令(例如 pushdcd)以非零状态完成时发生
  • 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 对于确保你的脚本干净地结束非常有用,无论它们是否成功运行。完全依赖自动垃圾回收永远是不安全的,因此这是一个通常应该养成的良好习惯。尝试在你的脚本中使用它们,看看它们能做什么!

接下来阅读什么
标签
Seth Kenlon
Seth Kenlon 是一位 UNIX 极客、自由文化倡导者、独立多媒体艺术家和 D&D 爱好者。他曾在电影和计算机行业工作,通常同时进行。

13 条评论

我尝试了 EXIT trap,当脚本被 HUP、TERM 和 INT 信号杀死时,它也会执行。我认为无论脚本是否以 0 退出状态退出,它都会执行。

我认为较新版本的 trap 命令使用 `-l` 而不是 `--list` - 我在 Debian Testing 上

我在 RHEL 8 上也看到了。已更新,谢谢!

回复 作者 Erik Lundmark (未验证)

我发现这篇文章非常有用且解释得很好。非常感谢!

好东西,但吹毛求疵

将 `pwd` 更改为 $PWD,无需子 shell 即可工作

引用 "$TMP" 的用法 -- 某人可能会在那里有 $IFS 字符

对 $IMG 也一样

(请注意,在 var=$TMP 的情况下,不需要引号)

trap "/usr/bin/rm -r "$TMP"; exit 255" SIGINT SIGTERM ERR EXIT

应该可以工作。你是否检查过 trap 没有执行两次(例如,对于 ERR 和对于 EXIT)?

(最后(但当然并非最不重要的),我个人建议所有 shell 脚本都使用 set -euf)

我同意其中许多建议,并相应地修改了文章的某些部分。其他要点也很好,但我会将它们作为评论留给用户自己决定。

回复 作者 Tomi-1234 (未验证)

当捕获多个信号(例如,SIGINT SIGTERM ERR EXIT)时,是否有办法以与 trap 对应的代码退出?谢谢

我所知道的唯一方法是为每个信号创建一个 trap 语句。

将所有 trap 放在脚本的开头,以确保 Bash “意识到”所有不同的条件。

我很想知道是否有人有更花哨的方法来做到这一点(尽管我也想在承诺使用它之前对其进行严格的测试)。

回复 作者 eli (未验证)

大家好,想提一下,命令替换首选使用括号而不是反引号,因为反引号已被弃用。请参阅“https://mywiki.wooledge.org/BashFAQ/082”。

在 ERR 和 EXIT 的描述中,可能有助于澄清,当命令(外部命令如 “mkdir”、“tar” 或内部命令如 “pushd”)以错误状态完成时,ERR 会被触发。

当 shell 进程(脚本)本身退出时,EXIT 会被触发。

很好的澄清。我已经将其添加到文章文本中以供后人参考。

感谢您的反馈!

回复 作者 Steven Bakker

知识共享许可协议本作品根据知识共享署名-相同方式共享 4.0 国际许可协议获得许可。
© . All rights reserved.