如果有任何命令返回非零值,则中止shell脚本吗?


437

我有一个Bash shell脚本,可以调用许多命令。如果任何命令返回的非零值,我都希望shell脚本自动返回1的返回值。

如果不显式检查每个命令的结果,是否有可能?

例如

dosomething1
if [[ $? -ne 0 ]]; then
    exit 1
fi

dosomething2
if [[ $? -ne 0 ]]; then
    exit 1
fi

8
除了set -e,还可以做set -u(或set -eu)。-u结束了这种愚蠢的,隐藏错误的行为,您可以访问任何不存在的变量,并在不进行诊断的情况下生成空白值。
卡兹(Kaz)2014年

Answers:


742

将此添加到脚本的开头:

set -e

如果一个简单的命令以非零退出值退出,这将导致shell立即退出。简单命令是不属于if,while或直到测试的一部分的任何命令,也不是&&或||的一部分的任何命令。清单。

有关更多详细信息,请参见“ set”内部命令上的bash(1)手册页

我个人几乎以“ set -e”开始几乎所有的shell脚本。当某个脚本在中间出现故障并顽固地破坏了其余脚本的假设时,顽固地继续执行脚本确实很烦人。


36
那行得通,但是我喜欢使用“#!/ usr / bin / env bash”,因为我经常从/ bin以外的地方运行bash。而且“#!/ usr / bin / env bash -e”不起作用。此外,当我想打开跟踪进行调试时,有一个地方可以修改为读取“ set -xe”,这是很好的。
维尔·劳里卡里(Fille Laurikari),2009年

48
另外,如果脚本以运行,则shebang行上的标志也将被忽略bash script.sh
汤姆·安德森

27
请注意:如果在bash脚本中声明函数,则要扩展此功能,必须在函数体内重新声明-e。
Jin Kim

8
另外,如果您获取脚本,则shebang行将是无关紧要的。

4
@JinKim在bash 3.2.48中似乎并非如此。在脚本中尝试以下操作:set -e; tf() { false; }; tf; echo 'still here'。即使没有set -e身体内部tf(),执行被中止。也许您是想说那set -e不是subshel​​l继承的,这是正确的。
mklement0

202

要添加到接受的答案中:

请记住,set -e有时这还不够,特别是如果您有管道。

例如,假设您有此脚本

#!/bin/bash
set -e 
./configure  > configure.log
make

...可以按预期工作:错误configure中止执行。

明天您将进行看似微不足道的更改:

#!/bin/bash
set -e 
./configure  | tee configure.log
make

...现在不起作用。此处对此进行了说明,并提供了一种解决方法(仅Bash):

#!/ bin / bash
设置-e 
设置-o pipefail

./configure | tee配置文件
使

1
感谢您解释必须pipefail与之相处的重要性set -o
马尔科姆'18

83

您的示例中的if语句是不必要的。像这样做:

dosomething1 || exit 1

如果您接受了Ville Laurikari的建议并使用set -e了某些命令,则可能需要使用以下命令:

dosomething || true

|| true将命令管道有一个true即使命令失败,所以返回值-e选项将不会终止该脚本。


1
我喜欢这个。尤其是因为最佳答案是以bash为中心的(我根本不清楚它是否/在何种程度上适用于zsh脚本)。我可以查找一下,但是您更清楚了,因为逻辑。
g33kz0r 2015年

set -e不是以bash为中心-甚至在原始的Bourne Shell上也受支持。
Marcos Vives Del Sol

27

如果您需要在退出时进行清理,则还可以将“ trap”与伪信号ERR一起使用。这与捕获INT或任何其他信号的方式相同。如果任何命令以非零值退出,则bash会抛出ERR:

# Create the trap with   
#    trap COMMAND SIGNAME [SIGNAME2 SIGNAME3...]
trap "rm -f /tmp/$MYTMPFILE; exit 1" ERR INT TERM
command1
command2
command3
# Partially turn off the trap.
trap - ERR
# Now a control-C will still cause cleanup, but
# a nonzero exit code won't:
ps aux | grep blahblahblah

或者,特别是如果您使用的是“ set -e”,则可以捕获EXIT;当脚本出于任何原因退出时(包括正常结束,中断,由-e选项引起的退出等),都会执行陷阱。


12

$?很少需要该变量。伪习惯用语command; if [ $? -eq 0 ]; then X; fi应始终写为if command; then X; fi

$?需要进行检查的情况是需要针对多个值进行检查:

command
case $? in
  (0) X;;
  (1) Y;;
  (2) Z;;
esac

或何时$?需要重用或以其他方式处理:

if command; then
  echo "command successful" >&2
else
  ret=$?
  echo "command failed with exit code $ret" >&2
  exit $ret
fi

3
为什么“应始终写为”?我的意思是,为什么 “这样”?当命令很长时(想用一打选项调用GCC),那么在检查返回状态之前运行该命令更具可读性。
ysap 2012年

如果命令太长,可以通过命名它来分解它(定义一个shell函数)。
Mark Edgar

12

-eset -e在顶部运行它。

也看看set -u


34
为了潜在地节省他人,需要仔细阅读help set:将对未-u设置变量的引用视为错误。
mklement0

1
所以是set -uset -e不是,还是两者都不是?@lumpynose
ericn '16

1
@eric我几年前退休了。即使我喜欢我的工作,我年迈的大脑也忘记了一切。我猜想你可以同时使用这两种方法。我的措辞不好;我应该说“和/或”。
lumpynose '16

3

像这样的表达

dosomething1 && dosomething2 && dosomething3

当命令之一返回非零值时,它将停止处理。例如,以下命令将永远不会打印“完成”:

cat nosuchfile && echo "done"
echo $?
1


-2

只是抛出另一个供参考,因为Mark Edgars输入的内容还有一个附加问题,这是一个附加示例,涉及整个主题:

[[ `cmd` ]] && echo success_else_silence

cmd || exit errcode和别人展示的一样。

例如。如果要挂载,我要确保已卸载分区:

[[ `mount | grep /dev/sda1` ]] && umount /dev/sda1 

5
不,[[ cmd`]]`不是一回事。如果命令的输出为空,则为false,否则为true,无论命令的退出状态如何。
吉尔(Gilles)“所以
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.