How can I write if/else with Boolean in Bash? [duplicate] - bash

This question already has answers here:
How can I declare and use Boolean variables in a shell script?
(25 answers)
Closed 5 years ago.
How can I write an 'if then' statement to switch between these to variables as in the following?
if(switch){
server_var_shortname=$server_shared_shortname
server_var=$server_shared
server_var_bare=$server_shared_bare
} else {
server_var_shortname=$server_vps_shortname
server_var=$server_vps
server_var_bare=$server_vps_bare
}
I'm not familiar with Bash syntax and basically just need an 'if/else' statement on a Boolean. Also, can I use true / false values as such? Also how do I do the 'else' statement?
$switch=true;
if $switch
then
server_var_shortname=$server_shared_shortname
server_var=$server_shared
server_var_bare=$server_shared_bare
fi

First, shells (including Bash) don't have Booleans; they don't even have integers (although they can sort of fake it). Mostly, they have strings.
Bash also has arrays... of strings. There are a number of ways of faking Booleans; my favorite is to use the strings "true" and "false". These also happen to be the names of commands that always succeed and fail respectively, which comes in handy, because the if statement actually takes a command, and runs the then clause if it succeeds and the else clause if it fails. Thus, you can "run" the Boolean, and it'll succeed if set to "true" and fail if set to "false". Like this:
switch=true # This doesn't have quotes around it, but it's a string anyway.
# ...
if $switch; then
server_var_shortname=$server_shared_shortname
server_var=$server_shared
server_var_bare=$server_shared_bare
else
server_var_shortname=$server_vps_shortname
server_var=$server_vps
server_var_bare=$server_vps_bare
fi
Note that the more usual format you'll see for if has square-brackets, like if [ something ]; then. In this case, [ is actually a command (not some funny sort of grouping operator) that evaluates its argument as an expression; thus [ "some string" = "some other string" ] is a command that will fail because the strings aren't equal. You could use if [ "$switch" = true ]; then, but I prefer to cheat and use the fake Boolean directly.
Caveat: if you do use the cheat I'm suggesting, make sure your "Boolean" variable is set to either "true" or "false" -- not unset, not set to something else. If it's set to anything else, I take no responsibility for the results.
Some other syntax notes:
Use $ on variables when fetching their values, not when assigning to them. You have $switch=true; up there, which will get you an error.
Also, you have a semicolon at the end of that line. This is unnecessary; semicolons are used to separate multiple commands on the same line (and a few other places), but they aren't needed to end the last (/only) command on a line.
The [ command (which is also known as test) has a kind of weird syntax. Mostly because it's a command, so it goes through the usual command parsing, so e.g. [ 5 > 19 ] is parsed as [ 5 ] with output sent to a file named "19" (and is then true, because "5" is nonblank). [ 5 ">" 19 ] is better, but still evaluates to true because > does string (alphabetical) comparisons, and "5" is alphabetically after "19". [ 5 -gt 19 ] does the expected thing.
There's also [[ ]] (similar, but cleaner syntax and not available in all shells) and (( )) (for math, not strings; also not in all shells). See Bash FAQ #31.
Putting commands in variables is generally a bad idea. See Bash FAQ #50.
shellcheck.net is your friend.

Bash doesn't have any concept of Boolean - there are no true / false values. The construct
[ $switch ]
will be true except when switch variable is not set or is set to an empty string.
[ ] && echo yes # Nothing is echoed
[ "" ] && echo yes # Nothing is echoed
unset switch && [ $switch ] && echo yes # Nothing is echoed
switch=1 && [ $switch ] && echo yes # 'yes' is echoed
switch=0 && [ $switch ] && echo yes # 'yes' is echoed - the shell makes no distinction of contents - it is true as long it is not empty
See also:
How can I declare and use Boolean variables in a shell script?

Here is a good guide for If else. But I want to show a different approach (which you will find also in the link on page 3).
Your coding looks like JavaScript, so I think with Switch you could also mean the case command instead of if. Switch in JavaScript is similar to case within a shell, but there isn't any method to check for Booleans. You can check string values for like true and false, and you can check for numbers.
Example...
#!/bin/bash
case "$Variable" in
false|0|"")
echo "Boolean is set to false."
;;
*)
echo "Boolean is set to true."
;;
esac
Addition
Keep in mind, there are many programs and tools that uses Boolean values in different forms.
Two examples...
SQL in general uses numbers as Boolean.
JavaScript uses true and false values.
Meaning: Your Bash script has to know the format of Booleans, before processing them!

