trap
许多用于trap ... INT TERM EXIT
清理任务的示例。但真的有必要列出所有三个 sigspec 吗?
手册说:
如果 SIGNAL_SPEC 为 EXIT (0) ARG,则在退出 shell 时执行。
我相信无论脚本正常完成还是因为收到SIGINT
或而完成,都适用SIGTERM
。一个实验也证实了我的信念:
$ 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
那为什么这么多例子都列出了所有的呢INT TERM EXIT
?或者我错过了什么,有没有鞋底EXIT
会错过的情况?
答案1
是,有一点不同。
Enter当您按、 或发送它SIGINT
或 时,此脚本将退出SIGTERM
:
trap '' EXIT
echo ' --- press ENTER to close --- '
read response
当您按以下命令时,该脚本将退出Enter:
trap '' EXIT INT TERM
echo ' --- press ENTER to close --- '
read response
* 测试于嘘,重击, 和兹什。 (不再适用于嘘当您添加陷阱运行命令时)
还有@Shawn 说的:灰和短跑不要用 捕获信号EXIT
。
因此,为了稳健地处理信号,最好EXIT
完全避免捕获,并使用如下所示的方法:
cleanup() {
echo "Cleaning stuff up..."
exit
}
trap cleanup INT TERM
echo ' --- press ENTER to close --- '
read var
cleanup
答案2
这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
#
答案3
完善最后一个答案,因为它有问题:
# 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
以上几点:
当我测试时,INT 和 TERM 处理程序不会为我退出 - 它们处理错误,然后 shell 返回退出状态(这并不奇怪)。因此,我确保清理之后退出,并且在信号的情况下始终使用错误代码(在正常退出的其他情况下,保留错误代码)。
使用 bash,似乎在 INT 处理程序中退出也会调用 EXIT 处理程序,因此我解开退出处理程序并自己调用它(无论行为如何,它都可以在任何 shell 中工作)。
我捕获 exit 是因为 shell 脚本可以在到达底部之前退出 - 语法错误、set -e 和非零返回,只需调用 exit 即可。您不能依靠 shell 脚本来了解真相。
如果您从未尝试过,SIGQUIT 就是 Ctrl-\。为您带来额外的核心转储。所以我认为它也值得捕获,即使它有点晦涩难懂。
过去的经验表明,如果您(像我一样)总是按 Ctrl-C 几次,有时您会在 shell 脚本的清理部分中途捕获它,因此这可以工作,但并不总是像您希望的那样完美。
答案4
这取决于您要实现的目标以及目标 shell。因为bash
可能只需使用 就可以了EXIT
。但是不是所有的贝壳调用/EXIT
上的处理程序。SIGINT
SIGTERM
对于它们,您可以尝试为多个信号设置一个处理程序 ( trap '...' INT EXIT
),但随后它可能会被调用多次:
$ bash -c 'trap "echo trap" INT EXIT; sleep 3' & pid=$!; sleep 1; kill -INT $pid; wait
[1] 276923
trap
trap
[1]+ Done bash -c 'trap "echo trap" INT EXIT; sleep 3'
所以要么你在写的时候牢记这一点,要么你可以尝试向前一切都交给EXIT
处理程序:
$ bash -c 'trap "exit 123" INT; trap "echo EXIT \$?" EXIT; sleep 3' & pid=$!; sleep 1; kill -INT $pid; wait
[1] 286229
EXIT 123
[1]+ Exit 123 bash -c 'trap "exit 123" INT; trap "echo EXIT \$?" EXIT; sleep 3'
但如果您为 建立一个处理程序SIGINT
,您通常希望它杀死脚本SIGINT
:
a.sh
:
trap 'exit 123' INT
trap 'echo EXIT $?; trap - INT; kill -INT $$' EXIT
sleep 3
$ bash h.sh & pid=$!; sleep 1; kill -INT $pid; wait $pid
[1] 236263
EXIT 123
[1]+ Interrupt bash h.sh
而在 Debian < 10 ( dash < 0.5.10
)下杀死脚本的信号(如果有的话)没有通过。
我想出的解决方案:
set -eu
cleanup() {
echo "cleanup ($1)"
trap - INT TERM EXIT # avoid reexecuting handlers
if [ "$1" = 130 ]; then
kill -INT $$
elif [ "$1" = 143 ]; then
kill -TERM $$
else
exit "$1"
fi
}
trap 'cleanup 130' INT
trap 'cleanup 143' TERM
trap 'cleanup $?' EXIT
if [ "${1-}" = fail ]; then
no-such-command
fi
sleep 3
$ bash f.sh; echo $?
cleanup (0)
0
$ bash f.sh fail; echo $?
f.sh: line 20: no-such-command: command not found
cleanup (127)
127
$ bash f.sh & pid=$!; sleep 1; kill -INT $pid; wait $pid
[1] 282422
cleanup (130)
[1]+ Interrupt bash f.sh
$ bash f.sh & pid=$!; sleep 1; kill -TERM $pid; wait $pid
[1] 282458
cleanup (143)
[1]+ Terminated bash f.sh
测试于:
bash
:5.1.8
- 破折号:
0.5.10
,0.5.8
,0.5.7
- 阿尔派 Linux 3.14 (
busybox
)