bash中管道(|)和逻辑与(&&)的优先级


17

具有操作符优先级的经典场景,您会看到如下一行:

(cd ~/screenshots/ && ls screenshot* | head -n 5)

而且您不知道它是否已解析((A && B) | C)(A && B | C) ...

这里找到的几乎官方文档没有在列表中列出管道,因此我不能简单地在表中检查。

此外,在bash中,(不仅用于更改操作顺序,而且还创建了一个subshel​​l,所以我不是100%确信此行与上一行等效:

((cd ~/screenshots/ && ls screenshot*) | head -n 5)

更笼统地说,如何知道bash线的AST?在python中,我有一个给我树的函数,这样我就可以轻松地仔细检查操作顺序。


1
知道这|只是一个适合LHS stdout和RHS stdin的连接器可能会有所帮助。因此,如果cd您的示例中的命令失败,它将不会向发送任何输出head,但head实际上仍然会execute解析任何内容,并且不返回任何输出。
DopeGhoti


1
bash使用yacc 解析器不是特别的事情;如果您通过yacc -v它运行它将为您提供y.output一个很好的语法,该语法可以显示&&||合并列表,列表最终由管道组成(而不是相反);tl; dr; 与预期A && B | C相同A && { B | C; }。不要假定B和之间的任何执行顺序C。管道中的命令并行运行。
mosvy

1
请注意,您指向的“几乎官方文档”是完全不相关的,因为它涉及[...]测试和$((...))算术评估中使用的运算符。特别是,||并且&&与shell语言中的命令列表一起使用时,具有相同的优先级,这与C算术运算中或运算中的各个运算符(其中&&绑定比紧密绑定||)不同。
mosvy

@mosvy,该文档甚至没有说表适用于什么上下文,因此从这个意义上来说,它似乎也没有用...
ilkkachu

Answers:


21
cd ~/screenshots/ && ls screenshot* | head -n 5

这相当于

cd ~/screenshots && { ls screenshot* | head -n 5 ; }

(花括号将命令分组在一起,而没有subshel​​l)。的优先级|比从而得到更高(更紧密的结合)&&||。那是,

A && B | C

A || B | C

总是意味着只有B的输出,是考虑到C。您可以根据需要使用(...){ ... ; }将命令作为单个实体结合在一起以消除歧义:

{ A && B ; } | C
A && { B | C ; } # This is the default, but you might sometimes want to be explicit

您可以使用一些不同的命令对此进行测试。如果你跑

echo hello && echo world | tr a-z A-Z

那么你会得到

hello
WORLD

back:将tr a-z A-Z其输入大写,您可以看到只有echo world通过管道输入时,echo hello它才独立进行。


在定义外壳的语法,虽然并不十分清楚:在and_or生产(为&&/ ||)被定义为具有AA pipeline在它的身上,而pipeline只包含command,其中包含and_or-只有complete_command产能达到and_or,而且它只能在存在顶层和内部结构构造体(例如函数和循环)。

您可以手动应用该语法以获取命令的分析树,但Bash本身不提供任何内容。我不知道有什么外壳程序可以进行超出其自身解析能力的工作。

外壳语法有很多特殊情况,它们只是半正式定义的,这是正确的使命。甚至Bash本身有时也弄错了,因此实用性和理想可能有所不同。

有一些外部解析器尝​​试匹配语法并生成树,在这些解析器中,我将广泛推荐Morbig,它试图是最可靠的。


7

TL; DR:列表分隔符,例如;&&&||确定解析顺序。

bash的手册告诉我们:

AND和OR列表是由&&和||分隔的一个或多个管道的序列。分别控制操作员。

或者Bash Hacker的Wiki如何简洁地说明这一点

<PIPELINE1> && <PIPELINE2>

因此,cd ~/screenshots/ && ls screenshot* | head -n 5 其中有一个管道- ls screenshot* | head -n 5一个简单的命令cd ~/screenshots/。注意根据手册

管道中的每个命令都作为单独的进程(即,在子Shell中)执行。

另一方面,(cd ~/screenshots/ && ls screenshot*) | head -n 5情况有所不同-您有一个管道:左侧有subshel​​l,右侧有head -n 5。在这种情况下,使用OP的符号将是(A && B) | C


让我们再举一个例子:

$ echo foo | false &&  echo 123 | tr 2 5
$

这里有一个清单<pipeline1> && <pipeline2>。由于我们知道管道的退出状态与上一个命令相同,并且会false返回负状态(即失败),&&因此不会执行右侧操作。

$ echo foo | true &&  echo 123 | tr 2 5
153

在这里,左侧管道具有成功退出状态,因此右侧管道已执行,我们将看到其输出。


请注意,shell语法并不意味着实际的执行顺序。引用吉尔斯的答案之一

管道命令同时运行。当您运行ps | grep…,这是运气的好运(或者说是shell的工作细节,再加上调度程序在内核的深处进行了微调)是关于ps还是grep首先开始,无论如何它们都继续同时执行。

从bash手册:

AND和OR列表以左关联性执行。

在此基础上在cd ~/screenshots/ && ls screenshot* | head -n 5cd ~/screenshots/会首先执行,ls screenshot* | head -n 5如果前面的命令成功,但head -n 5可能是第一个进程产生的,而不是ls因为他们是在一个管道。


1
我看不到问题在哪里问到产生什么顺序的问题。
迈克尔·荷马

“没有优先出现的优先级,cd ~/screenshots/ && ls screenshot*或者head -n 5”“是的,是这样((cd ~/screenshots/ && ls screenshot*) | head -n 5)。但这并没有说明模棱两可的情况cd ~/screenshots/ && ls screenshot* | head -n 5,这似乎是问题的重点(带或不带括号)。
ilkkachu

@ilkkachu是的,但以下句子对此有所提及。cd ~/screenshots/ && ls screenshot* 必须首先处理,因为&&列表的优先级更高(至少基于l0b0的答案)。您认为我应该在哪里改进答案?
Sergiy Kolodyazhnyy

@SergiyKolodyazhnyy,好吧,考虑到问题既有(cd && ls | head)((cd && ls) | head),则最好明确指出您的意思。
ilkkachu

@ilkkachu好吧,我现在将其删除,然后同时进行编辑
Sergiy Kolodyazhnyy

3

这是在中指定的位置bash(1)

SHELL GRAMMAR
[...]
   Pipelines
       A  pipeline  is  a sequence of one or more commands separated by one of
       the control operators | or |&.
[...]
   Lists
       A list is a sequence of one or more pipelines separated by one  of  the
       operators ;, &, &&, or ||, and optionally terminated by one of ;, &, or
       <newline>.

因此,&&分隔管道。


2

您可以尝试一下echo hello && echo world | less。您将看到|优先级更高(命令管道是命令)。你的第二个例子一样。但是,由于cd没有输出,以太方式不会有任何区别。

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.