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

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

我最近看到了这份清单具有特殊含义的退出代码来自高级 Bash 脚本指南。他们将这些代码称为预订的并建议:

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

不久前,我编写了一个使用以下退出状态代码的脚本:

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

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

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

我在 Bash 文档中找不到任何证实的建议;它的部分关于退出状态只是列出了 Bash 使用的退出代码,但没有说明其中任何一个是预订的或警告不要将它们用于您自己的脚本/程序。

答案1

退出代码有特殊含义,但 in 的值$?可能有特殊含义。

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()(1989 年为 SVr4 引入)使用更好的系统调用接口(类似于 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

人们曾多次尝试标准化进程退出代码的含义。除了你提到的,我还知道:

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

  • GNUgrep文档中退出代码 0 表示至少找到一个匹配项,1 表示未找到匹配项,2 表示发生 I/O 错误;这个约定显然对于其他程序也很有用,对于这些程序来说,“没有出问题,但我没有找到任何东西”和“发生了 I/O 错误”之间的区别是有意义的。

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

  • 在 Windows 上,NTSTATUS代码(不方便地分散在整个 32 位数字空间)可以用作退出代码,特别是指示进程由于灾难性不当行为而终止的代码(例如STATUS_STACK_OVERFLOW)。

您不能指望任何给定的程序都会遵守这些约定中的任何一个。唯一的可靠的规则是退出代码 0 表示成功,其他任何代码表示失败。 (请注意,C89EXIT_SUCCESS不是保证值为零;但是,即使值不同,也exit(0)需要表现相同。)exit(EXIT_SUCCESS)

答案3

sysexist.h对于 shell 脚本,我有时会使用 shell 保留的退出代码(前缀为)来获取相当于 shell 的源代码S_EX_,我将其命名为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代码:exit2strexit2str.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我在交互式 shell中使用它,以便在运行每个命令后,我可以看到它的退出状态及其字符串形式(如果它确实有已知的字符串形式):

[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)适合懒人(我就是其中之一)。

答案4

只要您记录退出代码,以便一年后您必须返回并调整脚本时记住它们,就可以了。除了习惯上用作0成功代码和其他任何代码用作失败代码之外, “保留退出代码”的想法实际上不再适用。

相关内容