Nested if else bash statement - bash

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

Related

bash if else and variable assignment

How can I add an if else in between a variable assignment?
Something like:
var="statement "[ $var2 -eq 0 ] && "is true" || "is false"
I want to avoid:
if [ $var2 -eq 0 ]; then
var="statement is true"
else
var="statement is false"
fi
Can this be done?
Thanks
Do:
[ "$var2" -eq 0 ] && var="statement is true" || var="statement is false"
Technically, var="statement is false" wil run if either [ "$var2" -eq 0 ] or var="statement is true" fails but the chance of failure of var="statement is true" is practically nearly null.
Not sure there's anyway that could be considered good coding, but here's two ways that are concise (please add a comment in your code as to what's going on, if you use these)
This way initializes your variable "var", and evaluates "var1" (also don't use these variable names ever, even in posts to SO) to see if it's a non-zero/null value. Then appends either true or false.
var="statement is "
(($var1)) && var+=true || var+=false
The other way is to change your logic, to have var set to "" (nothing) if false, and set to "true" if true. Then have an unset variable default to "false", and return the true that's assigned to it if set. Man bash, search for `:-' for more info.
var="statement is ${var1:-false}"
$(...) expressions nest inside pairs of double quotes
Therefore, you can do the following:
var="abc$( (($var2==0)) && printf '%s' ' is true' || printf '%s' ' is false')"

[[ 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'

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.

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