进程终止时的默认退出代码?


54

当某个进程被可处理的信号(例如SIGINT或)杀死SIGTERM而不处理该信号时,该进程的退出代码是什么?

对于像这样的无法处理的信号SIGKILL呢?

据我所知,杀死一个SIGINT可能导致退出代码的进程130,但这会因内核或外壳实现而有所不同吗?

$ cat myScript
#!/bin/bash
sleep 5
$ ./myScript
<ctrl-c here>
$ echo $?
130

我不确定如何测试其他信号...

$ ./myScript &
$ killall myScript
$ echo $?
0  # duh, that's the exit code of killall
$ killall -9 myScript
$ echo $?
0  # same problem

1
您的killall myScript作品,因此killall(而不是脚本!)的返回值为0。您可以放置kill -x $$[x是信号编号,而$$通常由外壳扩展为该脚本的PID(在sh,bash, ...)] 脚本中,然后测试其退出核心是什么。
Olivier Dulac 2013年


关于半问题的评论:不要将myScript放在后台。(忽略&)。从另一个Shell进程(在另一个终端)发送信号,然后$?在myScript结束后可以使用。
MattBianco

Answers:


61

进程可以使用整数参数调用_exit()系统调用(在Linux上,另请参见exit_group()),以向其父项报告退出代码。尽管是整数,但父级仅可使用8个最低有效位(这是在父级中对SIGCHLD 使用waitid()或处理程序以检索该代码时的例外,但在Linux上则除外)。

父级通常会做一个wait()waitpid()以获得其孩子的状态为整数(尽管waitid()也可以使用略有不同的语义)。

在Linux和大多数Unices上,如果进程正常终止,则该状态编号的8至15位将包含传递给的退出代码exit()。如果不是,则7个最低有效位(0至6)将包含信号编号,如果转储内核,则将设置7位。

perl$?例如含有如通过设置数字waitpid()

$ perl -e 'system q(kill $$); printf "%04x\n", $?'
000f # killed by signal 15
$ perl -e 'system q(kill -ILL $$); printf "%04x\n", $?'
0084 # killed by signal 4 and core dumped
$ perl -e 'system q(exit $((0xabc))); printf "%04x\n", $?'
bc00 # terminated normally, 0xbc the lowest 8 bits of the status

类似Bourne的shell也会在其自己的$?变量中使最后一个运行命令的退出状态。但是,它不直接包含所返回的数字waitpid(),而是对其的转换,并且外壳之间的区别是不同的。

所有shell之间的共同点是,如果进程正常终止,则$?包含退出代码的最低8位(传递给的数字exit())。

不同之处在于该过程由信号终止。在所有情况下,POSIX都需要该数字,该数字将大于128。POSIX没有指定该值可能是多少。但实际上,在我所知道的所有类似Bourne的外壳中,最低的7位$?将包含信号编号。但是,n信号号在哪里

  • 在ash,zsh,pdksh,bash,Bourne shell中$?128 + n。这意味着在这些shell中,如果获得$?129,则您不知道是因为进程退出了exit(129)还是被信号杀死1HUP在大多数系统上)。但是基本原理是,当shell退出自身时,它们默认情况下会返回最后退出的命令的退出状态。通过确保$?绝对不大于255,可以保持一致的退出状态:

    $ bash -c 'sh -c "kill \$\$"; printf "%x\n" "$?"'
    bash: line 1: 16720 Terminated              sh -c "kill \$\$"
    8f # 128 + 15
    $ bash -c 'sh -c "kill \$\$"; exit'; printf '%x\n' "$?"
    bash: line 1: 16726 Terminated              sh -c "kill \$\$"
    8f # here that 0x8f is from a exit(143) done by bash. Though it's
       # not from a killed process, that does tell us that probably
       # something was killed by a SIGTERM
    
  • ksh93$?256 + n。这意味着从$?您的价值可以区分已终止和未终止的过程。较新版本的ksh,如果退出$?时大于255 ,则会使用相同的信号杀死自己,以便能够向其父节点报告相同的退出状态。虽然这听起来像是一个好主意,但这意味着ksh如果该进程被产生核心的信号杀死,则会产生一个额外的核心转储(可能会覆盖另一个):

    $ ksh -c 'sh -c "kill \$\$"; printf "%x\n" "$?"'
    ksh: 16828: Terminated
    10f # 256 + 15
    $ ksh -c 'sh -c "kill -ILL \$\$"; exit'; printf '%x\n' "$?"
    ksh: 16816: Illegal instruction(coredump)
    Illegal instruction(coredump)
    104 # 256 + 15, ksh did indeed kill itself so as to report the same
        # exit status as sh. Older versions of `ksh93` would have returned
        # 4 instead.
    

    您甚至可以说存在一个错误,ksh93即使$?它来自return 257某个函数的完成,也会杀死自己:

    $ ksh -c 'f() { return "$1"; }; f 257; exit'
    zsh: hangup     ksh -c 'f() { return "$1"; }; f 257; exit'
    # ksh kills itself with a SIGHUP so as to report a 257 exit status
    # to its parent
    
  • yashyash提供一个折衷方案。它返回256 + 128 + n。这意味着我们还可以区分被终止的进程和正确终止的进程。并且在退出时,它将报告128 + n而不必自杀本身及其可能产生的副作用。

    $ yash -c 'sh -c "kill \$\$"; printf "%x\n" "$?"'
    18f # 256 + 128 + 15
    $ yash -c 'sh -c "kill \$\$"; exit'; printf '%x\n' "$?"
    8f  # that's from a exit(143), yash was not killed
    

