为什么 set -e; true && false && true 不会退出?
为什么 set -e; true && false && true 不会退出?
根据这个被接受的答案,在bash脚本中使用set -e
内置命令应该足以在第一个错误发生时退出。然而,下面的脚本:\n
#!/usr/bin/env bash set -e echo "a" echo "b" echo "about to fail" && /bin/false && echo "foo" echo "c" echo "d"
\n打印结果为:\n
$ ./foo.sh a b about to fail c d
\n删除echo \"foo\"
会停止脚本;但为什么?
问题:为什么set -e; true && false && true
不会退出?
在Bash脚本中,我遇到了set -e
的问题,不明白在&&
或||
列表中的最后一个命令的评估过程是如何进行的。我知道关于set -e
有以下引用:
“如果失败的命令是...在任何&&
或||
列表中的命令中,除了最后一个命令之外,shell不会退出...”
为了测试这个问题,我编写了一个小的Bash脚本:
#!/bin/bash bash -c "set -e ; true ; echo -n A" bash -c "set -e ; false ; echo -n B" bash -c "set -e ; true && true ; echo -n C" bash -c "set -e ; true && false ; echo -n D" bash -c "set -e ; false && true ; echo -n E" bash -c "set -e ; false && false ; echo -n F" bash -c "set -e ; true || true ; echo -n G" bash -c "set -e ; true || false ; echo -n H" bash -c "set -e ; false || true ; echo -n I" bash -c "set -e ; false || false ; echo -n J" echo ""
它会输出:
ACEFGHI
关于A:
true
没有非零状态,因此,shell不会退出。
关于B:
false
有非零状态,并且不是&&
或||
列表的一部分,因此,shell会退出。
关于C:
这是一个&&
或||
列表。我们需要查看最后一个&&
或||
之后的命令。该命令是true
,它没有非零状态。因此,无论命令是否被评估,shell都不会退出。
关于D:
这是一个&&
或||
列表。我们需要查看最后一个&&
或||
之后的命令。这次,命令是false
,它有非零状态。因此,我们需要检查是否评估了该false
- 这确实是情况,因为true
之后跟着的是&&
。因此,shell会退出。
关于E:
与C的推理相同:true
是最后一个&&
或||
之后的命令。因此,shell不会退出。
关于F:
类似于D:这是一个&&
或||
列表。我们需要查看最后一个&&
或||
之后的命令。再次,命令是false
,它有非零状态。但这次并不重要,因为第一个命令也是false
。由于它是一个&&
列表,第二个false
不会被评估。因此,shell不会退出。
关于G:
与C或E的推理相同:true
是最后一个&&
或||
之后的命令。因此,shell不会退出。
关于H:
这是一个&&
或||
列表。我们需要查看最后一个&&
或||
之后的命令。该命令是false
,它有非零状态,但它不会被评估,因为||
之前是true
。因此,shell不会退出。
关于I:
与C、E或G的推理相同:true
是最后一个&&
或||
之后的命令。因此,shell不会退出。
关于J:
这是一个&&
或||
列表。我们需要查看最后一个&&
或||
之后的命令。该命令是false
,它有非零状态。由于||
之前是false
,第二个false
将被评估。因此,shell会退出。
你应该能够将这些测试案例应用到你的情况:true && false && true
。由于最后一个&&
或||
之后的命令是true
,它没有非零状态,无论在&&
或||
之前是什么,shell都不会退出。
为什么set -e; true && false && true不会退出?
在这个问题中,set -e表示在脚本中如果有任何一个简单命令失败,脚本将立即退出。然而,根据man文档的描述,set -e在命令列表中的最后一个命令失败时不会退出。这就是为什么set -e; true && false && true不会退出的原因。
解决这个问题的方法是将命令列表中的最后一个命令放在单独的一行上,或者使用分号将其与前面的命令分隔开。这样,在最后一个命令失败时,脚本将退出。
以上是关于为什么set -e; true && false && true不会退出的原因以及解决方法的说明。根据man文档的描述,set -e不会在命令列表中的最后一个命令失败时退出。为了解决这个问题,可以将最后一个命令放在单独的一行上或者使用分号将其与前面的命令分隔开。希望这篇文章对你有帮助。
为了简化EtanReisner的详细回答,set -e
只在未捕获的错误上退出。在你的例子中:
echo "about to fail" && /bin/false && echo "foo"
失败的代码/bin/false
后面跟着&&
来测试它的退出代码。由于&&
测试退出代码,可以假设程序员知道自己在做什么,并预计到这个命令可能会失败。因此,脚本不会退出。
相比之下,考虑以下代码:
echo "about to fail" && /bin/false
程序不会测试或分支/bin/false
的退出代码。所以,当/bin/false
失败时,set -e
将导致脚本退出。
当/bin/false
失败时退出的替代方法
考虑以下代码:
set -e echo "about to fail" && /bin/false ; echo "foo"
如果/bin/false
失败,这个版本将退出。与使用&&
的情况一样,因此只有/bin/false
成功时,最后的语句echo "foo"
才会被执行。
因此,我是否正确理解,在这种情况下,程序员需要手动在这些链的末尾添加|| exit 1
?
我接受这个答案是因为给出的理由解释了为什么bash脚本将在失败的命令是&&
链中的最后一个命令时退出
我添加了一个部分,展示了一个可能比|| exit 1
更好的替代方法。
除了这个“理由”与事实完全无关,因为这与程序员是否知道自己在做什么无关。原因是由于已定义的shell行为(未详细说明但已定义的行为,最近似乎终于在man手册中得到了具体说明)。
您的答案提供了关于已定义的shell行为的文档,这是非常出色和明确的。我的回答是试图解释为什么开发人员选择了这种定义的行为。意图是为了补充,而不是反驳您的答案。
抱歉,我并不是在针对您,而是针对MarcusJuniusBrutus选择此答案的推理。我对您的回答没有任何实质性的异议。
您可能是有道理的,但我决定接受那个答案(并不是没有思考)即使它扩展了您的答案,因为它为shell的行为提供了我认为是合理的解释(否则看起来是武断的)。
我可以添加使echo "foo"
只在前一个命令成功时执行的功能:echo "about to fail" && /bin/false; [ $? -eq 0 ] && echo "foo"
那么使用&&
链接命令的目的是什么,如果功能被明确忽略了呢?
如果bash -e导致短路布尔运算不起作用,那么编写旨在使用布尔短路求值的语句的目的是什么。换句话说,它将处理布尔表达式中的每个命令,你可以将它们输入为单独的命令(a; b; c),而不会让不幸的未来读者困惑。
抱歉,我刚刚测试了一下,发现我误解了问题。这不是关于在一个bash命令中执行命令,而是关于脚本中其他命令的进一步执行。我编写了一个测试脚本,我看到了功能上的区别,现在我明白它与布尔短路求值无关。对于分心我感到抱歉。
非常好。(作为清理,我们可能需要删除这些评论。)