222

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?

1
  • This has confused me a lot in trying to write shell script pipelines with different logic based on some test (e.g. exiting if a file doesn't exist vs continuing the pipeline). Commented Sep 11, 2022 at 22:41

5 Answers 5

211

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 other shells && and || have the same precedence. This is different from most other programming languages which usually give && a higher precedence than ||.

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.
12
  • 34
    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. Commented Aug 30, 2013 at 12:17
  • 23
    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
    Commented Aug 30, 2013 at 13:06
  • 2
    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. Commented Aug 30, 2013 at 13:42
  • 7
    Today, for me, the top Google hit for "bash operator precedence" is tldp.org/LDP/abs/html/opprecedence.html... which claims that && has a higher precedence than ||. Yet @JosephR. is clearly right de facto and de jure too. Dash behaves the same and searching for "precedence" in pubs.opengroup.org/onlinepubs/009695399/utilities/…, I see it's a POSIX requirement, so we can depend on it. All I found for bug reporting was the author's email address, which has surely been spammed to death in the last six years. I'll try anyway... Commented Aug 21, 2018 at 19:37
  • 3
    @MartinDorey I read that page the same way as you, but having read the above answer, I am now interpreting the blank rows as "group separators" so that adjacent (non-blank) rows are of equal precedence.
    – IpsRich
    Commented Aug 8, 2019 at 8:42
114

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
6
  • 9
    +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.
    Commented Aug 30, 2013 at 13:53
  • 21
    Note the semi-column ; at the very end. Without this, it will not work! Commented Dec 19, 2016 at 2:46
  • 2
    I was running into trouble with this because I was missing the trailing semi-colon after the last command (e.g. echo bbb; in the first examples). Once I figured out I was missing that, this answer was exactly what I was looking for. +1 for helping me figure out how to accomplish what I wanted!
    – Doktor J
    Commented Jun 12, 2017 at 18:03
  • 23
    Worth reading for anyone confused by (...) and {...} notation: command grouping manual
    – ANTARA
    Commented Jun 4, 2018 at 14:57
  • 1
    This is the key: ( true || echo aaa ) && echo bbb, which explains how the OS interprets the raw command.
    – wisbucky
    Commented Nov 6, 2018 at 21:51
24

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.

1
  • I like your (implicit) recommendation to stick to one operator. Commented Sep 11, 2022 at 22:42
0

Maybe a neat example:

% true || true && false;echo $?
1
% true || (true && false);echo $?
0

true is a command that does nothing and has the exit status 0 meaning "true".

The content of the parentheses runs in a subshell. echo $? displays the exit status of the last command.

Moreover, the question is self-contradictory: || and && in the context of the question are not logical operators but command compositors. (Roughly, they belong to modal not boolean logic.)

If you are indeed interested in logical operators && and || on strings, occuring inside test [[...]], && precedes || so that for example

%  [[ 0 || 0 && '' ]];echo $? 
0
%  [[ (0 || 0) && '' ]];echo $?
1

A string is true if and only if it is not empty.

Abstractly of exit status:

% if [[ 0 || 0 && '' ]];then echo oui;else echo non;fi
oui
% if [[ (0 || 0) && '' ]];then echo oui;else echo non;fi
non

All this applies to zsh or bash.

-1

I also got confused by this but here's how I think about the way Bash reads your statement (as it reads the symbols left to right):

  1. Found symbol true. This will need to be evaluated once the end of the command is reached. At this point, don't know if it has any arguments. Store command in execution buffer.
  2. Found symbol ||. Previous command is now complete, so evaluate it. Command (buffer) being executed: true. Result of evaluation: 0 (i.e. success). Store result 0 in "last evaluation" register. Now consider symbol || itself. This depends on the result of the last evaluation being non-zero. Checked "last evaluation" register and found 0. Since 0 is not non-zero, the following command does not need to be evaluated.
  3. Found symbol echo. Can ignore this symbol, because the following command did not need to be evaluated.
  4. Found symbol aaa. This is an argument to command echo (3), but since echo (3) did not need to be evaluated, it can be ignored.
  5. Found symbol &&. This depends on the result of the last evaluation being zero. Checked "last evaluation" register and found 0. Since 0 is zero, the following command does need to be evaluated.
  6. Found symbol echo. This command needs to be evaluated once the end of the command is reached, because the following command did need to be evaluated. Store command in execution buffer.
  7. Found symbol bbb. This is an argument to command echo (6). Since echo did need to be evaluated, add bbb to execution buffer.
  8. Reached end of line. Previous command is now complete and did need to be evaluated. Command (buffer) being executed: echo bbb. Result of evaluation: 0 (i.e. success). Store result 0 in "last evaluation" register.

And of course, the last step causes bbb to be echoed to the console.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .