18

I know of the relative binding precedence of operators ;, &, &&, or || from the Bash documentation.

But when pipes come into the picture along with && I struggle to understand the binding strength and either stumble upon a correct command or just give up.

What is the binding precedence of | and > compared to the above?

Example where I get confused:

ls _thumbnails/video.mp4.jpg 2>/dev/null 
    && echo "thumbnail already generated. Not regenerating" \
    && exit \
    || ffmpeg_thumbnail_create video.mp4 2>/dev/null \
    && ls _thumbnails/video.mp4.jpg \
    && echo "Thumbnail successfully created" \
    && exit \
    || echo "Thumbnail creation failed" \
    | tee ~/thumbnails.log

The goal of the above is to create a thumbnail if and only if one isn't already present (I run a daily cronjob). And I don't like FFMpeg's huge amount of output when there is no error (which is not the Unix way).

There are other situations too, so advice that uses separate statements or special options specific to these programs will not help - I want to understand binding precedence.

AdminBee
  • 22,803
Sridhar Sarnobat
  • 1,802
  • 20
  • 27
  • Very helpful - I'd mark this correct if it was a response. Just one more question - how do I alter precedence? In theory I think {...} should work. I might have tried it unsystematically and not got the expected results. – Sridhar Sarnobat Oct 22 '14 at 00:27

1 Answers1

26

The short answer is that <, >, and their variants have the highest binding precedence (tightest binding), followed by |, followed by && and ||, followed by ; and &.  So, only the echo "Thumbnail creation failed" is piped into the tee.

A slightly longer answer would point out that the highest precedence is actually grouping, which can be indicated with parentheses or braces. For example,

A  &&  (B; C)

and

A  &&  { B; C;}

are approximately equivalent to

if A
then
    B
    C
fi

Notes:

  • The parentheses give you a subshell; i.e., commands B and C run in a child process.  Therefore, commands like variable assignments or cd will have no effect on the parent shell.  Commands in braces run in the same process as the A command.  Therefore, the brace construct A && { B; C;} is closer to the if-then-else construct.
  • In the brace syntax, there must be a space after the { and a ; (or a &, or newline) before the }.

For further reading, see What are the shell’s control and redirection operators? and When is ‘if’ not necessary? (particularly my answers).

For even more further reading, see the bash(1) man page and the POSIX specification / definition of the Shell Command Language, specifically Section 2.9, Shell Commands and Section 2.10.2, Shell Grammar Rules.  This is an attempt to provide some context for the above:

  • Things like
  • myVar=42
  • IFS= read a
  • date
  • cd /some/directory
  • ls -laR dir1 dir2
  • cat foo* > /tmp/allfoo
  • ls -laR dir{1,2}
  • find . -type f -name "foo*" -print > /tmp/output 2> /dev/null
  • > newfile
  • [ -f catfood ]
  • exit

are all considered to be “simple commands”.

  • Things like
      simple_command1  |  simple_command2  |  simple_command3
    are “pipelines”.  The grammar establishes building blocks and builds on them, as is typical for formal grammars like this (and for programming languages like C), so an individual “simple command” is considered to be a “pipeline”, even though it doesn’t contain a pipe.  It doesn’t make (semantic) sense for a variable assignment to be a component of a pipeline, but things like x=1 | od -ab or ls -laR | z=0 are syntactically valid.
  • Things like
      pipeline1  &&  pipeline2  ||  pipeline3
      are called “lists” by bash and “AND-OR lists” by POSIX.  Again, an individual “pipeline” or even an individual “simple command” is considered to be an “AND-OR list”, even though it doesn’t contain an AND or an OR.
    • When you get to things like
        AND-OR list1 &  AND-OR list2 ;  AND-OR list3
        the nomenclature starts to get a bit inconsistent.  Bash calls these “lists” also; POSIX calls them “lists”, “compound lists” and (rarely) “terms”.  Again, an individual “AND-OR list”, “pipeline” or even an individual “simple command” is considered to be a “list”, even though it doesn’t contain a & or a ;.
      • Things like
          (compound_list)
          and
          • { compound_list;}
            and the flow-control commands (`for`, `if`-`then`-`else`, `while`, etc.) are called “compound commands”.

            In the examples above, it probably makes the most sense to interpret A, B, and C to be pipelines.  Remember, a “pipeline” can be an individual “simple command”; it doesn’t need to contain a pipe.

            AJM
            • 133
            • 1
              Thanks for the detailed explanation on the role of (..) and {..}. I had concluded from previous bad experience that round brackets were only used in the context of "dynamic commands" (i.e. $(..)) since I could barely get them to do anything as far as associativity was concerned. – Sridhar Sarnobat Oct 22 '14 at 17:56
            • Just to add: { c1; c2; c3;} >>log joins output, so it could be easily redirected. Whereas the exit status of this expression would be the exit status of c3. – Hrobky Feb 03 '21 at 05:26