捕获 SIGINT 和类似事件时保留退出代码吗?

捕获 SIGINT 和类似事件时保留退出代码吗?

如果我trap像描述的那样使用,例如http://linuxcommand.org/wss0160.php#trap在退出之前捕获 ctrl-c (或类似的)并进行清理,然后我将更改返回的退出代码。

现在,这在现实世界中可能不会产生影响(例如,因为退出代码不可移植,而且除此之外并不总是明确的,如进程终止时的默认退出代码?)但我仍然想知道是否真的没有办法阻止这种情况并返回中断脚本的默认错误代码?

示例(在 bash 中,但我的问题不应被视为特定于 bash):

#!/bin/bash
trap 'echo EXIT;' EXIT
read -p 'If you ctrl-c me now my return code will be the default for SIGTERM. ' _
trap 'echo SIGINT; exit 1;' INT
read -p 'If you ctrl-c me now my return code will be 1. ' _

输出:

$ ./test.sh # doing ctrl-c for 1st read
If you ctrl-c me now my return code will be the default for SIGTERM.
$ echo $?
130
$ ./test.sh # doing ctrl-c for 2nd read
If you ctrl-c me now my return code will be the default for SIGTERM.
If you ctrl-c me now my return code will be 1. SIGINT 
EXIT
$ echo $?
1

(编辑删除以使其更符合 POSIX。)

(再次编辑以使其成为 bash 脚本,但我的问题不是特定于 shell 的。)

编辑为使用可移植的“INT”作为陷阱,而不是可移植的“SIGINT”。

编辑以删除无用的花括号并添加潜在的解决方案。

更新:

我现在通过简单地退出并硬编​​码一些错误代码并捕获 EXIT 来解决它。这在某些系统上可能会出现问题,因为错误代码可能不同,或者 EXIT 陷阱不可能,但在我的情况下,这已经足够了。

trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM

答案1

实际上,中断 bash 的内部read似乎与中断 bash 运行的命令有点不同。通常,当您输入trap,时,$?已设置,您可以保留它并以相同的值退出:

trap 'rc=$?; echo $rc SIGINT; exit $rc' INT
trap 'rc=$?; echo $rc EXIT; exit $rc' EXIT

如果您的脚本在执行类似命令sleep 甚至内置命令时被中断wait,您将看到

130 SIGINT
130 EXIT

退出代码是 130。但是,对于read -p,它似乎$?是 0 (无论如何在我的 bash 4.3.42 版本上)。


根据我的版本中的更改文件,信号的处理read可能正在进行中... (/usr/share/doc/bash/CHANGES)

此版本 bash-4.3-alpha 和先前版本 bash-4.2-release 之间的更改。

  1. Bash 中的新功能

    河当处于 Posix 模式时,“读取”可被捕获信号中断。运行陷阱处理程序后,read 返回 128+signal 并丢弃任何部分读取的输入。

答案2

$?进入陷阱处理程序后,任何常用的信号退出代码都将可用:

sig_handler() {
    exit_status=$?  # Eg 130 for SIGINT, 128 + (2 == SIGINT)
    echo "Doing signal-specific up"
    exit "$exit_status"
}
trap sig_handler INT HUP TERM QUIT

如果存在单独的 EXIT 陷阱,则可以使用相同的方法:立即将从信号处理程序(如果有)传递的退出状态清除,然后返回保存的退出状态。

答案3

仅返回一些错误代码不足以模拟 SIGINT 退出。我很惊讶到目前为止还没有人提到这一点。进一步阅读:https://www.cons.org/cracauer/sigint.html

正确的做法是:

for sig in EXIT ABRT HUP INT PIPE QUIT TERM; do
    trap "cleanup;
          [ $sig  = EXIT ] && normal_exit_only_cleanup;
          [ $sig != EXIT ] && trap - $sig EXIT && kill -s $sig $$
         " $sig
done

这适用于 Bash、Dash 和 zsh。为了进一步的可移植性,您需要使用数字信号规范(另一方面,zsh 需要命令的字符串参数kill......)

还要注意信号的特殊处理EXIT。这是由于某些 shell(即 Bash)EXIT也对任何信号执行陷阱(在该信号上可能定义的陷阱之后)。陷阱的重置EXIT可以防止这种情况发生。

仅在“正常”退出时执行代码

该检查[ $sig = EXIT ]允许仅在正常(无信号)退出时执行代码。然而,全部信号必须有陷阱,最后重置陷阱EXITnormal_exit_only_cleanup对于不存在的信号也会被调用。它还将通过退出来执行set -e。这可以通过捕获ERR(Dash 不支持)并[ $sig = ERR ]kill.

仅 Bash 的简化版本

另一方面,这种行为意味着在 Bash 中你可以简单地做

trap cleanup EXIT

仅执行一些清理代码并保留退出状态。

已编辑

  • 详细阐述 Bash 的“EXIT 捕获一切”的行为

  • 去掉KILL信号,该信号无法被捕获

  • 从信号名称中删除 SIG 前缀

  • 不要尝试kill -s EXIT

  • 考虑set -e/ERR

答案4

您需要做的就是更改 EXIT 处理程序里面你的清理处理程序。这是一个例子:

#!/bin/bash
cleanup() {
    echo trapped exit
    trap 'exit 0' EXIT
}
trap cleanup EXIT
read -p 'If you ctrl-c me now my return code will be the default for SIGTERM. '

相关内容