You need something like this:
if
CONDITION_SEE_BELOW
then
server_var_shortname=$server_shared_shortname
server_var=$server_shared
server_var_bare=$server_shared_bare
else
server_var_shortname=$server_vps_shortname
server_var=$server_vps
server_var_bare=$server_vps_bare
fi
In Bash (and other shells), the CONDITION_SEE_BELOW part has to be a command. A command returns a numerical value, and by convention 0 means "true" and any non-zero value means "false". The then clause will execute if the command returns 0, or the else clause in all other cases. The return value is not the text output by the command. In shells, you can access it with the special variable expansion $? right after executing a command.
You can test that with commands true and false, which do one thing: generate a zero (true) and non-zero (false) return value. Try this at the command line:
true ; echo "true returns $?"
false ; echo "false returns $?"
You can use any command you want in a condition. There are two commands in particular that have been created with the idea of defining conditions: the classic test command [ ] (the actual command only being the opening bracket, which is also available as the test command), and the double-bracketed, Bash-specific [[ ]] (which is not technically a command, but rather special shell syntax).
For instance, say your switch variable contains either nothing (null string), or something (string with at least one character), and assume in your case you mean a null string to be "false" and any other string to be "true". Then you could use as a condition:
[ "$switch" ]
If you are OK with a string containing only spaces and tabs to be considered empty (which will happen as a result of standard shell expansion and word splitting of arguments to a command), then you may remove the double quotes.
The double-bracket test command is mostly similar, but has some nice things about it, including double-quoting not being needed most of the time, supporting Boolean logic with && and || inside the expression, and having regular expression matching as a feature. It is, however a Bashism.
You can use this as a reference to various tests that can be performed with both test commands:
6.4 Bash Conditional Expressions
If at all interested in shell programming, be sure to find out about the various tests you can use, as you are likely to be using many of them frequently.

As addition to Gordon's excellent answer, in Bash you can also use the double-parentheses construct. It works for integers, and it is the closest form to other languages. Demo:
for i in {-2..2}; do
printf "for %s " "$i"
if (( i )) # You can omit the `$`
then
echo is nonzero
else
echo is zero
fi
done
Output:
for -2 is nonzero
for -1 is nonzero
for 0 is zero
for 1 is nonzero
for 2 is nonzero
You can use any arithmetic operations inside, e.g.:
for i in {1..6}; do
printf "for %s " "$i"
if (( i % 2 )) #modulo
then
echo odd
else
echo even
fi
done
Output
for 1 odd
for 2 even
for 3 odd
for 4 even
for 5 odd
for 6 even

Related

Conditional on non-instantiated variable

