错误时自动退出Bash外壳脚本

我一直在编写一些外壳脚本,如果能够在任何命令失败时停止执行所述外壳脚本,我会发现它很有用。参见下面的示例:

#!/bin/bash


cd some_dir


./configure --some-flags


make


make install

因此,在这种情况下,如果脚本无法更改到指定的目录,那么如果失败,它肯定不想在之后执行./配置

现在我很清楚我可以对每个命令进行if检查(我认为这是一个无望的解决方案),但是如果其中一个命令失败,是否有一个全局设置可以使脚本退出?

370480 次浏览

一个成语是:

cd some_dir && ./configure --some-flags && make && make install

我意识到这可能会很长,但对于较大的脚本,您可以将其分解为逻辑函数。

我认为你正在寻找的是trap命令:

trap command signal [signal ...]

有关详细信息,请参阅此页面

另一种选择是在脚本顶部使用set -e命令-如果任何程序/命令返回非真值,它将使脚本退出。

要在其中一个命令失败后立即退出脚本,请在开头添加以下内容:

set -e

这会导致脚本在某些不属于某些测试的命令(例如在if [ ... ]条件或&&构造中)使用非零退出代码退出时立即退出。

使用set -e内置:

#!/bin/bash
set -e
# Any subsequent(*) commands which fail will cause the shell script to exit immediately

或者,您可以在命令行上传递-e

bash -e my_script.sh

您还可以使用set +e禁用此行为。

您可能还需要使用全部或部分-e-u-x-o pipefail选项,如下所示:

set -euxo pipefail

-e在错误时退出,-u在未定义变量上出错,-o (for option) pipefail在命令管道故障时退出。一些陷阱和解决方法记录得很好这里

(*)注:

如果失败的命令是没有的一部分,则shell会退出 紧跟直到关键字的命令列表, 如果elif保留字之后的部分测试,部分 在&&||列表中执行的任何命令,除了命令 在最后的&&||之后,管道中的任何命令,但 最后一个,或者如果命令的返回值被反转为

(来自man bash

以下是如何做到这一点:

#!/bin/sh


abort()
{
echo >&2 '
***************
*** ABORTED ***
***************
'
echo "An error occurred. Exiting..." >&2
exit 1
}


trap 'abort' 0


set -e


# Add your script below....
# If an error occurs, the abort() function will be called.
#----------------------------------------------------------
# ===> Your script goes here
# Done!
trap : 0


echo >&2 '
************
*** DONE ***
************
'

替代,替代:适合第一行的公认答案的替代:

#!/bin/bash -e


cd some_dir


./configure --some-flags


make


make install

将其与pipefail结合使用。

set -e
set -o pipefail

-e(errexit):当命令以非零状态退出时,在第一个错误处中止脚本(直到循环、如果测试和列表构造除外)

-o pipe失败:导致管道返回管道中返回非零返回值的最后一个命令的退出状态。

第33章.选择

现有答案中遗漏的一点是展示如何继承错误陷阱。bash shell使用set提供了一个这样的选项

-E

如果设置,ERR上的任何陷阱都会被shell函数、命令替换和子shell环境中执行的命令继承。在这种情况下,ERR陷阱通常是<强>不继承的。


亚当·罗森菲尔德的回答建议使用set -e在某些情况下是正确的,但它有自己的潜在缺陷。见GreyCat的BashFAQ-105-为什么没有设置-e(或设置-o errexit,或陷阱ERR)做我所期望的?

根据手册,设置-e退出

如果一个简单的命令以非零状态退出。如果失败的命令是紧跟whileuntil关键字之后的命令列表的一部分、until0的一部分、until1或until2列表的一部分(until3、until4之后的命令除外),或者命令的返回值正在通过until5"进行反转,则shell不会退出。

这意味着,set -e在以下简单情况下不起作用(详细解释可以在wiki上找到)

  1. 使用算术运算符let$((..))bash 4.1起)将变量值递增为

    #!/usr/bin/env bash
    set -e
    i=0
    let i++                   # or ((i++)) on bash 4.1 or later
    echo "i is $i"
    
  2. 如果违规命令是没有通过&&||执行的最后一个命令的一部分。例如,下面的陷阱在预期的时候不会触发

    #!/usr/bin/env bash
    set -e
    test -d nosuchdir && echo no dir
    echo survived
    
  3. 当在if语句中错误使用时,if语句的退出代码是最后执行的命令的退出代码。在下面的示例中,最后执行的命令是echo,即使test -d失败了,它也不会触发陷阱

    #!/usr/bin/env bash
    set -e
    f() { if test -d nosuchdir; then echo no dir; fi; }
    f
    echo survived
    
  4. 当与命令替换一起使用时,它们将被忽略,除非inherit_errexit设置为bash 4.4

    #!/usr/bin/env bash
    set -e
    foo=$(expr 1-1; true)
    echo survived
    
  5. 当您使用看起来像赋值但不是赋值的命令时,例如exportdeclaretypesetlocal。这里对f的函数调用将没有退出,因为local已经扫描了之前设置的错误代码。

    set -e
    f() { local var=$(somecommand that fails); }
    g() { local var; var=$(somecommand that fails); }
    
  6. 在管道中使用时,违规命令是没有最后一个命令的一部分。例如,下面的命令仍然会通过。一种选择是通过返回第一失败进程的退出代码来启用pipefail

    set -e
    somecommand that fails | cat -
    echo survived
    

理想的建议是没有使用set -e并实现自己的错误检查版本。关于实现自定义错误处理的更多信息,请参阅我对在Bash脚本中引发错误的回答之一