使用“保留”代码获取shell脚本的退出状态


15

最近,我从《高级Bash脚本指南》中发现了具有特殊含义退出代码列表。他们将这些代码称为保留代码,并建议:

根据上表,退出代码1-2、126-165和255具有特殊含义,因此对于用户指定的退出参数应避免使用。

前一段时间,我编写了一个脚本,该脚本使用以下退出状态代码:

  • 0-成功
  • 1-错误的主机名
  • 2-指定了无效的参数
  • 3-用户权限不足

当我编写脚本时,我不知道任何特殊的退出代码,因此我只是从第一个错误条件的1开始,然后为每种连续的错误类型增加了退出状态。

我编写该脚本的目的是,在以后的阶段可以被其他脚本调用(可以检查非零退出代码)。我实际上还没有这样做;到目前为止,我只从交互式外壳程序(Bash)运行脚本,并且我想知道使用自定义退出代码可能导致什么/是否有问题。《高级Bash脚本指南》中的建议的相关性/重要性如何?

我在Bash文档中找不到任何确凿的建议;它的“ 退出状态”部分仅列出了Bash使用的退出代码,但未声明保留其中任何一个或警告您不要将其用于自己的脚本/程序。


6
我和其他人认为ABSG的质量普遍较低。在我看来,您链接的页面的作者提出了一个不受支持的断言,即所列出的退出代码显然是基于shell本身将其用于特定含义的事实而保留的。已经尝试创建脚本标准,但是都没有成功。重要的是要记录选择的错误代码,以便脚本(例如其他脚本)的使用者知道根据错误代码做什么。
暂停,直到另行通知。

@DennisWilliamson如果您发表评论作为答案,我很乐意支持它。我已经对所有其他答案进行了投票,因为我发现每个答案都很有用。尽管您的答案的内容与David King(在较小程度上是Zwol的)相似,但您明确声明没有证据表明ABSG报价中的主张。
Anthony G-Monica的正义

1
感谢您的报价,但我相信我的评论应该保持不变。
暂停,直到另行通知。

从那以后,我发现POSIX规范包含类似的建议,因此我已将该信息添加到自己的答案中(包含自提出该问题以来的研究结果)。
安东尼·G-莫妮卡的正义

Answers:


10

已经进行了几次尝试来标准化进程出口代码的含义。除了您提到的那一项,我还知道:

  • BSD具有sysexits.h定义从64开始的值的含义。

  • GNU grep文档的退出代码0表示至少找到一个匹配项,1表示没有找到匹配项,而2表示发生了I / O错误;对于其他程序,“什么都没出错但我什么都没找到”和“发生I / O错误”之间的区别很有意义,因此该约定显然也很有用。

  • C库函数的许多实现都system使用退出代码127指示该程序不存在或无法启动。

  • 在Windows上,可以使用NTSTATUS代码(不方便地散布在整个32位数字空间中)作为退出代码,尤其是那些表示进程由于灾难性的不良行为而终止的代码(例如STATUS_STACK_OVERFLOW)。

您不能指望任何遵循这些约定的特定程序。唯一可靠的规则是,退出代码0是成功,其他任何事情都是某种失败。(请注意,C89的EXIT_SUCCESS不能保证为零;但是,即使值不exit(0)相同,也必须具有相同的行为exit(EXIT_SUCCESS)。)


谢谢。很难选择一个答案,但是我接受这个答案,因为它回答了我的问题,同时还提供了使用中的不同退出代码的广泛风味(带有相关链接):它值得超过3个投票目前有。
Anthony G-Monica的正义

11

没有退出代码具有特殊含义,但是中的值$?可能具有特殊含义。

问题是Bourne Shell和ksh93处理退出代码和错误情况并将它们转发到shell变量$?的方式。与您列出的相反,只有以下值$?具有特殊含义:

  • 126即使二进制文件存在也无法执行
  • 127指定的二进制文件不存在
  • 128个退出状态为== 0,但存在一些未指定的问题

此外,还有一个未指定的Shell和特定于平台的$?代码范围> 128,该范围保留给被信号中断的程序:

  • Bourne Shell bash和ksh88使用128 +信号编号
  • ksh93使用256 +信号编号。

