如果任何命令返回非零值,则中止 shell 脚本
Posted
技术标签:
【中文标题】如果任何命令返回非零值,则中止 shell 脚本【英文标题】:Aborting a shell script if any command returns a non-zero value 【发布时间】:2010-10-23 17:05:27 【问题描述】:我有一个调用许多命令的 Bash shell 脚本。
如果任何命令返回非零值,我希望 shell 脚本自动退出并返回值 1。
如果不明确检查每个命令的结果,这是否可能?
例如,
dosomething1
if [[ $? -ne 0 ]]; then
exit 1
fi
dosomething2
if [[ $? -ne 0 ]]; then
exit 1
fi
【问题讨论】:
除了set -e
,还要做set -u
(或set -eu
)。 -u
结束了愚蠢的、隐藏错误的行为,您可以访问任何不存在的变量并在没有诊断的情况下生成一个空白值。
【参考方案1】:
将此添加到脚本的开头:
set -e
如果一个简单的命令以非零退出值退出,这将导致 shell 立即退出。简单命令是任何不属于 if、while 或 until 测试的命令,或者不属于 && 或 || 的任何命令。列表。
有关详细信息,请参阅“set”内部命令上的bash(1) man page。
我个人几乎所有的 shell 脚本都是用“set -e”开始的。当中间出现问题并打破脚本其余部分的假设时,让脚本顽固地继续下去真的很烦人。
【讨论】:
可以,但我喜欢使用“#!/usr/bin/env bash”,因为我经常从 /bin 以外的地方运行 bash。并且“#!/usr/bin/env bash -e”不起作用。另外,当我想打开跟踪进行调试时,有一个地方可以修改为“set -xe”。 此外,如果脚本以bash script.sh
运行,则 shebang 行上的标志将被忽略。
请注意:如果你在 bash 脚本中声明函数,如果你想扩展这个功能,函数需要在函数体内重新声明 set -e。
另外,如果您使用源脚本,shebang 行将无关紧要。
@JinKim 在 bash 3.2.48 中似乎并非如此。在脚本中尝试以下操作:set -e; tf() false; ; tf; echo 'still here'
。即使在tf()
的主体中没有set -e
,执行也会中止。也许您的意思是说set -e
不是由subshells 继承的,这是真的。【参考方案2】:
添加到已接受的答案:
请记住,set -e
有时是不够的,特别是如果您有管道。
例如,假设你有这个脚本
#!/bin/bash
set -e
./configure > configure.log
make
...按预期工作:configure
中的错误中止执行。
明天你做一个看似微不足道的改变:
#!/bin/bash
set -e
./configure | tee configure.log
make
...现在它不起作用了。 here 对此进行了解释,并提供了一种解决方法(仅限 Bash):
#!/bin/bash 设置-e set -o pipefail ./配置 |三通配置日志 制作【讨论】:
感谢您解释让pipefail
与set -o
一起使用的重要性!【参考方案3】:
您的示例中的 if 语句是不必要的。就这样做吧:
dosomething1 || exit 1
如果您接受 Ville Laurikari 的建议并使用 set -e
,那么对于某些命令,您可能需要使用这个:
dosomething || true
即使命令失败,|| true
也会使命令管道具有true
返回值,因此-e
选项不会终止脚本。
【讨论】:
我喜欢这个。特别是因为最重要的答案是以 bash 为中心(我完全不清楚它是否/在多大程度上适用于 zsh 脚本)。我可以查一下,但你的更清楚,因为逻辑。set -e
不是以 bash 为中心的 - 即使在原始的 Bourne Shell 上也支持它。
作为参考,这些运算符被称为控制运算符。更多信息:opensource.com/article/18/11/control-operators-bash-shell【参考方案4】:
如果您需要在退出时进行清理,您还可以将“陷阱”与伪信号 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 选项导致的退出等)退出时,您的陷阱将被执行。
【讨论】:
【参考方案5】:$?
变量很少需要。伪成语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
【讨论】:
为什么“应该总是写成”?我的意思是,为什么“应该”会这样?当一个命令很长时(想想用十几个选项调用 GCC),那么在检查返回状态之前运行命令会更具可读性。 如果一个命令太长,你可以通过命名来分解它(定义一个shell函数)。 见Why is testing “$?” to see if a command succeeded or not, an anti-pattern?【参考方案6】:在顶部使用-e
或set -e
运行它。
也请看set -u
。
【讨论】:
为了潜在地节省其他人阅读help set
的需要:-u
将对未设置变量的引用视为错误。
所以要么是set -u
,要么是set -e
,不是两者都有? @lumpynose
@eric 我几年前退休了。尽管我热爱我的工作,但我年迈的大脑已经忘记了一切。我猜你可以同时使用两者;我的措辞不好;我应该说“和/或”。【参考方案7】:
#!/bin/bash -e
应该足够了。
【讨论】:
【参考方案8】:类似的表达式
dosomething1 && dosomething2 && dosomething3
当其中一个命令返回非零值时将停止处理。例如,以下命令永远不会打印“完成”:
cat nosuchfile && echo "done"
echo $?
1
【讨论】:
【参考方案9】:出现错误时,以下脚本将打印 RED 错误消息并退出。 将其放在 bash 脚本的顶部:
# BASH error handling:
# exit on command failure
set -e
# keep track of the last executed command
trap 'LAST_COMMAND=$CURRENT_COMMAND; CURRENT_COMMAND=$BASH_COMMAND' DEBUG
# on error: print the failed command
trap 'ERROR_CODE=$?; FAILED_COMMAND=$LAST_COMMAND; tput setaf 1; echo "ERROR: command \"$FAILED_COMMAND\" failed with exit code $ERROR_CODE"; put sgr0;' ERR INT TERM
【讨论】:
【参考方案10】:我只是提供另一个作为参考,因为对 Mark Edgars 的输入还有一个额外的问题,这里有一个额外的例子,涉及到整个主题:
[[ `cmd` ]] && echo success_else_silence
这与有人展示的cmd || exit errcode
相同。
例如,如果已安装,我想确保已卸载分区:
[[ `mount | grep /dev/sda1` ]] && umount /dev/sda1
【讨论】:
不,[[
cmd` ]]` 不是一回事。如果命令的输出为空则为假,否则为真,无论命令的退出状态如何。以上是关于如果任何命令返回非零值,则中止 shell 脚本的主要内容,如果未能解决你的问题,请参考以下文章
英特尔 SIMD - 如何检查 __m256* 是不是包含任何非零值