基于进程退出代码的退出Shell脚本


378

我有一个执行许多命令的Shell脚本。如果任何命令以非零退出代码退出,如何使shell脚本退出?


3
我回答了您使用bash的假设,但是如果它是一个非常不同的shell,您可以在帖子中指定吗?
Martin W

硬方法:$?在每个命令之后测试的值。简单方法:将set -e#!/bin/bash -e放在Bash脚本的顶部。
mwfearnley

Answers:


494

在每个命令之后,都可以在$?变量中找到退出代码,因此您将获得以下内容:

ls -al file.ext
rc=$?; if [[ $rc != 0 ]]; then exit $rc; fi

您需要注意管道命令,因为$?唯一命令会为您提供管道中最后一个元素的返回代码,因此,在代码中:

ls -al file.ext | sed 's/^/xx: /"

如果文件不存在,则不会返回错误代码(因为sed管道的一部分实际起作用,返回0)。

bash壳实际上提供一种阵列,其可以协助在这种情况下,即是PIPESTATUS。对于每个管道组件,此数组都有一个元素,您可以像这样分别访问它们${PIPESTATUS[0]}

pax> false | true ; echo ${PIPESTATUS[0]}
1

请注意,这是您获得false命令的结果,而不是整个管道的结果。您还可以按照自己的意愿处理整个列表:

pax> false | true | false; echo ${PIPESTATUS[*]}
1 0 1

如果您想从管道中获取最大的错误代码,则可以使用以下方法:

true | true | false | true | false
rcs=${PIPESTATUS[*]}; rc=0; for i in ${rcs}; do rc=$(($i > $rc ? $i : $rc)); done
echo $rc

PIPESTATUS依次遍历每个元素,rc如果它大于先前的rc值,则将其存储在其中。


39
仅一行可移植代码中的相同功能: ls -al file.ext || exit $?([[]不可移植)
MarcH 2010年

19
MarcH,我想您会发现它[[ ]]在中很容易移植bash,这就是标记问题的地方:-)奇怪的是,它也无法移植lscommand.com所以也不是可移植的,我想这很可能,但这与您的观点相同当下。
paxdiablo

39
我知道这很古老,但是应该注意的是,您可以通过数组在管道中获取命令的退出代码PIPESTATUS(即,${PIPESTATUS[0]}对于第一个命令,${PIPESTATUS[1]}第二个命令或${PIPESTATUS[*]}所有退出状态的列表。)
DevSolar

11
需要强调的是,优雅而惯用的shell脚本很少需要$?直接检查。通常,您通常希望使用if ls -al file.ext; then : nothing; else exit $?; fi类似@MarcH所说的等价的东西,ls -al file.ext || exit $?但是如果thenor else子句更复杂,则它更易于维护。
2012年

9
[[ $rc != 0 ]]将给您一个0: not found1: not found错误。应该将其更改为[ $rc -ne 0 ]rc=$?然后也可以将其删除并使用[ $? -ne 0 ]
CurtisLeeBolin

223

如果要使用$ ?,则需要在每个命令后进行检查,因为$?每个命令退出后更新。这意味着,如果您执行管道,则只会获取管道中最后一个进程的退出代码。

另一种方法是这样做:

set -e
set -o pipefail

如果将其放在shell脚本的顶部,则bash似乎会为您解决这个问题。如前所述,“ set -e”将导致bash退出并在任何简单命令上出现错误。“ set -o pipefail”也将导致bash退出,并且管道中的任何命令也会出错。

有关此问题的更多讨论,请参见此处此处是内置的bash手册部分。


6
这实际上应该是最重要的答案:比PIPESTATUS在任何地方使用和检查退出代码要容易得多。
candu '16

2
#!/bin/bash -e是启动Shell脚本的唯一方法。您始终可以使用诸如foo || handle_error $?需要实际检查出口状态之类的东西。
戴维斯·鲱鱼

53