其他值不会带来问题,因为它们可能会与Shell特殊$?值区分开。

特别是,值1和2并不用于特殊条件,而只是内置命令使用的退出代码,当它们不是内置命令时,它们可以起到相同的作用。似乎您提供的bash脚本指南的指针不是一个好手册,因为它仅列出bash使用的代码,而没有评论特定代码是否是应为自己的脚本避免的特殊值。

较新版本的Bourne Shell使用waitid()而不是waitpid()等待程序退出,并且waitid()(对于SVr4于1989年推出)使用了更好的syscall接口(类似于UNOS在1980年使用的接口)。

当较新的Bourne Shell的版本编码退出原因在一个单独的变量${.sh.code}/ ${.sh.codename}退出代码即在${.sh.status}/ ${.sh.termsig},请参阅http://schillix.sourceforge.net/man/man1/bosh.1.html,退出代码没有过载具有特殊状态,并且由于使用了waitid()结果,Bourne Shell现在支持返回退出代码的所有32位-不仅是低8位。

顺便说一句:注意不要exit(256)与C程序或Shell脚本相似或与之相似,因为这会导致在$?经典Shell中解释为0。


2
顺便说一句:我在waitid()5月下旬针对FreeBSD和Linux内核针对此错误做了一个错误报告。FreeBSD人员在20小时内解决了该问题,Linux人员对修复他们的错误不感兴趣。...并且Cygwin的人说他们是一个与Linux兼容的bug ;-)
2015年

2
单个Unix规范要求此行为。有一个32位的值,是的,但是该值包含一个8位的位域,其中包含来自的值的低8位_exit。请链接您所指的FreeBSD错误报告,也许我误会了您所描述的问题。
2015年

2
OP用bash标记了问题,并在问题文本中提到了Bash。Bash是Bourne衍生的外壳。它不支持${.sh.}变量。但是,您说的是“ Bourne”而不是“ Bourne派生的”是正确的(尽管您确实包括ksh93)。
暂停,直到另行通知。

2
该答案似乎非常特定于某些SVR4派生的Unix的特定变体。请更清楚地知道什么是可移植的,什么不是可移植的,请记住,没有“ the” Bourne shell之类的东西,除非您是指V7中的那个。
zwol

4
相反,我相信正是您低估了这里的变化范围,尤其是历史变化。您听起来似乎/bin/sh可以依靠这些特殊的退出代码跨平台来始终如一地工作,但事实并非如此。(我不在乎是否/bin/sh可以将任何特定的系统称为“真正的Bourne shell”。更重要的是要知道,这些都不在POSIX中,并且您引用为“真正的Unix系统”的大多数东西都不会。/bin/sh仍然提供符合POSIX的功能。)
zwol 2015年

6

对于shell脚本编写,我有时会将sysexist.h与shell保留的退出代码(以前缀S_EX_)一起等价于shell 的源代码,我将其命名为exit.sh

基本上是:

EX_OK=0 # successful termination 
EX__BASE=64     # base value for error messages 
EX_USAGE=64     # command line usage error 
EX_DATAERR=65   # data format error 
EX_NOINPUT=66   # cannot open input 
EX_NOUSER=67    # addressee unknown 
EX_NOHOST=68    # host name unknown 
EX_UNAVAILABLE=69       # service unavailable 
EX_SOFTWARE=70  # internal software error 
EX_OSERR=71     # system error (e.g., can't fork) 
EX_OSFILE=72    # critical OS file missing 
EX_CANTCREAT=73 # can't create (user) output file 
EX_IOERR=74     # input/output error 
EX_TEMPFAIL=75  # temp failure; user is invited to retry 
EX_PROTOCOL=76  # remote error in protocol 
EX_NOPERM=77    # permission denied 
EX_CONFIG=78    # configuration error 
EX__MAX=78      # maximum listed value 

#System errors
S_EX_ANY=1      #Catchall for general errors
S_EX_SH=2       #Misuse of shell builtins (according to Bash documentation); seldom seen
S_EX_EXEC=126   #Command invoked cannot execute         Permission problem or command is not an executable
S_EX_NOENT=127  #"command not found"    illegal_command Possible problem with $PATH or a typo
S_EX_INVAL=128  #Invalid argument to exit       exit 3.14159    exit takes only integer args in the range 0 - 255 (see first footnote)                                                                                        
#128+n  Fatal error signal "n"  kill -9 $PPID of script $? returns 137 (128 + 9)                               
#255*   Exit status out of range        exit -1 exit takes only integer args in the range 0 - 255              
S_EX_HUP=129                                                                                                   
S_EX_INT=130   
#...

可以通过以下方式生成:

#!/bin/sh
src=/usr/include/sysexits.h
echo "# Generated from \"$src\"" 
echo "# Please inspect the source file for more detailed descriptions"
echo
< "$src" sed -rn 's/^#define  *(\w+)\s*(\d*)/\1=\2/p'| sed 's:/\*:#:; s:\*/::'
cat<<'EOF'

#System errors
S_EX_ANY=1  #Catchall for general errors
S_EX_SH=2   #Misuse of shell builtins (according to Bash documentation); seldom seen
S_EX_EXEC=126   #Command invoked cannot execute     Permission problem or command is not an executable
S_EX_NOENT=127  #"command not found"    illegal_command Possible problem with $PATH or a typo
S_EX_INVAL=128  #Invalid argument to exit   exit 3.14159    exit takes only integer args in the range 0 - 255 (see first footnote)
#128+n  Fatal error signal "n"  kill -9 $PPID of script $? returns 137 (128 + 9)
#255*   Exit status out of range    exit -1 exit takes only integer args in the range 0 - 255
EOF
$(which kill) -l |tr ' ' '\n'| awk '{ printf "S_EX_%s=%s\n", $0, 128+NR; }'

不过,我使用的不是很多,但是我使用的是一个将错误代码转换为字符串格式的shell函数。我已经命名了exit2str。假设您已命名上述exit.sh生成器exit.sh.sh,则exit2str可以使用(exit2str.sh.sh)生成用于的代码:

#!/bin/sh
echo '
exit2str(){
  case "$1" in'
./exit.sh.sh | sed -nEe's|^(S_)?EX_(([^_=]+_?)+)=([0-9]+).*|\4) echo "\1\2";;|p'
echo "
  esac
}"

我在PS1交互式外壳程序的中使用此命令,以便在我运行每个命令后,可以看到其退出状态和字符串形式(如果它确实具有已知的字符串形式):

[15:58] pjump@laptop:~ 
(0=OK)$ 
[15:59] pjump@laptop:~ 
(0=OK)$ fdsaf
fdsaf: command not found
[15:59] pjump@laptop:~ 
(127=S_NOENT)$ sleep
sleep: missing operand
Try 'sleep --help' for more information.
[15:59] pjump@laptop:~ 
(1=S_ANY)$ sleep 100
^C
[15:59] pjump@laptop:~ 
(130=S_INT)$ sleep 100
^Z
[1]+  Stopped                 sleep 100
[15:59] pjump@laptop:~ 
(148=S_TSTP)$

为了获得这些,您需要为exit2str函数添加一个不可购买的东西:

$ ./exit2str.sh.sh > exit2str.sh #Place this somewhere in your PATH

然后在您~/.bashrc的代码中使用它保存并翻译每个命令提示符下的退出代码,并在您的提示符(PS1)中显示该代码:

    # ...
    . exit2str.sh
PROMPT_COMMAND='lastStatus=$(st="$?"; echo -n "$st"; str=$(exit2str "$st") && echo "=$str"); # ...'
    PS1="$PS1"'\n($lastStatus)\$'
    # ...                                                                                   

观察某些程序如何遵循退出代码约定,有些不遵循退出代码约定,对于了解退出代码约定,或者只是为了能够更轻松地了解正在发生的事情,这非常方便。使用一段时间以来,我可以说许多面向系统的Shell脚本确实遵循约定。EX_USAGE尽管其他代码并不多,但是它非常普遍。尽管总是有$S_EX_ANY(1)懒惰的人(我是其中一员),但我还是会时不时地遵循惯例。


我确实想知道,如果errno代码报告的错误导致错误退出,是否有类似errno代码和退出代码之间的映射使用。我可能需要提出一些合理的映射。
PSkocik 2015年

