Precedence of Pipe (|) and logical and (&&) in bash

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

This is equivalent to

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

(the braces group commands together without a subshell). The precedence of | is thus higher (binds tighter) than && and ||. That is,

A && B | C

and

A || B | C

always mean that only B's output is to be given to C. You can use (...) or { ... ; } to join commands together as a single entity for disambiguation if necessary:

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

You can test this out using some different commands. If you run

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

then you'll get

hello
WORLD

back: tr a-z A-Z upper-cases its input, and you can see that only echo world was piped into it, while echo hello went through on its own.


This is defined in the shell grammar, although not terribly clearly: the and_or production (for &&/||) is defined to have a a pipeline in its body, while pipeline just contains command, which doesn't contain and_or - only the complete_command production can reach and_or, and it only exists at the top level and inside the bodies of structural constructs like functions and loops.

You can manually apply that grammar to get a parse tree for a command, but Bash doesn't provide anything itself. I don't know of any shell that does beyond what's used for their own parsing.

The shell grammar has a lot of special cases defined only semi-formally and it can be quite a mission to get right. Even Bash itself has sometimes gotten it wrong, so the practicalities and the ideal may be different.

There are external parsers that attempt to match the syntax and produce a tree, and of those I will broadly recommend Morbig, which attempts to be the most reliable.


TL;DR: list separators such as ;. &, &&, and || decide the parsing order.

The bash manual tells us:

AND and OR lists are sequences of one or more pipelines separated by the && and || control operators, respectively.

Or how Bash Hacker's wiki succinctly put it

<PIPELINE1> && <PIPELINE2>

Thus, in cd ~/screenshots/ && ls screenshot* | head -n 5 there is one pipeline - ls screenshot* | head -n 5 and one simple command cd ~/screenshots/. Note that according to the manual

Each command in a pipeline is executed as a separate process (i.e., in a subshell).

On the other hand, (cd ~/screenshots/ && ls screenshot*) | head -n 5 is different - you have one pipeline: on the left there is subshell and on the right you have head -n 5. In this case, using OP's notation it would be (A && B) | C


Let's take another example:

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

Here we have one list <pipeline1> && <pipeline2>. Since we know that exit status of pipeline is the same as of the last command and false returns negative status aka fail, && won't execute right hand side.

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

Here the left pipeline has success exit status, so right pipeline is executed and we see its output.


Note that shell grammar doesn't imply actual execution order. To quote one of Gilles's answer:

Piped commands run concurrently. When you run ps | grep …, it's the luck of the draw (or a matter of details of the workings of the shell combined with scheduler fine-tuning deep in the bowels of the kernel) as to whether ps or grep starts first, and in any case they continue to execute concurrently.

And from bash manual:

AND and OR lists are executed with left associativity.

Based on that in cd ~/screenshots/ && ls screenshot* | head -n 5 the cd ~/screenshots/ would be executed first, ls screenshot* | head -n 5 if previous command succeeds, but head -n 5 may be a first process spawned rather than ls since they are in a pipeline.


Here's where it's specified in 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>.

So, && separates pipelines.

Tags:

Bash