Bash shell脚本中遇到错误自动退出

814

我正在编写一些Shell脚本,如果有任何命令失败,我会发现停止执行该Shell脚本非常有用。以下是一个示例:

#!/bin/bash

cd some_dir

./configure --some-flags

make

make install

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

现在我知道我可以为每个命令添加if检查(我认为这是一个绝望的解决方案),但是否有全局设置使脚本在一个命令失败时退出?


2
感谢Adam对于set -e的详细解答(这正是我想知道的)。同时也感谢a_m0d提供关于traps的信息(虽然不完全相关)。 - radman
2
这些答案是否同样适用于shbash - William Entriken
8个回答

1297

使用内置命令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参数在遇到未定义的变量时报错,-x参数在执行命令前打印命令本身,-o (for option) pipefail参数在管道命令执行失败时退出。此外,一些注意事项和解决方案在这里文档中有详细记录。

(*) 注意:

如果失败的命令是紧接着 whileuntil 关键字之后的命令列表的一部分、ifelif 保留字之后的测试的一部分、&&|| 列表中除了最后一个命令之外的任何命令、管道命令中除了最后一个命令之外的任何命令或者命令的返回值被!反转的情况下,shell不会退出

(来自 man bash)


7
这也是Bourne Shell的内置命令吗? - Tom
6
@Tom 是的:请参阅http://pubs.opengroup.org/onlinepubs/009695399/utilities/set.html#tag_04_127_03。 - Max Nanasy
11
"Set +e"会撤销该设置,因此您可以仅设置某些块,在出现错误时自动退出。 - olivervbk
3
如果Shell脚本在远程服务器上执行命令,那么如果任何远程命令产生错误,它是否也会中断,或者需要包括set -e在远程命令序列中? - user487772
5
请注意,set -e 是一个不太可靠的功能,有时可能会做出您意料之外的事情。请参见这里的精彩解释:http://mywiki.wooledge.org/BashFAQ/105。 - jlh
显示剩余9条评论

85

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

set -e

当某个命令(例如在if [ ... ]条件或&&结构中)以非零退出码退出时,这将导致脚本立即退出,而此命令不是测试的一部分。


更多信息请参见:http://www.davidpashley.com/articles/writing-robust-shell-scripts.html#id2382181 - dhornbein

78

在使用时要与pipefail一起使用。

set -e
set -o pipefail

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

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

第33章. 选项


4
那是“set -o errexit”,或者“set -e”。 - user1457432
4
如果有人想知道,它也适用于Bourne Shell (/bin/sh)。 - Sridhar Sarnobat

59

以下是如何操作的步骤:

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

2
0 -- 退出,即在退出时陷入IOW - azat
如果在 EXIT ('0') 陷阱中不使用 "exit",则 shell 将以保留的原始退出值退出! - anthony
16
请扩展此答案。这项技术究竟是如何工作的? - wjandrea

31

一个与被接受答案相比,适合放在第一行的替代方案:

#!/bin/bash -e

cd some_dir  

./configure --some-flags  

make  

make install

10
我曾经见过这种方法(在#!行中加入"-e"),但是在我的Mac OS X上,使用bash v3.2.57却没有按预期工作。当我使用调用/usr/bin/false的简单测试脚本后跟着echo时,并没有像预期的那样停止程序。使用上述被接受的"set -e"却可以正常工作。 - pauldoo

23

一个常用语是:

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

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


13
你可以在“&&”之后添加换行符以提高可读性。 - glenn jackman

19

我认为你要找的是 trap 命令:

trap command signal [signal ...]
更多信息请参见此页面
另一个选择是在脚本顶部使用set -e命令 - 它将使脚本在任何程序/命令返回非真值时退出。

13
现有答案中忽略了如何继承错误陷阱的方法。使用setbash shell提供了一种选项来实现这一点。

-E

如果设置了此选项,则任何对ERR的陷阱都将被shell函数、命令替换和在子shell环境中执行的命令所继承。通常,在这些情况下不会继承ERR陷阱。


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

根据手册,如果一个简单命令以非零状态退出,则set -e会退出。

如果一个简单命令以非零状态退出,则shell不会退出。如果失败的命令是紧随whileuntil关键字之后的命令列表的一部分,是if语句中的测试的一部分,是&&或||列表的一部分,除了最终的&&或||之后的命令,管道中除了最后一个命令之外的任何命令,或者如果通过!来反转命令的返回值,则shell不会退出。

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

  1. Using the arithmetic operator let or $((..)) ( bash 4.1 onwards) to increment a variable value as

    #!/usr/bin/env bash
    set -e
    i=0
    let i++                   # or ((i++)) on bash 4.1 or later
    echo "i is $i" 
    
  2. If the offending command is not part of the last command executed via && or ||. For e.g. the below trap wouldn't fire when its expected to

    #!/usr/bin/env bash
    set -e
    test -d nosuchdir && echo no dir
    echo survived
    
  3. When used incorrectly in an if statement as, the exit code of the if statement is the exit code of the last executed command. In the example below the last executed command was echo which wouldn't fire the trap, even though the test -d failed

    #!/usr/bin/env bash
    set -e
    f() { if test -d nosuchdir; then echo no dir; fi; }
    f 
    echo survived
    
  4. When used with command-substitution, they are ignored, unless inherit_errexit is set with bash 4.4

    #!/usr/bin/env bash
    set -e
    foo=$(expr 1-1; true)
    echo survived
    
  5. when you use commands that look like assignments but aren't, such as export, declare, typeset or local. Here the function call to f will not exit as local has swept the error code that was set previously.

    set -e
    f() { local var=$(somecommand that fails); }        
    g() { local var; var=$(somecommand that fails); }
    
  6. When used in a pipeline, and the offending command is not part of the last command. For e.g. the below command would still go through. One options is to enable pipefail by returning the exit code of the first failed process:

    set -e
    somecommand that fails | cat -
    echo survived
    
理想的建议是不要使用set -e,而是实现自己的错误检查版本。有关在我的一个答案中实现自定义错误处理的更多信息,请参见Raise error in a Bash script

非常有帮助。谢谢! - Warm_Duscher

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接