set -e和短测试

24

当我刚开始学习Shell脚本时,我使用了很多短测试代替if语句,比如false && true

后来我学会了使用set -e,但发现我的脚本因某些原因而崩溃,如果我将短测试替换为完整的if语句,则可以运行。现在,时间过去了,我仍然只使用完整的if语句。

最有趣的是,如果我打开一个交互式shell并执行以下操作:

set -e
false && true
echo $?

它返回1但shell未中止!

我发现我浪费了太多的代码行。有人可以向我解释如何安全地使用set -e进行简短测试,例如:不让脚本死掉吗?

4个回答

43

Single UNIX规范中描述了set -e的作用:

当该选项被打开时,如果一个简单命令因为Shell错误的原因列表中所列出的任何原因失败或返回一个大于0的退出状态值,并且不是while、until、if关键字后面的复合列表的一部分,并且不是AND或OR列表的一部分,并且不是在!保留字之前的管道,则Shell将立即退出。

正如您所见,AND列表中的命令执行失败不会使Shell退出。

使用set -e

set -e开头编写Shell脚本被认为是最佳实践,因为如果发生某些错误,通常更安全的做法是终止脚本。 如果一个命令可能会无害地失败,我通常会添加|| true

下面是一个简单的例子:

#!/bin/sh
set -e

# [...]
# remove old backup files; do not fail if none exist
rm *~ *.bak || true

4
为了举例说明,让我们忽略 rm -f 的存在。 - Danilo Piazzalunga
谢谢!我只是想知道为什么我的早期脚本会默默失败,除非我使用完整的if语句...一定有原因! - admirabilis
我很久以前就停止使用短测试了。我可以做的是开始重复使用它,如果我再次发现问题,就在这里发布。 - admirabilis
5
我说错了,子shell 并没有导致这个问题,而是由于子shell 返回了 1 导致父进程退出了。在这种情况下,使用 if||: 更加安全。 - admirabilis
我相信这是预期的:false && true 是一个AND列表,并且不会使shell死亡,而( false && true )被(外部) shell视为单个命令。括号确实有所不同。 - Danilo Piazzalunga
显示剩余3条评论

5

您需要使用||和一个运算结果为0的命令或命令列表来结束每个可能失败的命令。使用||将在操作符前的表达式不为0时触发您的命令。您的命令需要评估为0以避免终止shell。

示例:

set -e
false || true # silently ignore error

false || echo "Command failed, but exit status of echo is 0. Continuing..."

false || {                                                                      
  echo "Command failed. Continuing..."                                          
  # do something else                                                           
  false && true # all commands in the list need to be true                      
}
false && true不会导致退出,因为结束表达式会被计算并且计算结果为0。从bash的set -e说明文档中可以看到:
如果管道(可能由单个简单命令组成),括在圆括号内的子shell命令或由大括号包围的命令列表中执行的任何命令(请参见上面的SHELL语法)以非零状态退出,则立即退出。如果失败的命令是while或until关键字后面紧接着的命令列表的一部分,if或elif保留字后面的测试的一部分,任何在&&或||列表中执行的命令(除了最后一个&&或||后面的命令),管道中的任何命令但不是最后一个命令,或者如果使用!反转命令的返回值,则Shell不会退出。如果设置了ERR陷阱,则在Shell退出之前执行。此选项适用于Shell环境和每个子Shell环境(请参见上面的COMMAND EXECUTION ENVIRONMENT),并可能导致子Shell在执行所有命令之前退出。
只有在||/&&链中执行的最后一个命令才能触发退出。
以下表达式将因为上述原因而失败。
true && false

但是以下内容不会:
if true && false                                                                
then                                                                            
  true                                                                          
fi                   

由于true && falseif语句后面测试的一部分。


2

关于我的脚本意外死亡的问题,是由于在子shell或函数中运行这些短测试,并且该函数返回一个非零值:

原始示例:

$ set -e
$ false && true
$ echo $?
1

使用子shell或函数:

$ set -e
$ (false && true)
<script died>

$ set -e
$ variable=$(false && true)
<script died>

$ set -e
$ foo()(false && true)
$ foo
<script died>

$ set -e
$ foo(){ false && true; }
$ foo
<script died>

可能的解决方案:
$ set -e
$ (false && true ||:)
$ (false && true) ||:
$ (if false; then true; fi)

1

set -e的行为在除了最简单的情况外都不直观,并且多年来已经发生了多次变化。因此,我不建议在除了真正简单的脚本(普通的命令序列)之外使用它。

请注意,Makefile中的命令通常会受到set -e的影响而被执行,因此仍然需要了解一些关于它如何工作的知识。

&&||运算符允许以相对较少的混乱实现类似的效果,但使其变得明确。特别是,&&可以像`;'一样放置在行末。


2
我在成千上万行代码的脚本中实际上使用了 set -e。在我看来,最佳实践是在开发阶段与 set -x 一起使用它,然后如果你担心发布后可能会失败,就将其删除。虽然我即使在发布后也不会删除它,但只会在我知道可能会出现问题的几行代码上添加 || true - admirabilis

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