要从的值获取信号$?,可移植的方法是使用kill -l

$ /bin/kill 0
Terminated
$ kill -l "$?"
TERM

(出于可移植性,您永远不要使用信号编号,而只能使用信号名称)

在非伯恩方面:

  • csh/ tcshfish相同,除了所述状态是在伯恩壳$status代替$?(注意,zsh还设置$status有用于兼容性csh(除$?))。
  • rc:退出状态也处于$status,但是当被信号杀死时,该变量包含信号的名称(例如sigtermsigill+core是否生成内核)而不是数字,这再次证明了该外壳的良好设计。
  • es。退出状态不是变量。如果需要,可以按以下方式运行命令:

    status = <={cmd}
    

    会传回中的数字sigtermsigsegv+core类似符号rc

也许对于完整性,我们应该提到zsh$pipestatusbash$PIPESTATUS包含最后一个流水线的组件的退出状态阵列。

而且为了完整性,在涉及外壳函数和源文件时,默认情况下,函数以上次运行命令的退出状态返回,但也可以使用return内置函数显式设置返回状态。我们在这里看到一些区别:

  • bashmksh(因为R41,回归^ Wchange显然有意引入的)将截断号(正或负),以8位。例如,return 1234将设置$?210return -- -1将设置$?为255。
  • zshpdksh(以及以外的派生mksh)允许任何有符号的32位十进制整数(-2 31至2 31 -1)(并将数字截断为32位)。
  • ashyash允许使用0到2 31 -1之间的任何正整数,并返回一个错误。
  • ksh93用于return 0按原样return 320设置$?,但对于其他任何内容,请截断为8位。如前所述,请注意,返回256到320之间的数字可能会导致ksh退出时自杀。
  • rces允许返回任何东西甚至列表。

还要注意,某些外壳程序还使用$?/的特殊值$status来报告不是进程退出状态的某些错误情况,例如127126对于未找到无法执行的命令(或源文件中的语法错误)...


1
an exit code to their parentto get the *status* of their child。您已经增加了对“状态”的重视。是exit code*status*相同吗?案例是的,两个名字的来历是什么?情况不一样,您能给出状态的定义/参考吗?
n611x007 2014年

2
这里有3个数字。在退出代码:传递给数exit()。的退出状态:由所获得的数量waitpid(),其包括退出代码,信号数量和是否有一个核心转储。而且,一些炮弹在他们的特殊变量(一个使可用数量$?$status)是的转型退出状态以这样的方式,是不包含退出代码的情况下,有一个正常的终止,也承载如果信号信息该进程被杀死(通常也称为退出状态)。这就是我的回答。
斯特凡Chazelas

1
我看谢谢你!我肯定会赞赏这里明确的区别。这些关于出口的表述在某些地方可以互换使用,值得使用。shell变量变体甚至具有(通用)名称吗?因此,我建议深入了解Shell细节之前,先将其明确清除。我建议在第一段或第二段之后插入解释(根据您的评论)。
n611x007 2014年

1
您能否指向POSIX引号,该引号说明前7位是信号?我只能找到的> 128部分是:“由于收到信号而终止的命令的退出状态应报告为大于128。” pubs.opengroup.org/onlinepubs/9699919799/utilities/...
西罗桑蒂利新疆改造中心法轮功六四事件

1
@cuonglm,我认为它不能通过HTTP在其他任何地方公开使用,您仍然可以通过NNTP从gmane获得它。寻找消息ID efe764d811849b34eef24bfb14106f61@austingroupbugs.net(从2015年5月6日)或Xref: news.gmane.org gmane.comp.standards.posix.austin.general:10726
斯特凡Chazelas

23

