3

I'm working through some basic exercises using Bash and I'm confused on the order of operations of && and ||. Below are some reproducible examples.

# Example 1
true && false || echo pass
# pass

Since the first true is executed, && passes on to false and false is executed (true && false). || evaluates false and since there's a false on the left hand side, echo pass gets executed (false || echo pass). So far so good.

Example 2

false && false || echo pass
# pass

Since the first expression is false, && does not execute the second false. However, echo pass gets printed because the left hand side of false || echo pass is false. All is good so far.

Example 3

[[ 2 -gt 3 ]] && echo t || echo f
# f

2 is not greater than 3, meaning that echo t doesn't get executed. However, echo t || echo f prints f. Based on the previous two examples, echo t should return a non-exit code and don't execute echo f on the right hand side.

What am I missing?

4
  • 3
    Have a look at Bash Pitfall 22. Commented Aug 20, 2020 at 13:26
  • Also, are you saying echo t || echo f on its own prints f? It prints t. Commented Aug 20, 2020 at 13:27
  • 3
    You forgot the case false && true || echo pass which I think would invalidate the misconception you're having. Commented Aug 20, 2020 at 13:32
  • 1
    As a general rule, don't combine && and || in a single list (at least, not without explicit grouping with { ... } and proper guarding of the exit status for the "if" block). Use an if statement instead.
    – chepner
    Commented Aug 20, 2020 at 13:35

2 Answers 2

6
  1. The overall general rule is: any expression has the exit code of the last command executed. [*]
  2. Grouping. a && b || c is equal to ( a && b ) || c, ie. the left side is one big expression. && and || have equal precedence, they are executed from left to right.

The last command executed in [[ 2 -gt 3 ]] && echo t is [[ 2 -gt 3 ]] and it returns nonzero. So the exit status of the whole [[ 2 -gt 3 ]] && echo t expression is nonzero - the exit status of last command executed.

[[ 2 -gt 3 ]] && echo t || echo f
( [[ 2 -gt 3 ]] && echo t ) || echo f
( false && echo t ) || echo f
( false ) || echo f
echo f

[*] - The rule is for any command that is in a list of commands ( ) { } && || and also in compound constructs while if case etc. You can do funny stuff like if case "$line" in a) false; ;; esac; then echo "line is not a"; fi. The exit status of case is equal the exit status of the last command executed, which is false in case line matches a).

-1

The '&&' and '||' operators do not always execute the second operand. The shell will not execute the second operand if the result of the whole expression.

When evaluating 'cmd1 && cmd2', if 'cmd1' fails, 'cmd2' is not executed and the result is a failure. Otherwise, the result is the result of executing 'cmd2'.

Similarly, when evaluating 'cmd1 || cmd2', if 'cmd1' succeeds, 'cmd2' is not executed and the result is success. Otherwise, the result is the result of executing 'cmd2'.

When multiple operations are chained together, start with the left most pair and evaluate them according to the above two rules. Then replace the left most pair with the result and repeat. For example:

To run multiple commands, but stop and return an error upon the first failure:

cmd1 && cmd2 && cmd3 || echo "Failed."

This is equivalent to

( ( cmd1 && cmd2 ) && cmd3 ) || echo "Failed."

If cmd1 fails, cmd2 is not executed the first pair of commands fails. Therefore cmd3 is not executed and the left hand side of the '||' operator is a failure. Which means the echo command has to be executed.

Alternatively, if cmd1, cmd2 and cmd3 all succeed, then the left hand side of the '||' operator is successful and so the echo command is not executed.

3
  • 1
    I don't think $( $( cmd1 && cmd2 ) && cmd3 ) || echo "Failed." is right. I don't think you want the dollar signs. That would cause the output of the inner commands to be run as commands themselves. It should be ( ( cmd1 && cmd2 ) && cmd3 ) || echo "Failed." or even { { cmd1 && cmd2 } && cmd3 } || echo "Failed.", if you don't want to use subshells. Commented Jan 13, 2023 at 20:22
  • Yes. The subshells are unnecessary. I updated the answer. Commented Jan 14, 2023 at 21:46
  • 1
    You're still using subshells. You have to use braces instead of parentheses if you don't want to use subshells. Commented Jan 17, 2023 at 13:32

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