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.
Related
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
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.
This is confusing me.
false || echo "Oops, fail" # Oops, fail
true || echo "Will not be printed" #
true && echo "Things went well" # Things went well
false && echo "Will not be printed" #
true return 0 exit code and false returns 1. I am unable to understand how || and && are working. Do both of them work on exit codes or the actual output of the command? What is the difference between && and || operator here?
The above code snippet is from the missing semester of your CS education lecture 2.
x && y executes x, then only executes y if x succeeds (i.e., has an exit status of 0)
x || y executes x, then only executes y if x fails (i.e., has a non-zero exit status)
The output of x is irrelevant.
One place they do not differ is in precedence. Unlike Boolean operators in other languages, && and || have equal precedence, so something like
a || b && c
is parsed and evaluated the same as
{ a || b; } && c
rather than
a || { b && c; }
Im trying to nest if else bash statements using [[..]] but I seem to be running into issues:
my line:
[[ $WRD == "tmp" ]] && tmpFlag=1 || [[ $someOtherVar == "test" ]] && tempFlag=2 || tempFlag=3
Basically, if WRD is tmp, then flag is 1, else if $someOtherVar is test, then the flag is 2 and if all else fails, then set the flag to 3.
But this level of nesting doesnt work.
If I provide WRD as tmp, flag is set to 2. [WRONG]
If I do not provide a WRD and $someOtherVar isn't test, then it is set to 3. [CORRECT].
If I do not provide a WRD and $someOtherVar is test, then it is set to 3. [WRONG]
Unlike boolean operators in other languages, && and || have the same precedence.
The tmpFlag=1 is treated as a statement and command and while it does not fail, there is nothing in bash that associates the || with the immediate previous term before a preceding &&, so the || is executed.
Here is a shorter snippet that makes that clear:
tempFlag=1 ||echo nooooo && echo hello
Causes the echo hello to appear, because of echo noooo not because of the success of tempFlag=1.
Also consider this:
true && echo yes || echo no && echo yeeees || echo noooooo
give you both:
yes
yeeees
Explain please why it would not be better to write this with if ... elsif .. else ... fi instead? –
Also, why not use [ ]?
I would do it like this, much clearer:
if [ "$WRD" = "tmp" ] ; then
tempFlag=1
elif [ "$someOtherVar" = "test" ] ; then
tempFlag=2
else
tempFlag=3
fi
PS, I also notice you have a typo, your first tmpFlag was supposed to be tempFlag. It doesn't change the behavior of course.
Another PS: using && and || instead of if .. elif .. fi is not really about the "[[ ]] functionality". You'd have the same problem if your were using the old test "[ ] functionality".
The test && command1 || command2 syntax does work as expected but if
you append another command with &&, it breaks due to the operator precedence.
As a workaround, you can make compound commands as command2 by surrounding
them with { ... }.
Then would you please try the following:
[[ $WRD == "tmp" ]] && tmpFlag=1 || { [[ $someOtherVar == "test" ]] && tmpFlag=2 || tmpFlag=3; }
echo "$tmpFlag"
if I provide WRD as tmp, flag is set to 1.
if I do not provide a WRD and $someOtherVar is test, then flag is set to 2.
if I do not provide a WRD and $someOtherVar isn't test, then flag is set to 3.
I'm not sure if it is readable than if .. else .. syntax but it works.
Note that you have a typo in the variable name tmpFlag.
Hope this helps.
Just use braces for each of the "primary" options:
{ [[ $WRD == "tmp" ]] && tmpFlag=1 ; } ||
{ [[ $someOtherVar == "test" ]] && tempFlag=2; } ||
tempFlag=3
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