I am new to Bash scripting, having a lot more experience with C-type languages. I have written a few scripts with a conditional that checks the value of a non-instantiated variable and if it doesn't exist or match a value sets the variable. On top of that the whole thing is in a for loop. Something like this:
for i in ${!my_array[#]}; do
if [ "${my_array[i]}" = true ]
then
#do something
else
my_array[i]=true;
fi
done
This would fail through a null pointer in Java since my_array[i] is not instantiated until after it is checked. Is this good practice in Bash? My script is working the way I designed, but I have learned that just because a kluge works now doesn't mean it will work in the future.
Thanks!
You will find this page on parameter expansion helpful, as well as this one on conditionals.
An easy way to test a variable is to check it for nonzero length.
if [[ -n "$var" ]]
then : do stuff ...
I also like to make it fatal to access a nonexisting variable; this means extra work, but better safety.
set -u # unset vars are fatal to access without exception handling
if [[ -n "${var:-}" ]] # handles unset during check
then : do stuff ...
By default, referencing undefined (or "unset") variable names in shell scripts just gives the empty string. But is an exception: if the shell is run with the -u option or set -u has been run in it, expansions of unset variables are treated as errors and (if the shell is not interactive) cause the shell to exit. Bash applies this principle to array elements as well:
$ array=(zero one two)
$ echo "${array[3]}"
$ echo "array[3] = '${array[3]}'"
array[3] = ''
$ set -u
$ echo "array[3] = '${array[3]}'"
-bash: array[3]: unbound variable
There are also modifiers you can use to control what expansions do if a variable (or array element) is undefined and/or empty (defined as the empty string):
$ array=(zero one '')
$ echo "array[2] is ${array[2]-unset}, array[3] is ${array[3]-unset}"
array[2] is , array[3] is unset
$ echo "array[2] is ${array[2]:-unset or empty}, array[3] is ${array[3]:-unset or empty}"
array[2] is unset or empty, array[3] is unset or empty
There are a bunch of other variants, see the POSIX shell syntax standard, section 2.6.2 (Parameter Expansion).
BTW, you do need to use curly braces (as I did above) around anything other than a plain variable reference. $name[2] is a reference to the plain variable name (or element 0 if it's an array), followed by the string "[2]"; ${name[2]}, on the other hand, is a reference to element 2 of the array name. Also, you pretty much always want to wrap variable references in double-quotes (or include them in double-quoted strings), to prevent the shell from "helpfully" splitting them into words and/or expanding them into lists of matching files. For example, this test:
if [ $my_array[i] = true ]
is (mostly) equivalent to:
if [ ${my_array[0]}[i] = true ]
...which isn't what you want at all. But this one:
if [ ${my_array[i]} = true ]
still doesn't work, because if my_array[i] is unset (or empty) it'll expand to the equivalent of:
if [ = true ]
...which is bad test expression syntax. You want this:
if [ "${my_array[i]}" = true ]

Is there an elegant way to store and evaluate return values in bash scripts?

I have a rather complex series of commands in bash that ends up returning a meaningful exit code. Various places later in the script need to branch conditionally on whether the command set succeed or not.
Currently I am storing the exit code and testing it numerically, something like this:
long_running_command | grep -q trigger_word
status=$?
if [ $status -eq 0 ]; then
: stuff
else
: more code
if [ $status -eq 0 ]; then
: stuff
else
For some reason it feels like this should be simpler. We have a simple exit code stored and now we are repeatedly typing out numerical test operations to run on it. For example I can cheat use the string output instead of the return code which is simpler to test for:
status=$(long_running_command | grep trigger_word)
if [ $status ]; then
: stuff
else
: more code
if [ $status ]; then
: stuff
else
On the surface this looks more straight forward, but I realize it's dirty.
If the other logic wasn't so complex and I was only running this once, I realize I could embed it in place of the test operator, but this is not ideal when you need to reuse the results in other locations without re-running the test:
if long_running_command | grep -q trigger_word; then
: stuff
else
The only thing I've found so far is assigning the code as part of command substitution:
status=$(long_running_command | grep -q trigger_word; echo $?)
if [ $status -eq 0 ]; then
: stuff
else
Even this is not technically a one shot assignment (although some may argue the readability is better) but the necessary numerical test syntax still seems cumbersome to me. Maybe I'm just being OCD.
Am I missing a more elegant way to assign an exit code to a variable then branch on it later?
The simple solution:
output=$(complex_command)
status=$?
if (( status == 0 )); then
: stuff with "$output"
fi
: more code
if (( status == 0 )); then
: stuff with "$output"
fi
Or more eleganter-ish
do_complex_command () {
# side effects: global variables
# store the output in $g_output and the status in $g_status
g_output=$(
command -args | commands | grep -q trigger_word
)
g_status=$?
}
complex_command_succeeded () {
test $g_status -eq 0
}
complex_command_output () {
echo "$g_output"
}
do_complex_command
if complex_command_succeeded; then
: stuff with "$(complex_command_output)"
fi
: more code
if complex_command_succeeded; then
: stuff with "$(complex_command_output)"
fi
Or
do_complex_command () {
# side effects: global variables
# store the output in $g_output and the status in $g_status
g_output=$(
command -args | commands
)
g_status=$?
}
complex_command_output () {
echo "$g_output"
}
complex_command_contains_keyword () {
complex_command_output | grep -q "$1"
}
if complex_command_contains_keyword "trigger_word"; then
: stuff with "$(complex_command_output)"
fi
If you don't need to store the specific exit status, just whether the command succeeded or failed (e.g. whether grep found a match), I's use a fake boolean variable to store the result:
if long_running_command | grep trigger_word; then
found_trigger=true
else
found_trigger=false
fi
# ...later...
if ! $found_trigger; then
# stuff to do if the trigger word WASN'T found
fi
#...
if $found_trigger; then
# stuff to do if the trigger WAS found
fi
Notes:
The shell doesn't really have boolean (true/false) variables. What's actually happening here is that "true" and "false" are stored as strings in the found_trigger variable; when if $found_trigger; then executes, it runs the value of $found_trigger as a command, and it just happens that the true command always succeeds and the false command always fails, thus causing "the right thing" to happen. In if ! $found_trigger; then, the "!" toggles the success/failure status, effectively acting as a boolean "not".
if long_running_command | grep trigger_word; then is equivalent to running the command, then using if [ $? -ne 0 ]; then to check its exit status. I find it a little cleaner, but you have to get used to thinking of if as checking the success/failure of a command, not just testing boolean conditions. If "active" if commands aren't intuitive to you, use a separate test instead.
As Charles Duffy pointed out in a comment, this trick executes data as a command, and if you don't have full control over that data... you don't have control over what your script is going to do. So never set a fake-boolean variable to anything other than the fixed strings "true" and "false", and be sure to set the variable before using it. If you have any nontrivial execution flow in the script, set all fake-boolean variables to sane default values (i.e. "true" or "false") before the execution flow gets complicated.
Failure to follow these rules can lead to security holes large enough to drive a freight train through.
Why don't you set flags for the stuff that needs to happen later?
cheeseballs=false
nachos=false
guppies=false
command
case $? in
42) cheeseballs=true ;;
17 | 31) cheeseballs=true; nachos=true; guppies=true;;
66) guppies=true; echo "Bingo!";;
esac
$cheeseballs && java -crash -burn
$nachos && python ./tex.py --mex
if $guppies; then
aquarium --light=blue --door=hidden --decor=squid
else
echo SRY
fi
As pointed out by #CharlesDuffy in the comments, storing an actual command in a variable is slightly dubious, and vaguely triggers Bash FAQ #50 warnings; the code reads (slightly & IMHO) more naturally like this, but you have to be really careful that you have total control over the variables at all times. If you have the slightest doubt, perhaps just use string values and compare against the expected value at each junction.
[ "$cheeseballs" = "true" ] && java -crash -burn
etc etc; or you could refactor to some other implementation structure for the booleans (an associative array of options would make sense, but isn't portable to POSIX sh; a PATH-like string is flexible, but perhaps too unstructured).
Based on the OP's clarification that it's only about success v. failure (as opposed to the specific exit codes):
long_running_command | grep -q trigger_word || failed=1
if ((!failed)); then
: stuff
else
: more code
if ((!failed)); then
: stuff
else
Sets the success-indicator variable only on failure (via ||, i.e, if a non-zero exit code is returned).
Relies on the fact that variables that aren't defined evaluate to false in an arithmetic conditional (( ... )).
Care must be taken that the variable ($failed, in this example) hasn't accidentally been initialized elsewhere.
(On a side note, as #nos has already mentioned in a comment, you need to be careful with commands involving a pipeline; from man bash (emphasis mine):
The return status of a pipeline is the exit status of the last command,
unless the pipefail option is enabled. If pipefail is enabled, the
pipeline's return status is the value of the last (rightmost) command
to exit with a non-zero status, or zero if all commands exit successfully.
To set pipefail (which is OFF by default), use set -o pipefail; to turn it back off, use set +o pipefail.)
If you don't care about the exact error code, you could do:
if long_running_command | grep -q trigger_word; then
success=1
: success
else
success=0
: failure
fi
if ((success)); then
: success
else
: failure
fi
Using 0 for false and 1 for true is my preferred way of storing booleans in scripts. if ((flag)) mimics C nicely.
If you do care about the exit code, then you could do:
if long_running_command | grep -q trigger_word; then
status=0
: success
else
status=$?
: failure
fi
if ((status == 0)); then
: success
else
: failure
fi
I prefer an explicit test against 0 rather than using !, which doesn't read right.
(And yes, $? does yield the correct value here.)
Hmm, the problem is a bit vague - if possible, I suggest considering refactoring/simplify, i.e.
function check_your_codes {
# ... run all 'checks' and store the results in an array
}
###
function process_results {
# do your 'stuff' based on array values
}
###
create_My_array
check_your_codes
process_results
Also, unless you really need to save the exit code then there is no need to store_and_test - just test_and_do, i.e. use a case statement as suggested above or something like:
run_some_commands_and_return_EXIT_CODE_FROM_THE_LAST_ONE
if [[ $? -eq 0 ]] ; then do_stuff else do_other_stuff ; fi
:)
Dale

