Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I am trying to understand how the logical operator precedence works in bash. For example, I would have expected, that the following command does not echo anything.

true || echo aaa && echo bbb

However, contrary to my expectation, bbb gets printed.

Can somebody please explain, how can I make sense of compounded && and || operators in bash?

share|improve this question

3 Answers 3

up vote 26 down vote accepted

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.

share|improve this answer
11  
A wee note to add a little more confusion: while the && and || shell operators as in cmd1 && cmd2 || cmd3 have same precedence, the && in ((...)) and [[...]] has precedence over || (((a || b && c)) is ((a || (b && c)))). Same goes for -a/-o in test/[ and find and &/| in expr. –  Stéphane Chazelas Aug 30 '13 at 12:17
    
Thanks. Adding to answer... –  Joseph R. Aug 30 '13 at 12:34
5  
In c-like languages, && has higher precedence than ||, so the OP's expected behavior would happen. Unwary users used to such languages may not realize that in bash they have the same precedence, so it may be worth pointing out more explicitly that they do have the same precedence in bash. –  Kevin Aug 30 '13 at 13:06
    
Also note that there are situations where all three commands can be run, i.e. if the middle command can return either true or false. –  Ignacio Vazquez-Abrams Aug 30 '13 at 13:42
    
@Kevin Please check edited answer. –  Joseph R. Aug 30 '13 at 18:51

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.

share|improve this answer
    
Thanks for the useful explanations –  Felipe Alvarez May 14 at 7:46

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

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

That returns nothing, while

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

echos 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
share|improve this answer
2  
+1 For achieving the OP's intended outcome. Note that the parentheses you placed in (true || echo aaa) && echo bbb is exactly what I'm making out. –  Joseph R. Aug 30 '13 at 13:53
1  
Nice to see @Oli in this side of the world. –  Braiam Aug 30 '13 at 16:12

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.