How to exit if a command failed?
我对shell脚本编写很在行。如果命令失败,我想打印一条消息并退出脚本。我试过了:
1
| my_command && (echo 'my_command failed; exit) |
但它不起作用。它一直在执行脚本中这一行后面的指令。我用的是Ubuntu和Bash。
- 您是否希望未闭合的引号是会导致失败/退出的语法错误?如果没有,您应该关闭示例中的引号。
尝试:
1
| my_command || { echo 'my_command failed' ; exit 1; } |
四个变化:
- 将&&改为||。
- 用{ }代替( )。
- 在exit之后引入;和
- {后和}前的空格
由于您只想打印消息并在命令失败时退出(以非零值退出),因此需要一个||,而不是&&。
当cmd1成功(退出值0时,将运行cmd2。在哪里
当cmd1失败(退出值非零)时,将运行cmd2。
使用( )使其内部的命令在子shell中运行,并从中调用exit会导致您退出子shell而不是原来的shell,因此在原来的shell中继续执行。
为了克服这种情况,使用{ }。
最后两个更改是bash所必需的。
- 它看起来确实是"颠倒的"。如果函数"成功",则返回0;如果函数"失败",则返回非零,因此,当上半部分返回非零时,可能需要对其进行评估。这并不意味着上面的答案是错误的-不,它是正确的。&;&;and在脚本中基于成功而不是返回值工作。
- 它看起来是相反的,但把它读出来是有意义的:"执行这个命令(成功)"或"打印这个错误并退出"
- 它背后的逻辑是语言使用短路评估(SCE)。在sce中,f的表达式形式为"p或q",p的计算结果为真,则没有理由甚至看q。如果表达式形式为"p和q",而p的计算结果为假,则没有理由看q。原因有两个:1)速度更快,原因显而易见;2)避免了某些类型的错误(因为例如:"如果X!=0和10/x>5"将在没有SCE的情况下崩溃)。像这样在命令行中使用它的能力是一个很好的副作用。
- 我建议向stderr回复:{ (>&2 echo 'my_command failed') ; exit 1; }
其他答案很好地涵盖了直接问题,但您也可能对使用set -e感兴趣。这样,任何失败的命令(在特定上下文(如if测试)之外)都会导致脚本中止。对于某些脚本,它非常有用。
- 不幸的是,这也是非常危险的——set -e有一套关于何时工作,何时不工作的长而神秘的规则(如果有人运行if yourfunction; then ...,那么set -e将永远不会在yourfunction或它调用的任何东西内点火,因为该代码被认为是"选中的")。请参阅bashfaq 105的练习部分,讨论这一点和其他缺陷(其中一些缺陷仅适用于特定的shell版本,因此您需要确保针对可能用于运行脚本的每个版本进行测试)。
如果您希望脚本中的所有命令都具有这种行为,只需添加
1 2
| set -e
set -o pipefail |
在脚本的开头。这对选项告诉bash解释器,每当命令返回非零退出代码时,都要退出。
但是,这不允许您打印退出消息。
- 您可以使用trapbash内置命令在退出时运行命令。
- 这是x-y问题的答案。
- 解释命令是独立执行的,而不是成对执行的,这可能很有趣。尤其是因为set -o pipefail可能不是理想的行为。还是谢谢你指出!
- set -e根本不可靠、一致或可预测!为了让自己相信这一点,请查看bashfaq 105的练习部分,或表格,比较ulm.de/~mascheck/variable/set-e中不同外壳的行为。
- @Charlesduffy谢谢你的反馈
注意,每个命令的退出状态都存储在shell变量$?,可以在运行命令后立即检查。非零状态表示故障:
1 2 3 4 5 6 7
| my_command
if [ $? -eq 0 ]
then
echo"it worked"
else
echo"it failed"
fi |
- 如果我的命令可以替换它,这里不需要使用test命令。
- +1因为我认为你不应该因为列出这个选项而受到惩罚——它应该放在桌子上——尽管这有点难看,而且在我的经验中,在不注意测试性质的情况下运行另一个命令太容易了(也许我只是个傻瓜)。
- @bartsas如果命令很长,最好把它放在自己的行上。它使脚本更具可读性。
- @Michael,如果它创建了跨行的依赖关系,那么在理解B行之前,您需要知道A行发生了什么(或者更糟的是,在知道在A行结束后添加新内容是安全的之前,需要知道B行将发生什么)。我见过很多次有人加了一个echo"Just finished step A",不知道echo覆盖了$?,以后会检查。
我已经把下面的成语拼凑了一下:
1 2 3 4 5 6 7 8 9
| echo"Generating from IDL..."
idlj -fclient -td java/src echo.idl
if [ $? -ne 0 ]; then { echo"Failed, aborting." ; exit 1; } fi
echo"Compiling classes..."
javac *java
if [ $? -ne 0 ]; then { echo"Failed, aborting." ; exit 1; } fi
echo"Done." |
在每个命令前面加上一个信息性的回声,然后在每个命令后面加上相同的回声。if [ $? -ne 0 ];...线。(当然,如果需要,可以编辑该错误消息。)
- 这可以定义为一个函数,因此可以重用它。
- 如果是?-NE 0];然后…fi'是一种复杂的书写方式''
- if ! javac *.java; then ...——为什么要麻烦$??
如果my_command是规范设计的,即成功时返回0,那么&&与您想要的正好相反。你要的是||。
同样要注意的是,在巴什,以东十一〔三〕在我看来是不对的,但我不能从我所在的地方去尝试。告诉我。
1 2 3 4
| my_command || {
echo 'my_command failed' ;
exit 1;
} |
- iirc,在内的每一行后面都需要分号。
- @亚历克斯:如果他们在单独的线路上,就不会。
trapshell内置允许捕获信号和其他有用条件,包括命令执行失败(即非零返回状态)。因此,如果您不想显式测试每个命令的返回状态,您可以说trap"your shell code" ERR,并且在命令返回非零状态时,shell代码将被执行。例如:
trap"echo script failed; exit 1" ERR
注意,与其他捕获失败命令的情况一样,管道需要特殊处理;上述情况不会捕获false | true。
如果要保留退出错误状态,并且每行有一个命令的可读文件,也可以使用:
1 2
| my_command1 || exit $?
my_command2 || exit $? |
但是,这不会打印任何其他错误消息。但在某些情况下,错误将由失败的命令打印出来。
- 没有理由是exit $?;只是exit使用$?作为其默认值。
- @查尔斯达菲不知道。太棒了,所以更简单!
以下函数仅在命令失败时回显错误:
1 2 3 4 5 6 7 8 9 10
| silently () {
label="${1}"
command="${2}"
error=$(eval"${command}" 2>&1 >"/dev/null")
if [ ${?} -ne 0 ]; then
echo"${label}: ${error}">&2
exit 1
fi
} |
- execute 'whatever'"./my script with spaces in its name"将试图运行./my,而不是my script with spaces in its name;它忽略了所有进一步的争论。此外,使用$?会增加更多的错误空间,因为在数据收集和评估之间添加的任何日志都会改变其价值;使用if ! error=$(...whatever...); then更安全,并且让$?的评估是隐式的。
- 第一期已修复。给我举第二个例子。
- 使用eval并不像"固定"那样远程操作;eval"$command"仍将运行./my,而不是./my command with spaces in its name,在我上面给出的示例中(一个示例,其中空格是文件名的一部分),它还引入了一系列安全问题。此外,您正在将所有可能的退出状态压缩到0或1,从而丢弃程序失败的特定非零退出状态的任何细节。
- 好的做法更像是silently() { local label error retval=0; label=$1; shift; error=$("$@" 2>&1 >/dev/null) || retval=$?; if (( retval )); then printf '%s: %s
'"$label""$error">&2; exit"$retval"; fi; }--注意,我们不只是把$2当作一个命令,而是把后面跟着1美元的所有参数传递出去,这样您就可以运行silently"some label""/Users/Some Username/whatever""first argument with spaces""second argument with spaces",并正确地传递这些参数。
- 由于retval与$?被分配到同一个复合命令中,所以修改代码以添加日志记录的人不必担心他们的echo"Just ran $command">&2行或设置$?到0的任何内容,即使它是一个非零值。
- …关于:使用printf,而不是echo,请参阅pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html上的echo的posix标准的应用程序使用部分。
- 我去看看。