“trap ... INT TERM EXIT”真的有必要吗?

mus*_*hil 80 shell bash signals trap

对于许多例子trap使用trap ... INT TERM EXIT的清理任务。但是真的有必要列出所有三个 sigspecs 吗?

手册上说

如果 SIGNAL_SPEC 为 EXIT (0),则在退出 shell 时执行 ARG。

我相信这适用于脚本是否正常完成或因为它收到SIGINTSIGTERM. 一个实验也证实了我的信念:

$ cat ./trap-exit
#!/bin/bash
trap 'echo TRAP' EXIT
sleep 3
$ ./trap-exit & sleep 1; kill -INT %1
[1] 759
TRAP
[1]+  Interrupt               ./trap-exit
$ ./trap-exit & sleep 1; kill -TERM %1
[1] 773
TRAP
[1]+  Terminated              ./trap-exit
Run Code Online (Sandbox Code Playgroud)

那为什么这么多例子一一列举INT TERM EXIT呢?或者我错过了什么,有没有鞋底EXIT会错过的情况?

Zaz*_*Zaz 34

是,有一点不同。

当您按下Enter、 或发送SIGINT或时,此脚本将退出SIGTERM

trap '' EXIT
echo ' --- press ENTER to close --- '
read response
Run Code Online (Sandbox Code Playgroud)

当您按 时,此脚本将退出Enter

trap '' EXIT INT TERM
echo ' --- press ENTER to close --- '
read response
Run Code Online (Sandbox Code Playgroud)

* 在shBashZsh 中测试。(当你添加一个命令让陷阱运行时不再在sh 中工作)


还有@Shawn 所说的:AshDash不会用EXIT.

因此,要稳健地处理信号,最好EXIT完全避免捕获,并使用以下内容:

cleanup() {
    echo "Cleaning stuff up..."
    exit
}

trap cleanup INT TERM
echo ' --- press ENTER to close --- '
read var
cleanup
Run Code Online (Sandbox Code Playgroud)

  • 当另一个 shell 调用它时,这个解决方案并不健壮。它不处理[等待合作退出](https://www.cons.org/cracauer/sigint.html);你会想要`trap - INT TERM; kill -2 $$` 作为清理的最后一行,告诉父 shell 它过早退出。如果父 shell foobar.sh 调用您的脚本 (foo.sh),然后调用 bar.sh,如果 INT/TERM 发送到您的 foo.sh,您不希望 bar.sh 执行。`trap cleanup EXIT` 将自动处理这种传播,因此它是 IMO 最健壮的。这也意味着您不必在脚本末尾调用 `cleanup`。 (6认同)
  • 如果您的代码中有 shellscript 错误导致它过早退出,这将不起作用。 (4认同)
  • @ijw:在 Bash 和 Ksh 中,您可以捕获 `ERR` 来处理它,但 [它不可移植](https://en.wikibooks.org/wiki/Bourne_Shell_Scripting/Debugging_and_signal_handling#Err..._ERR.3F) . (2认同)

Sha*_*off 26

POSIX规范并没有太多说的,导致执行EXIT陷阱,只有它的环境必须考虑什么是执行时它像的条件。

在 Busybox 的 ash shell 中,由于 SIGINT 或 SIGTERM,您的陷阱退出测试在退出之前不会回显“TRAP”。我怀疑还有其他 shell 可能也不能那样工作。

# /tmp/test.sh & sleep 1; kill -INT %1
# 
[1]+  Interrupt                  /tmp/test.sh
# 
# 
# /tmp/test.sh & sleep 1; kill -TERM %1
# 
[1]+  Terminated                 /tmp/test.sh
# 
Run Code Online (Sandbox Code Playgroud)

  • `zsh` 也是如此 - 因此,也许 `bash` 是唯一一个 `EXIT` 也匹配信号的 shell。 (5认同)
  • `dash` 在接收到 `SIGINT/SIGTERM` 时也不会仅仅停留在 `EXIT` 上。 (4认同)
  • @maxschlepzig `zsh` 在收到 `INT` 时不会在 `EXIT` 上捕获,但在收到 `TERM` 时会捕获。编辑:我刚刚注意到这有多老了...... (3认同)

ijw*_*ijw 13

完善最后一个答案,因为它有问题:

# Our general exit handler
cleanup() {
    err=$?
    echo "Cleaning stuff up..."
    trap '' EXIT INT TERM
    exit $err 
}
sig_cleanup() {
    trap '' EXIT # some shells will call EXIT after the INT handler
    false # sets $?
    cleanup
}
trap cleanup EXIT
trap sig_cleanup INT QUIT TERM
Run Code Online (Sandbox Code Playgroud)

以上几点:

当我测试时,INT 和 TERM 处理程序不会为我退出 - 它们处理错误然后外壳返回退出(这并不奇怪)。所以我确保清理之后退出,并且在信号的情况下总是使用错误代码(在正常退出的另一种情况下,保留错误代码)。

使用 bash,似乎在 INT 处理程序中退出也会调用 EXIT 处理程序,因此我解开退出处理程序并自己调用它(无论行为如何,它都可以在任何 shell 中工作)。

我捕获退出是因为 shell 脚本可以在到达底部之前退出 - 语法错误,设置 -e 和非零返回,只需调用 exit。你不能依靠 shellscript 深入了解。

SIGQUIT 是 Ctrl-\ 如果您从未尝试过。为您提供额外的核心转储。所以我认为它也值得陷害,即使它有点晦涩。

过去的经验表明,如果您(像我一样)总是多次按下 Ctrl-C,您有时会在 shell 脚本的清理部分中途遇到它,因此这有效,但并不总是如您所愿。

  • 调用者只会得到 1 作为退出代码,不管是什么信号导致退出,而如果没有我们的 `trap`,调用者会得到 130 的 SIGINT,143 的 SIGTERM 等等。所以我会捕获并传递正确的退出代码: `sig_cleanup() { err=$?; 陷阱''退出; (退出 $err); 清理; }`。 (2认同)
  • 你能澄清清理函数中`trap'' EXIT INT TERM`的目的吗?这是为了防止您在上一段中提到的用户意外中断清理吗?“EXIT”不是多余的吗? (2认同)

小智 8

这就是如何使 Bash 脚本报告其返回代码$?,同时能够捕获 SIGINT 和 SIGTERM 信号。我发现这对于在 CI/CD 管道中运行的脚本非常有用:

\n\n
notify() {\n    [[ $1 = 0 ]] || echo \xe2\x9d\x8c EXIT $1\n    # you can notify some external services here,\n    # ie. Slack webhook, Github commit/PR etc.\n}\n\ntrap '(exit 130)' INT\ntrap '(exit 143)' TERM\ntrap 'rc=$?; notify $rc; exit $rc' EXIT\n
Run Code Online (Sandbox Code Playgroud)\n

  • 将 INT 和 TERM 重定向到 EXIT 陷阱是一个优雅的解决方案,它可以防止对 trap 命令的双重调用。谢谢! (3认同)