捕获SIGINT等时保留出口代码吗?


14

如果我在退出之前使用traphttp://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

您的脚本看起来有些奇怪:您告诉您read要从当前的协同进程中读取内容,并且trap cmd SIGINT不会按标准要求使用起作用trap cmd INT
2015年

是的,在POSIX下,当然没有SIG前缀。
phk 2015年

糟糕,但是“ read -p”也不被支持,因此我将其调整为适用于bash。
phk 2015年

@schily:我不知道您对“协同处理”的含义。
phk 2015年

好吧,Korn Shell手册页说read -p从当前的协同处理中读取输入。
2015年

Answers:


4

所有你需要做的是改变退出处理程序的清理处理。这是一个例子:

#!/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. '

您是trap cleanup INT不是要说trap cleanup EXIT
杰夫·谢勒

我想我的意思是退出。最终调用exit时,但是这样做完成了,我们将陷阱更改为以return 0退出。我不认为信号处理程序是递归的。
2015年

好的,在您的示例中,即使用户正在通过ctrl-c退出我的交互式脚本,我也根本不需要捕获(SIG)INT来进行清理。
phk 2015年

@phk好。我认为尽管您有了如何强制退出以返回0或我收集的其他一些值的问题的想法。我假设您将能够调整代码以将trap EXIT设置放入由SIGINT调用的实际清理处理程序中。
2015年

@rocky:我的目标是拥有一个陷阱处理程序,同时不更改通常没有处理程序时的退出代码。现在,我可以简单地返回您通常会获得的退出代码,但是问题是在这些情况下我不知道确切的退出代码(如我链接到的线程和meuh的帖子所示,它非常复杂)。您的帖子仍然很有帮助,我没有考虑过动态重新定义陷阱处理程序。
phk 2015年

9

实际上,中断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模式下,“读取”可被捕获的信号中断。运行陷阱处理程序后,读取返回128+信号,并丢弃所有部分读取的输入。


好吧,那真的很奇怪。在bash 4.3.42(在cygwin下)的交互式shell上,它是130;在脚本和POSIX模式下,在同一shell下,它是否为0无效。但随后在仪表板和busybox的,它总是1
PHK

我尝试使用没有退出的陷阱的POSIX模式,并且重新启动了read,如CHANGES文件中所述(已添加到我的答案中)。因此,可能正在进行中。
2015年

退出代码130是100%不可移植的。当Bourne Shell使用128 + signo信号的退出代码时,ksh93使用256 + signo。POSIX说:上面的东西.... 128
席利

@schily是True,正如我在原始帖子(unix.stackexchange.com/questions/99112)中链接的线程中所述。
phk 2015年

6

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

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陷阱,则可以使用相同的方法:立即清除从信号处理程序传递的退出状态(如果有),然后返回保存的退出状态。


这适用于大多数贝壳吗?非常好。local不是POSIX。
phk

1
编辑。除了,我没有其他考试{ba,z}sh。AFAIK,无论如何,这是最好的。
汤姆·黑尔'18

这在破折号中不起作用($?无论接收到的信号是1还是1)。
MoonSweep

2

仅返回一些错误代码不足以模拟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信号的特殊处理。这是由于某些外壳程序(即Bash)EXIT也对任何信号执行了陷阱(在该信号可能定义的陷阱之后)。EXIT陷阱的重置可以防止这种情况。

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

该检查[ $sig = EXIT ]仅允许在正常(无信号)退出时执行代码。但是,所有信号都必须具有陷阱,最后才将陷阱复位EXITnormal_exit_only_cleanup也会为不存在的信号而调用。也可以通过退出执行set -e。可以通过设置陷阱ERR(达[ $sig = ERR ]世币不支持)并在之前添加一个检查来解决此问题kill

简化的仅Bash版本

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

trap cleanup EXIT

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

已编辑

  • 精心制作的Bash的“退出陷阱”行为

  • 消除无法捕获的KILL信号

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

  • 不要试图 kill -s EXIT

  • 就拿set -e/ ERR考虑


没有一般要求捕获程序的程序必须以保留接收到信号的事实的方式终止。例如,一个程序可能声明发送SIGINT是关闭它的方式,并且如果成功终止而没有错误,则它可能决定以0退出,或者如果没有彻底关闭,它可能会另一个错误代码。恰当的例子:top。运行top; echo $?,然后按Ctrl-C。屏幕上显示的状态
路易(Louis)

1
感谢您指出这一点。但是,发帖人特别询问如何保留退出代码。
philipp2100
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.