1
哇!我没想到这么详尽的答案。我一定会尝试将其作为查看不同命令行为方式的一种好方法。谢谢。
安东尼·G-莫妮卡的大法官,2015年

4

只要您记录退出代码,以便您记得从现在起一年后必须回来重新调整脚本,就可以了。“保留退出代码”的想法实际上不再适用,只是说习惯将其0用作成功代码,而将其他任何内容用作失败代码。


4

我能找到的最佳参考是: http //tldp.org/LDP/abs/html/exitcodes.html

根据这个:

1 是错误的总称,我一直看到它用于用户定义的错误。

2 用于滥用shell内置程序,例如语法错误

要直接回答您的问题,使用保留的错误代码可以正常运行脚本,假设您根据错误代码= 1/2/3处理错误,则脚本将按预期运行。

但是,如果遇到任何知道并使用保留的错误代码的人,这可能会造成混淆,这似乎很少见。

您可以使用的另一种选择是,如果有错误,则回显该错误,然后退出,并假设您的脚本遵循Linux惯例,“没有新闻就是好消息”,并且在成功时没有任何回声。

if [ $? -ne 0 ];then
    echo "Error type"
    exit 1
fi

2

根据我收到的答案(很难选择一个答案),使用Bash也使用的退出代码来指示某些类型的错误并没有什么害处。如果用户脚本退出并显示以下错误代码之一,则Bash(或任何其他Unix Shell)将不会执行任何特殊操作(例如运行异常处理程序)。

看来,《高级Bash脚本指南》的作者同意BSD对出口代码(sysexits.h)进行标准化的尝试,只是建议用户在编写Shell脚本时,不要指定已经与预定义退出代码冲突的退出代码。在使用中,即他们将其自定义退出代码限制为64-113范围内的50个可用状态代码。

我很欣赏这个主意(和基本原理),但是如果作者更明确地认为忽略建议无害,那么我更愿意这样做–除了脚本使用者检查错误的情况(例如引用的127示例) (command not found)。

POSIX相关规格

我研究了POSIX关于退出代码的说法,而POSIX规范似乎与Advanced Bash-Scripting Guide的作者一致。我引用了相关的POSIX规范(重点是我的):

命令的退出状态

每个命令的退出状态都会影响其他Shell命令的行为。本节介绍了不是实用程序的命令的退出状态。标准实用程序的退出状态记录在其各自的部分中。

如果未找到命令,则退出状态应为127。如果找到命令名称,但不是可执行实用程序,则退出状态应为126。不使用Shell调用实用程序的应用程序应使用这些退出状态值报告类似的错误。

如果在字扩展或重定向期间命令失败,则其退出状态应大于零。

在内部,为了确定命令是否以非零退出状态退出,外壳程序应通过等效的wait()函数WEXITSTATUS宏(如“系统接口”卷中的定义)识别该命令检索到的整个状态值。 POSIX.1-2008)。当使用特殊参数“?”报告退出状态时,外壳程序应报告可用的退出状态的全部八位。由于收到信号而终止的命令的退出状态应报告为大于128。

exit实用程序

如其他部分所述,某些退出状态值已保留用于特殊用途,并且仅由应用程序用于以下目的:

  • 126 –找到了要执行的文件,但它不是可执行实用程序。
  • 127 –找不到要执行的实用程序。
  • >128 –命令被信号中断。

更多信息

值得的是,我能够验证具有特殊含义退出代码列表中的所有列表,但其中之一。该退出代码表非常有用,因为它提供了更多详细信息-以及如何生成Bash参考中记录的错误代码的示例。

尝试生成退出状态为128

使用Bash版本3.2.25和4.2.46,我尝试引发128 Invalid argument to exit错误,但是每次收到255(退出状态超出范围)时,我都会抛出该错误。例如,如果exit 3.14159作为外壳程序脚本的一部分或在交互式子外壳程序中执行,则该外壳程序将以以下代码退出255

$ exit 3.14159
exit
bash: exit: 3.14159: numeric argument required

为了获得更多的乐趣,我还尝试运行一个简单的C程序,但是在这种情况下,似乎该exit(3)函数在退出前只是将float转换为int(在这种情况下为3):

#include <stdlib.h>
main()
{
    exit(3.14159);
}
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.