命令失败怎么退出?


222

我是shell脚本方面的菜鸟。如果命令失败,我想打印一条消息并退出脚本。我试过了:

my_command && (echo 'my_command failed; exit)

但它不起作用。它会继续执行脚本中此行之后的指令。我正在使用Ubuntu和bash。


5
您是否打算将未封闭的引号视为会导致失败/退出的语法错误?如果不是,则应在示例中关闭引号。
滚刀

Answers:


406

尝试:

my_command || { echo 'my_command failed' ; exit 1; }

四个变化:

  • 更改&&||
  • 使用{ }代替( )
  • ;exit和之后介绍
  • {之前和之后的空格}

既然你要打印的消息,只有当命令失败(非零值退出)退出你需要一个||不是&&

cmd1 && cmd2

cmd2cmd1成功时运行(退出值0)。在哪里

cmd1 || cmd2

cmd2cmd1失败时运行(退出值非零)。

使用( )使其中的命令在子shell中运行然后exit从那里调用a 会导致您退出子外壳而不是原始外壳,因此在原始外壳中继续执行。

为了克服这种使用 { }

bash需要最后两个更改。


4
它似乎确实是“反转”的。如果函数“成功”,则返回0,如果函数“失败”,则返回非零,因此,当前半部分返回非零时,可能需要&&进行评估。这并不意味着上面的答案是错误的-不,这是正确的。&&和|| 在脚本中,工作基于成功而不是返回值。
CashCow 2012年

7
似乎相反,但读出来并很有意义:“(成功执行此命令)”或“打印此错误并退出”
simpleuser 2014年

2
其背后的逻辑是该语言使用短路评估(SCE)。对于SCE,如果表达式的形式为“ p OR q”,并且p被评估为真,那么就没有理由看q。如果表达式的形式为“ p AND q”,并且p被评估为假,则无需查看q。这样做的原因有两个:1)由于明显的原因,它的速度更快; 2)它避免了某些错误(例如:如果没有SCE,则“如果x!= 0 AND 10 / x> 5”将崩溃) )。像这样在命令行中使用它的能力是一个令人愉快的副作用。
user2635263

2
我建议回显STDERR:{ (>&2 echo 'my_command failed') ; exit 1; }
rynop

127

其他答案很好地解决了直接问题,但您可能也对使用感兴趣set -e。这样,任何失败的命令(例如if测试之类的特定上下文之外)都将导致脚本中止。对于某些脚本,它非常有用。


3
不幸的是,这也是非常危险的- set -e关于何时工作以及何时不工作有很长而神秘的规则。(如果有人运行if yourfunction; then ...,则set -e永远不会在内部触发yourfunction它调用的任何东西,因为该代码被视为“已检查”)。请参阅BashFAQ#105的练习部分,讨论此陷阱和其他陷阱(其中一些陷阱仅适用于特定的Shell版本,因此您需要确保针对可能用于运行脚本的每个发行版进行测试)。
查尔斯·达菲

67

还要注意,每个命令的退出状态都存储在shell变量$?中,您可以在运行命令后立即检查它。非零状态表示失败:

my_command
if [ $? -eq 0 ]
then
    echo "it worked"
else
    echo "it failed"
fi

10
如果使用my_command,则可以替换它,这里不需要使用test命令。
Bart Sas

5
+1是因为我认为您不应因为列出此替代方案而受到惩罚-应该在桌上-尽管它有点丑陋,但根据我的经验,在不注意测试性质的情况下,在两者之间运行另一个命令太容易了(也许我我只是愚蠢的)。
托尼·德罗伊

1
@BartSas如果命令很长,最好将其放在单独的行中。它使脚本更具可读性。
迈克尔(Michael)

@Michael,如果它跨行创建了依赖关系,那么在理解行B之前,您需要先了解行A发生了什么(或更糟糕的是,需要知道行B会发生什么,然后才能安全地添加一些东西)在行A)结束后重新输入。我见过很多次有人添加了echo "Just finished step A",却不知道那会echo覆盖$?以后要检查的内容。
查尔斯·达菲

67

如果您希望脚本中所有命令都具有这种行为,只需添加

  set -e 
  set -o pipefail

在脚本的开头。这对选项告诉bash解释器在命令返回非零退出代码时退出。

但是,这不允许您打印退出消息。


8
您可以使用trapbash内置命令在退出时运行命令。
加文·史密斯

这是XY问题的答案。
jwg 2015年

5
解释什么命令独立而不是成对执行可能会很有趣。特别是由于set -o pipefail可能不是您期望的行为。仍然感谢您指出!
安托万·皮萨德

3
set -e根本不是可靠,一致或可预测的!为了使自己相信这一点,请查看BashFAQ#105的练习部分,或在in-ulm.de/~mascheck/various/set-e
Charles Duffy,

14

我破解了以下成语:

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 ];...行。(当然,您可以根据需要编辑该错误消息。)


可以将其定义为一个函数,从而重新使用它。
艾瑞克·王

1
'如果[$?-ne 0]; 那么... fi'是写'||'的复杂方法
凯文·克莱恩

if ! javac *.java; then ...-为什么要打扰$?呢?
查尔斯·达菲

查尔斯(Charles)-因为我充其量是一名普通的bash脚本撰写者:)
格兰特·伯奇迈尔

10

提供my_command是经过规范设计的,成功时返回0,则&&与您想要的相反。你要||

还要注意,(在bash中这似乎不适合我,但是我无法从自己所在的位置尝试。告诉我。

my_command || {
    echo 'my_command failed' ;
    exit 1; 
}

IIRC,您需要在{}中的每一行后加上分号
Alex Howansky 2010年

9
@Alex:如果他们不在同一行,则不会。
暂停,直到另行通知。

5

如果要保留退出错误状态,并具有每行一个命令的可读文件,也可以使用:

my_command1 || exit $?
my_command2 || exit $?

但是,这不会打印任何其他错误消息。但是在某些情况下,无论如何该错误都会由失败的命令打印出来。


1
没有理由是这样exit $?。仅exit$?其用作默认值。
查尔斯·达菲,

@CharlesDuffy不知道。太棒了,因此更加简单!
alexpirine

3

trap内置的Shell允许捕获信号和其他有用的条件,包括命令执行失败(即非零返回状态)。因此,如果您不想显式地测试每个命令的返回状态,则可以说trap "your shell code" ERR,只要命令返回非零状态,便会执行shell代码。例如:

trap "echo script failed; exit 1" ERR

请注意,与其他捕获失败命令的情况一样,管道需要特殊处理。以上都不能抓住false | true

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.