Consider a specific exit code not a failure and proceed - bash

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

Related

Order of execution of && and || in 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.

Bash $(false) is true as a function argument

I created a function to echo T or F.
tf() {
$1 && echo "T" || echo "F"
}
It seems to work...
tf true # T
tf false # F
But then I want to use it for feedback when using the 'test' command. I pass it a test that should result in F for false:
tf $(test 4 -eq 3) # T ?
But it comes back with a T for true!
What is this? I thought $() would substitute the result (false) in its place!
So I check to see what happens without the function; everything else being equal:
$(test 4 -eq 3) && echo "T" || echo "F" # F
...and it's the correct answer; F for false. 4 does not equal 3.
So: why does command substitution of false become true when it is passed as an argument to a function?
Also I see that plain $(false) becomes true when it is passed as a function argument. But outside of a function, $(false) is correctly false:
tf $(false) # T ?
$(false) && echo "T" || echo "F" # F
How can I safely pass true-or-false-evaluating commands in to a function, and be sure that their true or false result will be dealt with correctly inside the body of the function? Is there some other formatting in which to wrap it?
Looks like you are mixing things up here.
test only returns an exit code of 0 (TRUE) or 1 (FALSE)
You can easily check it by running:
$(test 4 -eq 3)
echo $?
You are trying to provide this value to a function
tf $(test 4 -eq 3) which does not work because $(test 4 -eq 3) does not return a value, only an exit code.
When you are trying it out manually it works, because && and || operators are relying on exit code of previous command.
To make it work, you need to change the way you supply parameters to your function. For example like this (probably you'll want to split this up to a variable to make it readable):
tf $(test 4 -eq 3 && echo true || echo false)
Same happens with 'plain' $(false) as you mention below. When you execute true or false you will get an exit code of either 0 or 1. They will not make it to the function, but work well with && and || operators:
$(false) && echo "T" || echo "F"

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

Bash boolean expression and its value assignment

Is there a way to to evaluate a boolean expression and assign its value to a variable?
In most of the scripting languages there is way to evaluates e.g
//PHS
$found= $count > 0 ; //evaluates to a boolean values
I want similar way to evaluate in bash:
BOOL=[ "$PROCEED" -ne "y" ] ;
This is not working and tried other way but could not get a boolean value. IS there a way to
do this WITHOUT using IF ?
You could do:
[ "$PROCEED" = "y" ] ; BOOL=$?
If you're working with set -e, you can use instead:
[ "$PROCEED" = "y" ] && BOOL=0 || BOOL=1
BOOL set to zero when there is a match, to act like typical Unix return codes. Looks a bit weird.
This will not throw errors, and you're sure $BOOL will be either 0 or 1 afterwards, whatever it contained before.
I would suggest:
[ "$PROCEED" = "y" ] || BOOL=1
This has the advantage over checking $? that it works even when set -e is on. (See writing robust shell scripts.)
Rather than using ... && BOOL=0 || BOOL=1 suggested in the currently-accepted answer, it's clearer to use true and false.
And since this question is about bash specifically (not POSIX shell), it's also better to use [[ instead of [ (see e.g. 1 and 2), which allows using == instead of =.
So if you had to use a one-liner for something like this in bash, the following would be better:
[[ "$PROCEED" == "y" ]] && should_proceed=true || should_proceed=false
Then you can use the derived variable ergonomically in boolean contexts...
if $should_proceed; then
echo "Proceeding..."
fi
...including with the ! operator:
if ! $should_proceed; then
echo "Bye for now."
exit 0
fi
Assignment:
found=$((count > 0))
For a boolean test:
BOOL=$(test "$PROCEED" = y && echo true || echo false)
In general, a
x=$(...)
assigns the output of ... to the variable x. The y does not need quotes, because it contains nothing which needs to be masked.
A -ne is used for arithmetic comparison; see help test for an overview and quick reminder.
As explained in the accepted answer, the return value seems odd as true will return 0 and false 1. To make it easier to understand:
#!/bin/bash
test=$( [[ $1 == "y" ]]; echo $(($? == 0)) )
echo "$test"
# It will print "1", otherwise "0".
# To use it in conditions:
if [ $test ]; then
...
fi
Another way is:
test=$( [[ $1 == "y" ]] && echo "true" || echo "false" )
# In this case `[]` are not required:
if $test; then
...
fi

Resources