我试图了解逻辑运算符优先级在 bash 中的工作原理。例如,我预计以下命令不会回显任何内容。
true || echo aaa && echo bbb
然而,与我的预期相反,它bbb
被打印了。
有人可以解释一下,我如何理解bash 中的复合运算符&&
和运算符吗?||
答案1
在许多计算机语言中,具有相同优先级的运算符是左结合。也就是说,在没有分组结构的情况下,首先执行最左边的操作。重击是没有例外到这个规则。
这很重要,因为在 Bash 和其他 shell 中&&
具有||
相同的优先级。这与大多数其他编程语言不同,大多数其他编程语言通常&&
比||
.
因此,在您的示例中,||
首先执行最左边的操作 ( ):
true || echo aaa
由于true
显然是正确的,因此||
运算符短路并且整个语句被认为是正确的,而不需要echo aaa
像您期望的那样进行评估。现在剩下要做最右边的操作:
(...) && echo bbb
由于第一个操作评估为 true(即退出状态为 0),就好像您正在执行
true && echo bbb
所以&&
不会短路,这就是为什么你看到bbb
回声。
你会得到同样的行为
false && echo aaa || echo bbb
根据评论进行注释
您应该注意左结合性规则是仅有的当两个操作员都有相同优先。当您将这些运算符与
[[...]]
or等关键字结合使用((...))
或使用-o
and-a
运算符作为test
or[
命令的参数时,情况并非如此。在这种情况下,AND(&&
或-a
)优先于 OR(||
或-o
)。感谢 Stephane Chazelas 的评论澄清了这一点。看起来在C中类 C 语言的
&&
优先级高于||
这可能就是为什么您期望原始构造的行为类似于true || (echo aaa && echo bbb).
然而,Bash 的情况并非如此,其中两个运算符具有相同的优先级,这就是 Bash 使用左关联性规则解析表达式的原因。感谢凯文的评论提出这一点。
- 也可能存在这样的情况所有 3 个表达式被求值。如果第一个命令返回非零退出状态,则
||
不会短路并继续执行第二个命令。如果第二个命令以零退出状态返回,则&&
也不会短路并且将执行第三个命令。感谢 Ignacio Vazquez-Abrams 的评论提出了这一点。
答案2
如果您希望多项事情取决于您的情况,请将它们分组:
true || { echo aaa && echo bbb; }
那什么都不打印,而
true && { echo aaa && echo bbb; }
打印两个字符串。
发生这种情况的原因比约瑟夫想象的要简单得多。记住 Bash 对||
和 的作用&&
。这都是关于前一个命令的返回状态。查看原始命令的字面方式是:
( true || echo aaa ) && echo bbb
第一个命令 ( true || echo aaa
) 以 退出0
。
$ true || echo aaa; echo $?
0
$ true && echo aaa; echo $?
aaa
0
$ false && echo aaa; echo $?
1
$ false || echo aaa; echo $?
aaa
0
答案3
&&
和运算符||
是不是if-then-else 的精确内联替换。但如果使用得当,它们也能完成同样的事情。
单个测试是简单且明确的......
[[ A == A ]] && echo TRUE # TRUE
[[ A == B ]] && echo TRUE #
[[ A == A ]] || echo FALSE #
[[ A == B ]] || echo FALSE # FALSE
然而,尝试添加多个测试可能会产生意想不到的结果......
[[ A == A ]] && echo TRUE || echo FALSE # TRUE (as expected)
[[ A == B ]] && echo TRUE || echo FALSE # FALSE (as expected)
[[ A == A ]] || echo FALSE && echo TRUE # TRUE (as expected)
[[ A == B ]] || echo FALSE && echo TRUE # FALSE TRUE (huh?)
为什么两者都是FALSE和TRUE 回响了吗?
这里发生的情况是,我们还没有意识到 和&&
是||
重载运算符,它们在条件测试括号内的行为[[ ]]
与我们这里的 AND 和 OR(条件执行)列表中的行为不同。
来自 bash 联机帮助页(已编辑)...
列表
列表是由运算符 ;、&、&& 或 ││ 之一分隔的一个或多个管道的序列,并且可以选择以 ;、& 或 之一终止。在这些列表运算符中,&& 和 │ 具有相同的优先级,其次是 ;和 &,具有相同的优先级。
列表中可能会出现一系列的一个或多个换行符,而不是分号来分隔命令。
如果命令以控制操作符 & 结束,则 shell 会在子 shell 中在后台执行该命令。shell 不会等待命令完成,返回状态为 0。以 ; 分隔的命令按顺序执行;shell 会依次等待每个命令终止。返回状态是最后执行的命令的退出状态。
AND 和 OR 列表是分别由 && 和 │ 控制运算符分隔的多个管道之一的序列。 AND 和 OR 列表以左关联性执行。
AND 列表的形式为 ...
command1 && command2
当且仅当 command1 返回退出状态为零时,才会执行 Command2。OR 列表的形式为 ...
command1 ││ command2
当且仅当 command1 返回非零退出状态时,才会执行 Command2。AND 和 OR 列表的返回状态是列表中最后执行的命令的退出状态。
回到我们的最后一个例子......
[[ A == B ]] || echo FALSE && echo TRUE
[[ A == B ]] is false
|| Does NOT mean OR! It means...
'execute next command if last command return code(rc) was false'
echo FALSE The 'echo' command rc is always true
(i.e. it successfully echoed the word "FALSE")
&& Execute next command if last command rc was true
echo TRUE Since the 'echo FALSE' rc was true, then echo "TRUE"
好的。如果这是正确的,那么为什么倒数第二个示例会回显任何内容?
[[ A == A ]] || echo FALSE && echo TRUE
[[ A == A ]] is true
|| execute next command if last command rc was false.
echo FALSE Since last rc was true, shouldn't it have stopped before this?
Nope. Instead, it skips the 'echo FALSE', does not even try to
execute it, and continues looking for a `&&` clause.
&& ... which it finds here
echo TRUE ... so, since `[[ A == A ]]` is true, then it echos "TRUE"
当使用多个&&
或||
在命令列表中时,出现逻辑错误的风险相当高。
建议
命令列表中的单个&&
或按预期工作,因此非常安全。||
如果您不需要 else 子句,则可以更清楚地遵循以下内容(需要花括号来对最后 2 个命令进行分组)...
[[ $1 == --help ]] && { echo "$HELP"; exit; }
多个&&
and||
运算符,其中除最后一个之外的每个命令都是一个测试(即在方括号内[[ ]]
),通常也是安全的,因为除了最后一个运算符之外的所有运算符都按预期运行。最后一个运算符的作用更像是then
orelse
子句。
答案4
我也对此感到困惑,但以下是我对 Bash 读取语句的方式的看法(因为它从左到右读取符号):
- 找到符号
true
.一旦到达命令末尾,就需要对其进行评估。至此,不知道有没有什么争论。将命令存储在执行缓冲区中。 - 找到符号
||
.之前的命令现已完成,因此请对其进行评估。正在执行的命令(缓冲区):true
.评估结果:0(即成功)。将结果 0 存储在“上次评估”寄存器中。现在考虑符号||
本身。这取决于上次评估的结果是否非零。检查“上次评估”寄存器并发现 0。由于 0 不是非零,因此不需要评估以下命令。 - 找到符号
echo
.可以忽略该符号,因为不需要评估以下命令。 - 找到符号
aaa
.这是命令 (3) 的一个参数echo
,但由于echo
不需要计算 (3),因此可以忽略它。 - 找到符号
&&
.这取决于上次评估的结果是否为零。检查“上次评估”寄存器并发现 0。由于 0 为零,因此确实需要评估以下命令。 - 找到符号
echo
.一旦到达命令末尾,就需要评估该命令,因为确实需要评估以下命令。将命令存储在执行缓冲区中。 - 找到符号
bbb
.这是命令echo
(6)的一个参数。由于echo
确实需要评估,因此添加bbb
到执行缓冲区。 - 已到达行尾。先前的命令现已完成,并且确实需要评估。正在执行的命令(缓冲区):
echo bbb
.评估结果:0(即成功)。将结果 0 存储在“上次评估”寄存器中。
当然,最后一步会导致bbb
回显到控制台。