我一直在编写一些外壳脚本,如果能够在任何命令失败时停止执行所述外壳脚本,我会发现它很有用。参见下面的示例:
#!/bin/bash cd some_dir ./configure --some-flags make make install
因此,在这种情况下,如果脚本无法更改到指定的目录,那么如果失败,它肯定不想在之后执行./配置。
现在我很清楚我可以对每个命令进行if检查(我认为这是一个无望的解决方案),但是如果其中一个命令失败,是否有一个全局设置可以使脚本退出?
一个成语是:
cd some_dir && ./configure --some-flags && make && make install
我意识到这可能会很长,但对于较大的脚本,您可以将其分解为逻辑函数。
我认为你正在寻找的是trap命令:
trap
trap command signal [signal ...]
有关详细信息,请参阅此页面。
另一种选择是在脚本顶部使用set -e命令-如果任何程序/命令返回非真值,它将使脚本退出。
set -e
要在其中一个命令失败后立即退出脚本,请在开头添加以下内容:
这会导致脚本在某些不属于某些测试的命令(例如在if [ ... ]条件或&&构造中)使用非零退出代码退出时立即退出。
if [ ... ]
&&
使用set -e内置:
#!/bin/bash set -e # Any subsequent(*) commands which fail will cause the shell script to exit immediately
或者,您可以在命令行上传递-e:
-e
bash -e my_script.sh
您还可以使用set +e禁用此行为。
set +e
您可能还需要使用全部或部分-e-u-x和-o pipefail选项,如下所示:
-u
-x
-o pipefail
set -euxo pipefail
-e在错误时退出,-u在未定义变量上出错,-o (for option) pipefail在命令管道故障时退出。一些陷阱和解决方法记录得很好这里。
-o (for option) pipefail
(*)注:
如果失败的命令是没有的一部分,则shell会退出 紧跟而或直到关键字的命令列表, 如果或elif保留字之后的部分测试,部分 在&&或||列表中执行的任何命令,除了命令 在最后的&&或||之后,管道中的任何命令,但 最后一个,或者如果命令的返回值被反转为 !
(来自man bash)
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结合使用。
pipefail
set -e set -o pipefail
-e(errexit):当命令以非零状态退出时,在第一个错误处中止脚本(直到或而循环、如果测试和列表构造除外)
-o pipe失败:导致管道返回管道中返回非零返回值的最后一个命令的退出状态。
第33章.选择
现有答案中遗漏的一点是展示如何继承错误陷阱。bash shell使用set提供了一个这样的选项
bash
set
-E 如果设置,ERR上的任何陷阱都会被shell函数、命令替换和子shell环境中执行的命令继承。在这种情况下,ERR陷阱通常是<强>不强>继承的。
-E
如果设置,ERR上的任何陷阱都会被shell函数、命令替换和子shell环境中执行的命令继承。在这种情况下,ERR陷阱通常是<强>不强>继承的。
ERR
亚当·罗森菲尔德的回答建议使用set -e在某些情况下是正确的,但它有自己的潜在缺陷。见GreyCat的BashFAQ-105-为什么没有设置-e(或设置-o errexit,或陷阱ERR)做我所期望的?
根据手册,设置-e退出
如果一个简单的命令以非零状态退出。如果失败的命令是紧跟while或until关键字之后的命令列表的一部分、until0的一部分、until1或until2列表的一部分(until3、until4之后的命令除外),或者命令的返回值正在通过until5"进行反转,则shell不会退出。
while
until
这意味着,set -e在以下简单情况下不起作用(详细解释可以在wiki上找到)
使用算术运算符let或$((..))(bash 4.1起)将变量值递增为
let
$((..))
#!/usr/bin/env bash set -e i=0 let i++ # or ((i++)) on bash 4.1 or later echo "i is $i"
如果违规命令是没有通过&&或||执行的最后一个命令的一部分。例如,下面的陷阱在预期的时候不会触发
||
#!/usr/bin/env bash set -e test -d nosuchdir && echo no dir echo survived
当在if语句中错误使用时,if语句的退出代码是最后执行的命令的退出代码。在下面的示例中,最后执行的命令是echo,即使test -d失败了,它也不会触发陷阱
if
echo
test -d
#!/usr/bin/env bash set -e f() { if test -d nosuchdir; then echo no dir; fi; } f echo survived
当与命令替换一起使用时,它们将被忽略,除非inherit_errexit设置为bash 4.4
inherit_errexit
#!/usr/bin/env bash set -e foo=$(expr 1-1; true) echo survived
当您使用看起来像赋值但不是赋值的命令时,例如export、declare、typeset或local。这里对f的函数调用将没有退出,因为local已经扫描了之前设置的错误代码。
export
declare
typeset
local
f
set -e f() { local var=$(somecommand that fails); } g() { local var; var=$(somecommand that fails); }
在管道中使用时,违规命令是没有最后一个命令的一部分。例如,下面的命令仍然会通过。一种选择是通过返回第一失败进程的退出代码来启用pipefail:
set -e somecommand that fails | cat - echo survived
理想的建议是没有使用set -e并实现自己的错误检查版本。关于实现自定义错误处理的更多信息,请参阅我对在Bash脚本中引发错误的回答之一