set -e”可能是最简单的方法。只需将其放在程序中的任何命令之前即可。


6
@SwaroopCH set -e如果脚本中的任何命令以错误状态退出并且您未处理此错误,则脚本将中止。
安德鲁

2
set -e是100%等同于set -o errexit可以搜索的前者。搜索opengroup + errexit以获得官方文档。
MarcH

30

如果您仅在bash中调用exit而没有任何参数,它将返回最后一条命令的退出代码。如果与OR结合使用,则bash仅在前一个命令失败时才调用exit。但是我还没有测试过。

command1 || 出口;
command2 || 出口;

Bash还将最后一个命令的退出代码存储在变量$?中。



21

http://cfaj.freeshell.org/shell/cus-faq-2.html#11

  1. 如何获得cmd1in 的退出代码cmd1|cmd2

    首先,请注意,cmd1退出代码可以为非零值,但仍然并不意味着错误。例如发生在

    cmd | head -1

    您可能会看到141(或带有ksh93的269)退出状态cmd1,但这是因为在读取一行后终止cmd时被SIGPIPE信号中断了 head -1

    知道管道元素的退出状态 cmd1 | cmd2 | cmd3

    一个。使用zsh:

    退出代码在pipestatus特殊数组中提供。 cmd1输入退出代码$pipestatus[1],输入cmd3退出代码 $pipestatus[3],因此$?总是与 $pipestatus[-1]

    b。用bash:

    退出代码在PIPESTATUS特殊数组中提供。 cmd1输入退出代码${PIPESTATUS[0]},输入cmd3退出代码 ${PIPESTATUS[2]},因此$?总是与 ${PIPESTATUS: -1}

    ...

    有关更多详细信息,请参见以下链接


19

对于bash:

# this will trap any errors or commands with non-zero exit status
# by calling function catch_errors()
trap catch_errors ERR;

#
# ... the rest of the script goes here
#  

function catch_errors() {
   # do whatever on errors
   # 
   #
   echo "script aborted, because of errors";
   exit 0;
}

19
可能不应“退出0”,因为这表示成功。
nobar 2010年

3
exit_code = $ ?; echo“脚本因错误而中止”;退出$ exit_code
RaSergiy 2012年

1
@ HAL9001你有证据吗?IBM文档另有说明
帕特里克·詹姆斯·麦克杜格

11

在bash中,这很容易,只需将它们与&&绑在一起:

command1 && command2 && command3

您还可以使用嵌套的if构造:

if command1
   then
       if command2
           then
               do_something
           else
               exit
       fi
   else
       exit
fi

+1这是我正在寻找的最简单的解决方案。此外,if (! command)如果您希望命令返回非零错误代码,也可以编写。
Berci

这是用于顺序命令的..如果我要并行启动这3个命令,并且如果其中任何一个失败,则杀死所有人,该怎么办?
Vasile Surdu

4
#
#------------------------------------------------------------------------------
# run a command on failure exit with message
# doPrintHelp: doRunCmdOrExit "$cmd"
# call by:
# set -e ; doRunCmdOrExit "$cmd" ; set +e
#------------------------------------------------------------------------------
doRunCmdOrExit(){
    cmd="$@" ;

    doLog "DEBUG running cmd or exit: \"$cmd\""
    msg=$($cmd 2>&1)
    export exit_code=$?

    # if occured during the execution exit with error
    error_msg="Failed to run the command:
        \"$cmd\" with the output:
        \"$msg\" !!!"

    if [ $exit_code -ne 0 ] ; then
        doLog "ERROR $msg"
        doLog "FATAL $msg"
        doExit "$exit_code" "$error_msg"
    else
        #if no errors occured just log the message
        doLog "DEBUG : cmdoutput : \"$msg\""
        doLog "INFO  $msg"
    fi

}
#eof func doRunCmdOrExit

2
很少有使用的理由$*;使用"$@"而不是保留空间和通配符。
戴维斯·鲱鱼
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.