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

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

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上的处理程序。SIGINTSIGTERM

对于它们,您可以尝试为多个信号设置一个处理程序 ( 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

测试于:

  • bash5.1.8
  • 破折号:0.5.10, 0.5.8,0.5.7
  • 阿尔派 Linux 3.14 ( busybox)

相关内容