为啥设置 -e; true && false && true 不退出?

Posted

技术标签:

【中文标题】为啥设置 -e; true && false && true 不退出?【英文标题】:Why does set -e; true && false && true not exit?为什么设置 -e; true && false && true 不退出? 【发布时间】:2014-11-05 20:13:43 【问题描述】:

根据this accepted answer,使用set -e 内置应该足以让bash 脚本在第一个错误时退出。然而,下面的脚本:

#!/usr/bin/env bash

set -e

echo "a"
echo "b"
echo "about to fail" && /bin/false && echo "foo"
echo "c"
echo "d"

打印:

$ ./foo.sh 
a
b
about to fail
c
d

删除echo "foo" 停止脚本;但为什么呢?

【问题讨论】:

相关:unix.stackexchange.com/questions/312631/… 【参考方案1】:

因为这个答案不够具体。

应该说(粗体字是我的补充):

#任何后续的simple命令失败都会导致shell脚本立即退出

因为手册页是这样写的:

-e      Exit  immediately if a simple command (see SHELL GRAMMAR
        above) exits with a non-zero status.  The shell does not
        exit  if  the  command that fails is part of the command
        list immediately following a  while  or  until  keyword,
        part  of the test in an if statement, part of a && or ││
        list, or if the command’s return value is being inverted
        via  !.   A  trap on ERR, if set, is executed before the
        shell exits.

SHELL GRAMMAR 就这样扩展了:

SHELL GRAMMAR
   Simple Commands
       A simple command is a sequence of optional  variable  assignments  fol-
       lowed  by  blank-separated  words and redirections, and terminated by a
       control operator.  The first word specifies the command to be executed,
       and  is  passed  as  argument  zero.  The remaining words are passed as
       arguments to the invoked command.

       The return value of a simple command is its exit status,  or  128+n  if
       the command is terminated by signal n.

【讨论】:

好吧,想要扮演“手册页律师”,如果失败的命令是 && 列表中的最后一个命令,那么显然 shell 确实 退出,所以这部分读取“does not exit if the command [..] is part of a && [..] list”并不完全正确 @MarcusJuniusBrutus -e 的文档明确涵盖了该细节。 “如果失败的命令是...... && 或 ││ 列表的一部分,则 shell 不会退出”,尽管这对于“......列表的一部分”的含义并不明确。这似乎意味着不是这样一个列表的最终单元。 bash 4.3 的手册页最终澄清了这一点:“[...] 在 && 或 || 列表中执行的任何命令的一部分 除了最后的 && 或 | 之后的命令。 | [...]"【参考方案2】:

为了简化 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 失败,此版本将退出。与使用&& 的情况一样,最终语句echo "foo" 因此只有在/bin/false 成功时才会执行。

【讨论】:

因此,我是否正确理解在这种情况下,程序员有责任在此类链的末尾手动附加|| exit 1 我接受这个答案是因为给出的理由解释了为什么如果失败的命令是&& 链中的最后一个命令,bash 脚本退出 @MarcusJuniusBrutus 我添加了一个部分,其中显示了可能比 || exit 1 更好地满足您的需求的替代方案。 除了这里给出的“基本原理”与现实完全无关,因为这与程序员做了什么或不知道他们在做什么的假设无关。原因是由于已定义的 shell 行为(未指定但已定义的行为,显然最近终于在手册页中指定了)。 @EtanReisner 您的回答出色而明确地提供了有关已定义外壳行为的文档。我的回答是试图解释为什么开发人员选择了该定义的行为。目的是补充而不是反驳您的答案。【参考方案3】:

我在 Bash 脚本中遇到了 set -e,但无法理解在 &&|| 列表中最后一个 &&|| 之后的命令评估会发生什么。我知道http://man7.org/linux/man-pages/man1/bash.1.htmlset -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 不会退出。

关于乙:

false 确实具有非零状态,并且不是&&|| 列表的一部分。因此,shell 退出。

关于C:

这是一个&&|| 列表。我们将不得不查看最后一个&&|| 之后的命令。该命令是true,它不具有非零状态。因此,是否对命令进行评估并不重要 - shell 不会以任何方式退出。

关于 D:

这是一个&&|| 列表。我们将不得不查看最后一个&&|| 之后的命令。这一次,命令是false,它确实具有非零状态。所以我们必须检查false 是否正在被评估——确实是这样,因为&& 正在关注true。因此,shell 退出。

关于E:

C 的推理相同:true 是最后一个 &&|| 之后的命令。因此,shell 不会退出。

关于 F:

类似于D:这是一个&&|| 列表。我们将不得不查看最后一个&&|| 之后的命令。同样,命令是false,它确实具有非零状态。不过这次没关系,因为第一个命令也是false。由于它是 && 列表,因此不会评估第二个 false。因此,shell 不会退出。

关于 G:

CE 的推理相同:true 是最后一个 &&|| 之后的命令。因此,shell 不会退出。

关于 H:

这是一个&&|| 列表。我们将不得不查看最后一个&&|| 之后的命令。这个命令是false,它确实有一个非零状态,但它不会被评估,因为||前面是true。因此,shell 不会退出。

关于我:

CEG 的推理相同:true 是最后一个 &&|| 之后的命令.因此,shell 不会退出。

关于 J:

这是&&|| 列表。我们将不得不查看最后一个&&|| 之后的命令。这个命令是false,它确实有一个非零状态。由于|| 前面是false,第二个false 将被评估。因此,shell 确实退出了。


您应该能够将这些测试用例应用于您的案例:true && false && true。由于最后一个&&|| 之后的命令是true,它不具有非零状态,因此&&|| 之前的命令无关紧要,shell 也不会退出方式。

【讨论】:

以上是关于为啥设置 -e; true && false && true 不退出?的主要内容,如果未能解决你的问题,请参考以下文章

为啥“true && () => ”会产生“Uncaught SyntaxError: Malformed arrow function parameter list”? [复制

当按位运算符做同样的事情时,为啥要使用逻辑运算符?

为啥下面的表达式返回 true?

为啥 `bool?` 上没有解除短路运算符?

为啥 Coq 中的逻辑连接词和布尔值是分开的?

为啥这个结合赋值和相等检查的 if 语句返回 true?