Shell逻辑运算符&&,||的优先级


126

我试图了解逻辑运算符优先级如何在bash中工作。例如,我曾期望,以下命令不会回显任何内容。

true || echo aaa && echo bbb

但是,与我的预期相反,bbb被印刷了。

可有人请解释一下,我怎样才能使复利感&&||在bash运营商?

Answers:


127

在许多计算机语言中,具有相同优先级的运算符是左关联的。即,在没有分组结构的情况下,最左边的操作首先被执行。Bash 也不例外

因为,在击,这一点很重要&&,并||具有相同的优先级。

因此,在您的示例中发生的是最左边的操作(||)首先执行:

true || echo aaa

由于true显然是正确的,所以||操作员会短路,整个语句被认为是正确的,而无需echo aaa按您期望的那样进行评估。现在剩下要做最右边的操作了:

(...) && echo bbb

由于第一个操作的评估结果为true(即退出状态为0),就好像您正在执行

true && echo bbb

因此&&不会短路,这就是您看到bbb回声的原因。

您将获得与相同的行为

false && echo aaa || echo bbb

基于注释的注释

  • 您应注意,当两个运算符优先级相同时,遵循左关联规则。当您将这些运算符与诸如[[...]]or ((...))或关键字一起使用,或将-oand -a运算符用作testor [命令的参数时,情况并非如此。在这种情况下,AND(&&-a)优先于OR(||-o)。感谢Stephane Chazelas的评论澄清了这一点。
  • 似乎在C和类似C的语言中,&&其优先级高于||,这可能就是为什么您希望原始构造的行为像

    true || (echo aaa && echo bbb). 

    但是Bash情况并非如此,因为这两个运算符的优先级都相同,这就是Bash使用左关联规则解析您的表达式的原因。感谢Kevin提出的评论。

  • 在某些情况下,可能会同时评估所有3个表达式。如果第一个命令返回非零退出状态,||则不会短路,然后继续执行第二个命令。如果第二个命令返回的退出状态为零,则&&不会短路,第三个命令将被执行。感谢Ignacio Vazquez-Abrams提出的评论。


26
稍加说明一下,这会增加一些混乱:尽管in &&||shell运算符的cmd1 && cmd2 || cmd3优先级相同,但&&in ((...))和in的[[...]]优先级高于||((a || b && c))is ((a || (b && c))))。这同样适用于-a/ -otest/ [find&/ |expr
斯特凡Chazelas

16
在类似C的语言中,&&优先级高于||,因此将发生OP的预期行为。习惯这种语言的粗心用户可能不会意识到在bash中它们具有相同的优先级,因此可能需要更明确地指出他们在bash中具有相同的优先级。
凯文

还要注意,在某些情况下,所有三个命令都可以运行,即中间命令可以返回true false。
伊格纳西奥·巴斯克斯

@Kevin请检查编辑后的答案。
Joseph R.

1
今天,对我而言,“ bash运算符优先级”在Google上的热门搜索是tldp.org/LDP/abs/html/opprecedence.html ...,它声称&&的优先级高于||。但是@JosephR。显然在事实上和法律上都是正确的。Dash的行为相同,并在pubs.opengroup.org/onlinepubs/009695399/utilities/…中搜索“优先级” ,我认为这是POSIX要求,因此我们可以依靠它。我发现的所有错误报告都是作者的电子邮件地址,在过去六年中,该电子邮件地址肯定是垃圾邮件。无论如何,我都会尝试...
Martin Dorey

62

如果您希望多种情况取决于您的状况,请将它们分组:

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

7
+1用于实现OP的预期结果。请注意,您放入的括号(true || echo aaa) && echo bbb正是我要写的。
约瑟夫R.13年

1
很高兴在世界这边看到@Oli。
Braiam

7
请注意最后的半列;。没有这个,它将无法工作!
Serge Stroobandt

2
我遇到了麻烦,因为我在最后一条命令之后缺少尾部分号(例如echo bbb;,在第一个示例中)。一旦我发现自己想不到的话,这个答案正是我想要的。+1帮助我弄清楚如何实现自己想要的目标!
Doktor J

5
值得任何困惑(...){...}表示法的人阅读:命令分组手册
ANTARA

16

&&||运营商都没有精确的内联替代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手册页(已编辑)...

清单

列表是由一个或多个管线组成的序列,这些管线由运算符;,&,&&或││分隔,并可选地由;,&,或之一终止。在这些列表运算符中,&&和││具有相同的优先级,其后是;;和&,具有相同的优先级。

列表中可能会出现一个或多个换行符序列,而不是用分号分隔命令。

如果命令由控制操作符&终止,则外壳程序将在子外壳程序的后台执行该命令。外壳程序不等待命令完成,返回状态为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; }

多重运算符&&||运算符(除了最后一个运算符之外的每个命令都是测试)(即,放在方括号内[[ ]])通常也很安全,因为除最后一个运算符外,所有命令的行为均符合预期。最后一个运算符的行为更像是thenor else子句。


0

我对此也感到困惑,但这是我对Bash读取语句的方式的看法(因为它从左到右读取符号):

  1. 找到符号true。到达命令末尾时,将需要对此进行评估。在这一点上,不知道它是否有任何参数。将命令存储在执行缓冲区中。
  2. 找到符号||。上一条命令现已完成,因此请对其进行评估。命令(缓冲器)中执行:true。评估结果:0(即成功)。将结果0存储在“上次评估”寄存器中。现在考虑符号||本身。这取决于上次评估的结果为非零。检查“最后评估”寄存器,发现为0。由于0并非非零,因此不需要评估以下命令。
  3. 找到符号echo。可以忽略此符号,因为不需要评估以下命令。
  4. 找到符号aaa。这是命令echo(3)的参数,但是由于echo(3)不需要评估,因此可以忽略。
  5. 找到符号&&。这取决于最后评估的结果为零。检查“最后评估”寄存器,发现为0。由于0为零,因此需要评估以下命令。
  6. 找到符号echo。一旦到达命令结尾,就需要评估此命令,因为确实需要评估以下命令。将命令存储在执行缓冲区中。
  7. 找到符号bbb。这是命令echo(6)的参数。由于echo确实需要评估,因此添加bbb到执行缓冲区。
  8. 到达行尾。上一条命令现已完成,需要评估。命令(缓冲器)中执行:echo bbb。评估结果:0(即成功)。将结果0存储在“上次评估”寄存器中。

当然,最后一步会引起bbb控制台响应。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.