【问题标题】:Automatic exit from Bash shell script on error [duplicate]错误时自动退出 Bash shell 脚本 [重复]
【发布时间】:2011-02-21 16:41:07
【问题描述】:

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

#!/bin/bash

cd some_dir

./configure --some-flags

make

make install

所以在这种情况下,如果脚本不能切换到指定的目录,那么如果失败,它肯定不想在之后执行 ./configure

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

【问题讨论】:

  • 关于 set -e 的详细信息请联系 Adam(这正是我们想要的)。还要感谢 a_m0d 提供有关陷阱的信息(尽管不是 100% 相关)。
  • 这些答案是否适用于shbash 一样吗?

标签: bash shell error-handling exit


【解决方案1】:

使用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 命令管道失败退出。一些问题和解决方法已被很好地记录在 here

(*) 注意:

如果失败的命令是 命令列表紧跟 whileuntil 关键字, ifelif 保留字之后的部分测试,部分 在 &&|| 列表中执行的任何命令,除了命令 在最后的 &&|| 之后,管道中的任何命令,但 最后一个,或者如果命令的返回值被反转

(来自man bash

【讨论】:

  • 这也是 Bourne Shell 内置的吗?
  • 'Set +e' 将再次恢复设置,因此您只能让某些块在出错时自动退出。
  • 如果shell脚本在远程服务器上执行命令,如果任何远程命令会产生错误,或者set -e是否也包含在远程命令序列中,它也会中断吗?
  • 请注意set -e 是一个不稳定的功能,有时可能无法达到您的预期。请参阅此处以获得精彩的解释:mywiki.wooledge.org/BashFAQ/105
【解决方案2】:

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

set -e

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

【讨论】:

【解决方案3】:

这是怎么做的:

#!/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 *** 
************
'

【讨论】:

  • trap 'abort' 0
  • 0 -- EXIT,退出时的 IOW 陷阱
  • 如果在 EXIT ('0') 陷阱中不使用“exit”,shell 将退出并保留原始退出值!
  • 请展开此答案。这种技术究竟是如何工作的?
【解决方案4】:

pipefail结合使用。

set -e
set -o pipefail

-e (errexit):在第一个错误时中止脚本,当命令以非零状态退出时(除了在 untilwhile 循环中,if-tests 和列表结构)

-o pipefail:使管道返回管道中返回非零返回值的最后一个命令的退出状态。

Chapter 33. Options

【讨论】:

  • 那是“set -o errexit”或“set -e”。
【解决方案5】:

适合第一行的已接受答案的替代方案:

#!/bin/bash -e

cd some_dir  

./configure --some-flags  

make  

make install

【讨论】:

  • 我以前见过这种情况(在 #! 行中添加了 '-e'),但对我来说,它在 Mac OS X 上使用 bash v3.2.57 时无法正常工作。简单的测试脚本调用 /usr/bin/false 后跟 echo 不要在预期时保释。使用上面接受的“set -e”可以正常工作。
【解决方案6】:

一个成语是:

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

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

【讨论】:

  • 您可以在&& 之后添加换行符以提高可读性
【解决方案7】:

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

trap command signal [signal ...]

有关详细信息,请参阅this page

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

【讨论】:

    【解决方案8】:

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

    -E

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


    Adam Rosenfield's answer 建议使用set -e 在某些情况下是正确的,但它有其自身的潜在缺陷。见GreyCat's BashFAQ - 105 - Why doesn't set -e (or set -o errexit, or trap ERR) do what I expected?

    根据手册,set -e 退出

    如果一个简单的命令以非零状态退出。如果失败的命令是紧跟在 whileuntil 关键字(@987654331 的一部分)之后的命令列表的一部分,则 shell 不会退出@&&|| 列表的一部分,final && or || 后面的命令除外any command in a pipeline but the last,或者如果通过 !" 反转命令的返回值。

    这意味着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 语句中错误地使用 as 时,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 并实现自己的错误检查版本。有关在我对Raise error in a Bash script 的回答之一中实施自定义错误处理的更多信息

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-05
      • 2019-08-11
      • 1970-01-01
      • 1970-01-01
      • 2016-10-17
      相关资源
      最近更新 更多