进程退出时,它将向操作系统返回一个整数值。在大多数Unix变体中,该值取256模:除低位以外的所有值都将被忽略。子进程的状态通过一个16位整数返回给其父进程,其中

  • 位0–6(低7位)是用于终止进程的信号号;如果进程正常退出,则为0;
  • 如果进程被信号和转储内核杀死,则设置位7;
  • 如果进程正常退出,则第8-15位是进程的退出代码;如果进程被信号杀死,则位为0。

该状态由wait系统调用或其同级之一返回。POSIX没有指定出口状态和信号号的确切编码;它只提供

  • 一种判断退出状态是对应于信号还是正常退出的方法;
  • 如果进程正常退出,则访问退出代码的方法;
  • 如果进程被信号杀死,一种访问信号号的方法。

严格来说,当进程被信号杀死时,没有退出代码:而是退出状态

在Shell脚本中,命令退出状态通过特殊变量来报告$?。此变量以一种模糊的方式对退出状态进行编码:

  • 如果该进程正常退出,$?则为退出状态。
  • 如果该进程被一个信号杀死,那么$?在大多数系统上,它是128加信号号。$?在这种情况下,POSIX仅要求大于128;ksh93加256而不是128。我从未见过unix变体,除了将常数添加到信号号之外,它没有做任何其他事情。

因此,在shell脚本中,除了ksh93之外,您无法确定性地确定命令是被信号杀死还是以大于128的状态代码退出。程序退出时,状态代码大于128的情况非常少见,部分原因是由于$?歧义,程序员避免使用它。

SIGINT在大多数unix变体上是信号2,因此$?对于被SIGINT终止的进程,它是128 + 2 = 130。对于SIGHUP,您将看到129;对于SIGKILL,您将看到137,等等。


即使它在本质上说相同的话,其措词也要比我的好得多,而且更切题。您可能需要澄清的$?是,仅适用于类似伯恩的贝壳。另请参见yash以了解其他行为(但仍为POSIX)。同样按照POSIX + XSI(Unix),a kill -2 "$pid"将向该进程发送一个SIGINT,但是实际的信号编号可能不是2,所以$?尽管kill -l "$?"将返回INT,不一定会是128 + 2(或256 + 2或384 + 2),这就是为什么我建议可移植性不要指代数字本身。
斯特凡Chazelas

8

这取决于您的外壳。在bash(1)手册页的“ 外壳语法”部分的“ 简单命令”子部分中:

一个的返回值简单的命令是[...] 128+ Ñ如果命令是通过信号终止Ñ

由于SIGINT系统上的信号号为2,因此在Bash下运行时返回值为130。


1
您如何在世界上找到它,甚至不知道在哪里看?我在你的天才面前鞠躬。
科里·克莱因

1
@CoryKlein:主要是经验。哦,您可能还会想要signal(7)手册页。
Ignacio Vazquez-Abrams

很酷的东西; 你知道我是否偶然在C文件中包含那些常量吗?+1
Rui F Ribeiro

@CoryKlein为什么不选择此作为正确答案?
Rui F Ribeiro

3

在1989年SVr4引入了waitid()似乎是正确的地方,但是到目前为止,没有重要的程序使用它。waitid()允许从exit()代码中检索完整的32位。

大约2个月前,我重写了Bourne Shell的wait / job控制部分,以使用waitid()而不是waitpid()。这样做是为了消除用0xFF屏蔽退出代码的限制。

除了1980年以来UNOS的cwait()调用外,waitid()接口比以前的wait()实现要干净得多。

您可能有兴趣在以下位置阅读手册页:

http://schillix.sourceforge.net/man/man1/bosh.1.html

并检查当前盯着第8页的“参数替换”部分。

已经为waitid()接口引入了新变量.sh。*。对于以$?闻名的数字,该接口不再具有含糊的含义。并使连接更加容易。

请注意,您需要具有POSIX兼容的waitid()才能使用此功能,因此Mac OS X和Linux当前不提供此功能,但是waitid()是在waitpid()调用上模拟的,因此在非POSIX平台上,您仍然只能从退出代码中获得8位。

简而言之:.sh.status是数字退出代码,.sh.code是数字退出原因。

为了更好的可移植性,请使用:.sh.codename作为退出原因的文本版本,例如“ DUMPED”和.sh.termsig,这是终止进程的信号的唯一名称。

为了更好地使用,有两个与退出无关的.sh.codename值:当根本无法启动程序时使用“ NOEXEC”和“ NOTFOUND”。

FreeBSD在我报告后的20小时内修复了他们的waitid()内核错误,Linux尚未开始修复。我希望在引入现在POSIX中的此功能26年之后,所有OS都将很快对其提供支持。


一个相关的答案是unix.stackexchange.com/a/453432/5132
JdeBP '18年
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.