捕获 SIGINT 等时保留退出代码?

phk*_*phk 18 shell signals shell-script exit trap

如果我使用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. ' _
Run Code Online (Sandbox Code Playgroud)

输出:

$ ./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
Run Code Online (Sandbox Code Playgroud)

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

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

编辑以使用便携式“INT”作为陷阱,以支持非便携式“SIGINT”。

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

更新:

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

trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM
Run Code Online (Sandbox Code Playgroud)

meu*_*euh 12

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

trap 'rc=$?; echo $rc SIGINT; exit $rc' INT
trap 'rc=$?; echo $rc EXIT; exit $rc' EXIT
Run Code Online (Sandbox Code Playgroud)

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

130 SIGINT
130 EXIT
Run Code Online (Sandbox Code Playgroud)

退出代码是 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 并丢弃任何部分读取的输入。


Tom*_*ale 8

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

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
Run Code Online (Sandbox Code Playgroud)

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


phi*_*100 7

仅返回一些错误代码不足以模拟 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
Run Code Online (Sandbox Code Playgroud)

这适用于 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
Run Code Online (Sandbox Code Playgroud)

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

已编辑

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

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

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

  • 不要尝试kill -s EXIT

  • 考虑set -e/ERR


roc*_*cky 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. '
Run Code Online (Sandbox Code Playgroud)