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

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

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.

Nested if else bash statement

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

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.

[[ test ]] || <action> format not working in bash script

Following up with a question I asked yesterday, I have a script which runs three tests and reports back on each of them.
Tom Fenech provided me with some code that is simple and should address my concerns. However, it doesn't seem to work as expected.
pass=1
[[ test1 ]] || { echo 'test1 failed'; pass=0 }
[[ test2 ]] || { echo 'test2 failed'; pass=0 }
[[ test3 ]] || { echo 'test3 failed'; pass=0 }
[[ $pass -eq 0 ]] && echo 'one of the tests failed'
Let's just work with one of tests. Suppose I have a variable and I need to compare its value to a number:
[[ ${VAR} == '128' ]] || { echo "test failed"; pass=0 }
This always results in an error:
./magic_sysrq.sh: line 64: syntax error near unexpected token `else'
./magic_sysrq.sh: line 64: `else'
For context the script contains an if...elif...else...fi block in which these tests are run. The first (if) block runs code one way depending on the version of RedHat, the second (elif) runs it another way also depending on the RedHat version. The else block just says nothing was done due to an unexpected version.
I always hit the above error with the format of the code that was provided. I can get past the error if I remove the braces. However, this always results in the tests failing regardless of successful changes.
I've tried setting the format to
[[ ${VAR} == '128' ]] || echo "test failed" || pass=0
This isn't right either. It will result in a success message even if something fails. I've tried setting the second logical operator to && but that also results in the tests failed message despite the successful changes.
Can someone shed some light on what I might be doing wrong? I suppose I could just write out all of the if...fi blocks for each test as another suggested but that would be tedious at best.
Syntax.
[[ ${VAR} == '128' ]] || { echo "test failed"; pass=0 }
...is missing a semicolon; it needs to be:
[[ ${VAR} == '128' ]] || { echo "test failed"; pass=0; }
...otherwise, the } is interpreted as an argument (or, immediately following a variable assignment as here, as a command to run with that assignment applied to the environment), leaving the { unclosed, leading to the syntax error seen.
By contrast:
[[ ${VAR} == '128' ]] || echo "test failed" || pass=0
....is wrong for a different reason: If the echo command succeeds (and an echo command failing with an error is a very uncommon occurance), it'll never proceed to run pass=0. (This is true for any language that implements short-circuiting boolean logic, not just bash).
Curly braces, unlike parentheses, are not inherently special to the shell; they're only recognized in certain positions. Most significantly, a close-brace is only recognized as terminating a code block if it's found where the shell would otherwise expect the beginning of a new statement. That means you have to insert either a newline or a semicolon before every }:
pass=1
[[ test1 ]] || { echo 'test1 failed'; pass=0; }
[[ test2 ]] || { echo 'test2 failed'; pass=0; }
[[ test3 ]] || { echo 'test3 failed'; pass=0; }
[[ $pass -eq 0 ]] && echo 'one of the tests failed'
Note that since the last test is arithmetic, you could use ((...)). For example:
(( pass )) || echo 'one of the tests failed'

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