Compare a Bash string literal to a local variable

#!/bin/bash
function getComment(){
local lang=$1;
local theComment=$2;
if [$lang == "Java"] #Surprisingly, an error occurs here: prog.sh: line 6: [Java: command not found
then
echo "//"$theComment; return;
else
echo "Language not found!"; return;
fi
}
getComment "Java" "Whoo!";
exit $?
I'm writing a Bash script that compares a variable to a string literal, and I'm using [$lang == "Java"] (as shown above) to compare the value of lang to "Java". However, this comparison produces the following error:
stderr:
prog.sh: line 6: [Java: command not found
I've tried using [$lang -eq "Java"] and ($lang -eq "Java") as well, but those statements didn't work either, and they produced exactly the same error.
Why is this error occurring, and what is the correct way to compare a local variable to a string literal?
You need spaces around [ and ]:
if [ "$lang" = "Java" ]
[ is a command (it's a synonym for test), and like any other command you delimit the parameters with spaces.
You should also put variables in double quotes, in case the value is empty or contains whitespace or wildcard characters.
Finally, the operator to perform string comparison is =, although some versions of test allow == as an extension.
First, you have to enclose the variable between double quotes, because the variable could have some spaces or special characters.
Finally remember that "[" it's an executable by itself (usually is in /bin).
if [ "$lang" == "Java" ]; then
First thing is, don't use [ ] - it's better to use [[.
And second - you need to add some spaces:
if [[ $lang == Java ]]

Shell script test

I'm tring to update a bash script written by someone else and I've come accross a line I'm not sure about.
Can anyone tell me what the following check does:
if [ :$RESULT != :0,0 ]
I assume it's checking for some value in $RESULT, possibly with a substring?
Any help appreciated!
The command [ is just an alias of the command test, the closing square bracket just being sytax sugar (the command [ ignores the last argument if it's a closing bracket), so the line actually reads
if test :$RESULT != :0,0
It compares if the string :$RESULT equals to the string :0,0. The colon is prepended for the case that the variable $RESULT is empty. The line would look like the following if the colon was omitted and $RESULT was an empty string:
if test != 0,0
This would lead to an error, since test expects an argument before !=. An alternative would be to use quotes to indicate that there is an argument, which is an empty string:
if test "$RESULT" != 0,0
# Will become
if test "" != 0,0
The variation you posted is more portable, though.
I think the : is a common trick people use in case the variable is empty.
If it's empty, then the shell would have this:
if [ != 0,0 ]
which would be a syntax error. Putting the : in front means that if the variable is empty the shell has this:
if [ : != :0,0 ]
which is not a syntax error and would (correctly) report false.
Sometimes you'll see an x used in the way that the colon is used in your example.
The preferred way to do this type of test in Bash is to use the double square bracket:
if [[ $RESULT != 0,0 ]]
The double bracket form allows more flexibility, improved readability, reduced need for escaping and quoting and a few more features. See this page for more information.
If you want to test numeric values, instead of strings or files, use the double parentheses:
if (( 3 + 4 > 6 ))

BASH: Assign '&' to variable NOT as string

I wanted to conditionally run a command as a background or foreground process, so I wrote something like this:
test $some_var; bg_suffix=&
long_command $bg_suffix
it doesn't work because bg_suffix is always empty whether it's been assigned or not.
But
test $some_var; bg_suffix="&"
long_command $bg_suffix
doesn't work either because now bg_suffix is interpreted as a string.
Any ideas how to solve this problem? Thanks!
Here is how to do it without using a quote-breaking eval
inBackground () {
t=$1
shift
if $t; then
"$#"&
else
"$#"
fi
}
This lets you do something like:
inBackground false echo '$$'
inBackground true sleep 4
This gets around the problem that all the eval-based solutions have: new and sometimes impossible quoting rules. For example, try to pass the '$$' through eval. Because true and false are not significant to the parser they can be in variables and things will still work.
Of course, if you wanted shell metachars to work (say, you redirect i/o) then eval is better, or you need to define a procedure for the command, and if you define a procedure, you problem is solved:
complicated_command () {
sleep 3
echo replace this with something complex
}
do_background=true
$do_background && (complicated_command&) || complicated_command
How about:
if [[ ${somevar} ]] ; then
long_command &
else
long_command
fi
or, if it is a long command you don't want to have to enter twice:
long_command=insert your big honking command here
if [[ ${somevar} ]] ; then
${long_command} &
else
${long_command}
fi
Just as an aside, I hope you're aware that the command sequence:
test ${condition}; x=2
will set x to 2 regardless of the test results. You may have meant to write:
test ${condition} && x=2
did you try
eval (long_command $bg_suffix)
using bg_suffix="&"
I don't know why I am not able comment, but anyway
test $some_var; bg_suffix="&"
would cause bg_suffix to be set regardless of the result of test.

Resources