Order of execution of && and || in Bash - bash

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?

The overall general rule is: any expression has the exit code of the last command executed. [*]
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).

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.

Related

Consider a specific exit code not a failure and proceed

I found questions about ignoring errors in shell scripts, but they ignore any error, not just a specific one.
My use case is that in some cases my_command returns an exit code -3 (253), but that is not a real error and any &&-chained commands should execute. In case of a different exit code != 0, it should stop.
So basically I'm looking for something like
my_command || $(($?==253 ? true : false) && next_command
and I've looked at the different options of using ternary operators, but none of them seem to apply to my case.
What can I do?
There are some problems cmd1 || $(($?==253 ? true : false) && cmd2:
A ) is missing after false.
You don't want $(( ... )) but (( ... )). The former would execute the result of the expression (that is a number!) as a command. The latter just evaluates the expression and fails if the result is 0 and succeeds otherwise. Note that is the opposite of how exit codes work.
true and false are not commands here, but variables. If an undefined variable is used inside (( ... )) its value is always 0. Therefore the command ((... ? true : false)) always fails.
Here is want you could have written instead:
cmd1 || (($?==253 ? 1 : 0)) && cmd2
Test:
prompt$ true || (($?==253 ? 1 : 0)) && echo ok
ok
prompt$ false || (($?==253 ? 1 : 0)) && echo ok
prompt$ ( exit 253; ) || (($?==253 ? 1 : 0)) && echo ok
ok
However, the ternary operator isn't really needed here. The following would be equivalent:
cmd1 || (($?==253)) && cmd2
Despite that, an if would probably be better. If you don't want to use one inline, you can write a function for that:
# allow all exit codes given as the first argument
# multiple exit codes can be comma separated
allow() {
ok=",$1,"
shift
"$#"
[[ "$ok" = *,$?,* ]]
}
allow 0,253 cmd1 && cmd2
Or define a wrapper just for this one command
cmd1() {
command cmd1 "$#" || (( $? == 253 ))
}
cmd1 && cmd2
The code you have evaluates to a number (once you fix the missing closing parenthesis), which is not what you want; but you can refactor it slightly if you want to maintain the same sort of flow, at the expense of a subshell.
my_command || ( exit $(($?==253)) ) && next_command
Probably a less expensive and, all things counted, both more obvious and elegant solution is the trivial
my_command || [ $? == 253 ] && next_command
Um, like this?
my_command; (($?==0|$?==253)) && next_command

Why does command after `||` execute even if prior command succeeds?

I am using short-circuit evaluation (with && and ||) in a bash function,
and I don't understand the behavior I'm seeing.  I want the function to return
if the first number is not greater than the second:
[[ 5 > 2 ]] && echo true || echo false && return
# ^ ^ ^
# true so do this not this && this
[[ 5 > 8 ]] && echo true || echo false && return
# ^ ^ ^
# false so don't do this do this && this
but the function returns in either case. 
Why does the return command execute regardless of the first command's status?
Instead of return I have tried break,
but it doesn't work due to not being within a loop.
Why does return seem to execute in both these cases?
How else can I end a running function?
stmt1 && stmt2 || stmt3 && stmt4
is evaluated as
( ( stmt1 && stmt2 ) || stmt3 ) && stmt4
i.e., left to right.
So the logic is
Execute stmt1
If it succeeds,
then
execute stmt2
endif
If stmt1 succeeds and stmt2 succeeds,
then
(do nothing here)
else # i.e., if stmt1 fails, OR stmt1 succeeds and then stmt2 fails
execute stmt3
endif
If stmt1 succeeds and stmt2 succeeds,
OR stmt3 succeeds,
then
execute stmt4
endif
Since stmt2 and stmt3
are both echo statements, they both always succeed,
and so stmt4 (the return statement)
is always executed.
I suspect you were expecting
( stmt1 && stmt2 ) || ( stmt3 && stmt4 )
and you can get that behavior (in general) by typing parentheses,
just like that:
( [[ 5 > N ]] && echo true ) || ( echo false && return ) # No, don’t do this
or braces:
{ [[ 5 > N ]] && echo true; } || { echo false && return; }
Note that you must have whitespace after a {
and a semicolon before a }.
Note also that, with parentheses, the commands run in subshells,
whereas with braces, they don’t (they run in the main shell context). 
In your specific code example, you must use braces
(at least for the part after the ||),
because return doesn’t have any effect if it’s run in a subshell.
Use if instead of logical operators.
if [[ 5 > 8 ]]
then echo true
else
echo false
return
fi
See precedence of the shell logical operators for an explanation of how the operators combine.

What is the correct way to use the bash if else && || shortcut?

I have come across some strange behavious (well probably not strange but I don't understand it !)
I want to write some if / e,se statements using the bash shorthand syntax:
[[ 1 -eq 1 ]] && echo "true" || echo "false"
The output of the above code gives:
true
Now the code above actually works fine. But this next code does not:
[[ 1 -eq 1 ]] && infMsg "true" || infMsg "false"
infMsg is just a function. The output from the above code gives:
true
false
I only want it to say 'true'.
Is there something I am missing in the way the && || syntax handles exit codes from functions ?
I suspect your exit code for 'infMsg' is not 0 (success).
If we break down the first piece of code:
[[ 1 -eq 1 ]] && echo "true" || echo "false"
What's happening is:
The [[ 1 -eq 1 ]] && echo "true" portion of code is evaluated first since && has a higher precedence than || (more info[here1)
The first half of that returns true. Then, since the first half was true, the second half executes the echo "true" statement. (&& -> If part one is true, run the second part)
Now we have the || echo "false". This part actually doesn't get executed since the first half (echo "true") returned true since the echo printed successfully ((||-> If part one is false, run the second part).).
Now if we break down the second piece of code:
[[ 1 -eq 1 ]] && infMsg "true" || infMsg "false"
What's most likely happening is:
[[ 1 -eq 1 ]] && infMsg "true" is evaluated. The first side is true. Then, since the first half was true, the second half executes the infMsg "true" statement. (&& -> If part one is true, run the second part).
Now we have || infMsg "false". If the infMsg "true" from the previous command didn't return a status code that is interpreted as success (0), the first half is interpreted as false, triggering the infMsg "false"((||-> If part one is false, run the second part).)..
The important info to pull away here:
|| only runs the second half if the first half is FALSE.
&& only run the second half if the first half is TRUE.

How does AND and OR operators work in Bash?

I tried the following command on bash
echo this || echo that && echo other
This gives the output
this
other
I didn't understand that!
My dry run goes this way :
echo this || echo that && echo other implies true || true && true
Since, && has more precedence than ||, the second expression evaluates first
Since, both are true, the || is evaluated which also gives true.
Hence, I conclude the output to be:
that
other
this
Being from a Java background where && has more precedence than ||, I am not able to relate this to bash.
Any inputs would be very helpful!
From man bash
3.2.3 Lists of Commands
A list is a sequence of one or more pipelines separated by one of the operators ‘;’, ‘&’, ‘&&’, or ‘||’, and optionally terminated by one of ‘;’, ‘&’, or a newline.
Of these list operators, ‘&&’ and ‘||’ have equal precedence, followed by ‘;’ and ‘&’, which have equal precedence.
So, your example
echo this || echo that && echo other
could be read like
(this || that) && other
In bash, && and || have equal precendence and associate to the left. See Section 3.2.3 in the manual for details.
So, your example is parsed as
$ (echo this || echo that) && echo other
And thus only the left-hand side of the or runs, since that succeeds the right-hand side doesn't need to run.
Boolean evaluation in bash is short-circuit: true || false will never evaluate the false operand, because the true operand is enough to determine the outcome of the operation. Likewise, false && true will not evaluate the true operand, because it cannot change the value of the expression.
Boolean evaluation in bash is actually used mainly for controlling the conditional evaluation of the operands, not their order. Typical usage is do_foo || do_bar_if_foo_fails or do_foo && do_bar_only_if_foo_has_succeeded.
In no situation will your echo that command be executed, because the echo this is true and determines the value of the entire echo this || echo that sub-expression.
From man bash:
Of these list operators, && and || have equal precedence, followed by
; and &, which have equal precedence.
So your output is expected.
I think you've already figured it out. Operator precedence doesn't work like that in bash. Everything just goes left to right in your example.
Let's explain what this does:
echo this || echo that && echo other
echo this || echo that -> echo that only if echo this FAILS.
&& echo other -> echo other only if the command before && SUCCEEDS.
So basically:
echo this---> SUCCESS ----> echo that ----> is not executed since echo this succeeded ---> echo other ---> is executed cause echo this || echo that was executed correctly.

A way to do multiple statements per bash test && statement

Does anyone know of a way to execute multiple statements within a bash test? So if I use:
[[ $Var = 1 ]] && echo "yes-1" || echo "no-1"
And set Var=1 then output is: yes-1
If i set Var=2 then output is: no-1
And this work as I expected. But If i try to add another statement to execute in the mix and it doesn't work:
[[ $Var = 1 ]] && echo "yes-1";echo "yes-2" || echo "no-1";echo "no-2"
Which makes sense as bash sees the command ending at; but... this is not what I want.
I've tried grouping and evals and functions and have had failures and successes but I'd really just like to do is have this work on one line. Anyone have any ideas?
Simple command grouping should work; the syntax can be a little tricky though.
[[ $Var = 1 ]] && { echo "yes-1"; echo "yes-2"; } || { echo "no-1"; echo "no-2"; }
A few things to note:
Heed #tvm's advice about using an if-then-else statement if you do anything more complicated.
Every command inside the braces needs to be terminated with a semi-colon, even the last one.
Each brace must be separated from the surrounding text by spaces on both sides. Braces don't cause word breaks in bash, so "{echo" is a single word, "{ echo" is a brace followed by the word "echo".
Consider using regular IF THEN ELSE statement. Use of && and || is justified in simple test such as this:
[[ -z "$PATH" ]] && echo 'Disaster, PATH is empty!' || echo 'Everything ok!'
But, consider following command:
true && true && true && false && true || echo 'False!'
False!
OR
true && { echo true; false ; } || { echo false; true ; }
true
false
Anytime a non-zero exit status is returned, command after || is executed. As you can see, even command grouping doesn't help.
Execution in subshell behaves in similar manner:
true && ( true; echo true; true; false ) || ( true; echo true; false )
true
true
Just use regular IF, if you need proper IF behavior.
Use subshells:
$ Var=1; [[ $Var = 1 ]] && ( echo "yes-1";echo "yes-2" ) || ( echo "no-1";echo "no-2"; )
yes-1
yes-2
$ Var=2; [[ $Var = 1 ]] && ( echo "yes-1";echo "yes-2" ) || ( echo "no-1";echo "no-2"; )
no-1
no-2

Resources