Precedence of the shell logical operators &&, ||

In many computer languages, operators with the same precedence are left-associative. That is, in the absence of grouping structures, leftmost operations are executed first. Bash is no exception to this rule.

This is important because, in Bash, && and || have the same precedence.

So what happens in your example is that the leftmost operation (||) is carried out first:

true || echo aaa

Since true is obviously true, the || operator short-circuits and the whole statement is considered true without the need to evaluate echo aaa as you would expect. Now it remains to do the rightmost operation:

(...) && echo bbb

Since the first operation evaluated to true (i.e. had a 0 exit status), it's as if you're executing

true && echo bbb

so the && will not short-circuit, which is why you see bbb echoed.

You would get the same behavior with

false && echo aaa || echo bbb

Notes based on the comments

  • You should note that the left-associativity rule is only followed when both operators have the same precedence. This is not the case when you use these operators in conjunction with keywords such as [[...]] or ((...)) or use the -o and -a operators as arguments to the test or [ commands. In such cases, AND (&& or -a) takes precedence over OR (|| or -o). Thanks to Stephane Chazelas' comment for clarifying this point.
  • It seems that in C and C-like languages && has higher precedence than || which is probably why you expected your original construct to behave like

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

    This is not the case with Bash, however, in which both operators have the same precedence, which is why Bash parses your expression using the left-associativity rule. Thanks to Kevin's comment for bringing this up.

  • There might also be cases where all 3 expressions are evaluated. If the first command returns a non-zero exit status, the || won't short circuit and goes on to execute the second command. If the second command returns with a zero exit status, then the && won't short-circuit as well and the third command will be executed. Thanks to Ignacio Vazquez-Abrams' comment for bringing this up.


If you want multiple things to depend on your condition, group them:

true || { echo aaa && echo bbb; }

That prints nothing, while

true && { echo aaa && echo bbb; }

prints both strings.


The reason this happens is a lot more simple than Joseph is making out. Remember what Bash does with || and &&. It's all about the previous command's return status. A literal way of looking at your raw command is:

( true || echo aaa ) && echo bbb

The first command (true || echo aaa) is exiting with 0.

$ true || echo aaa; echo $?
0
$ true && echo aaa; echo $?
aaa
0

$ false && echo aaa; echo $?
1
$ false || echo aaa; echo $?
aaa
0

The && and || operators are not exact inline replacements for if-then-else. Though if used carefully, they can accomplish much the same thing.

A single test is straightforward and unambiguous...

[[ A == A ]]  && echo TRUE                          # TRUE
[[ A == B ]]  && echo TRUE                          # 
[[ A == A ]]  || echo FALSE                         # 
[[ A == B ]]  || echo FALSE                         # FALSE

However, attempting to add multiple tests may yield unexpected results...

[[ 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?)

Why are both FALSE and TRUE echoed?

What's happening here is that we've not realized that && and || are overloaded operators that act differently inside conditional test brackets [[ ]] than they do in the AND and OR (conditional execution) list we have here.

From the bash manpage (edited)...

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 . Of these list operators, && and ││ have equal precedence, followed by ; and &, which have equal precedence.

A sequence of one or more newlines may appear in a list instead of a semicolon to delimit commands.

If a command is terminated by the control operator &, the shell executes the command in the background in a subshell. The shell does not wait for the command to finish, and the return status is 0. Commands separated by a ; are executed sequentially; the shell waits for each command to terminate in turn. The return status is the exit status of the last command executed.

AND and OR lists are sequences of one of more pipelines separated by the && and ││ control operators, respectively. AND and OR lists are executed with left associativity.

An AND list has the form ...
command1 && command2
Command2 is executed if, and only if, command1 returns an exit status of zero.

An OR list has the form ...
command1 ││ command2
Command2 is executed if and only if command1 returns a non-zero exit status.

The return status of AND and OR lists is the exit status of the last command executed in the list.

Returning to our last example...

[[ 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"

Okay. If that's correct, then why does the next to last example echo anything at all?

[[ 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"

The risk of logic errors when using more than one && or || in a command list is quite high.

Recommendations

A single && or || in a command list works as expected so is pretty safe. If it's a situation where you don't need an else clause, something like the following can be clearer to follow (the curly braces are required to group the last 2 commands) ...

[[ $1 == --help ]]  && { echo "$HELP"; exit; }

Multiple && and || operators, where each command except for the last is a test (i.e. inside brackets [[ ]]), are usually also safe as all but the last operator behave as expected. The last operator acts more like a then or else clause.

Tags:

Shell

Bash