bash arithmetic expression: check for zero-valued variable - bash

I find this behaviour counter-intuitive:
v=0; ((v)) && echo K; v=1; ((v)) && echo J
echoes:
J
but generally 0 is success:
f() { return 0; }; if f; then echo W; fi
Explain the reasoning?
man page for (( )) says:
If the value of the expression is non-zero, the return status is 0; otherwise the return status is 1.
How best to check if a variable is zero using Arithmetic Evaluation?

The canonical way to test for 0 is [ $something -eq 0 ], often written as [ x$something = x0 ]. (Using = is a string comparison but it is still valid syntax when the variable isn't set or is otherwise blank.) See man expr.
So,
q=0
if [ $q -eq 0 ]; then # or [ x$q = x0 ]
echo yes, zero
fi
The ((expr)) syntax is a shortcut for the let command, and that command is simply defined to return true status when the expression is non-zero and false otherwise.
And neither ((...)) nor let ... are Posix commands, so some amount of WTF?! may be expected. It's just something the authors of bash either thought up or pulled in from some now-obscure earlier shell. (It seems to be in zsh.) It is not in ash(1), aka dash(1), which is kind of the reference pure-Posix shell.

One good reason to "flip" the meaning to 0 sucess any value not 0 not sucess is that you might want to indicate different error conditions.
Using 0 as failure this wouldn't be possible at all as any value != 0 would have been wasted to indicate sucess.
Given a numeric variable the best way to check if the value is 0 is to do exactly that:
((v==0))

Related

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

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

If statement with arithmetic comparison behaviour in bash

I'm learning bash and I noticed something weird I can't (yet) explain. At school I learned that an if statement evaluates 0 as true and 1 as false, so it can be used with the status code from an other command. Now is my question: Why does this happen:
echo $((5>2)) #prints 1
echo $((5<2)) #prints 0
if ((5>2)) ; then echo "yes" ; else echo "no" ; fi #prints yes
if ((5<2)) ; then echo "yes" ; else echo "no" ; fi #prints no
This doesn't seem logical. How does bash know i'm using an arithmetic expression and not another command?
You're confusing the output of the command substitution and the return value of the arithmetic context.
The output of the command substitution is 1 for true and 0 for false.
The return value of (( )) is 0 (success) if the result is true (i.e. non-zero) and 1 if it is false.
if looks at the return value, not the output of the command.
$((...)) is an arithmetic expression; it expands to the value of the expression inside the parentheses. Since it is not a command, it does not have an exit status or return value of its own. A boolean expression evaluates to 1 if it is true, 0 if it is false.
((...)), on the other hand, is an arithmetic statement. It is a command in its own right, which works be evaluating its body as an arithmetic expression, then looking at the resulting value. If the value is true, the command succeeds and has an exit status of 0. If the value is false, the command fails, and has an exit status of 1.
It's a good idea when learning bash to stop thinking of the conditions in if statements, while loops, etc. as being true or false, but rather if the commands succeed or fail. After all, shell languages are not designed for data processing; they are a glue language for running other programs.
From the bash manual:
((expression))
The expression is evaluated according to the rules described below under ARITHMETIC EVALUATION. If the value of the expression is non-zero, the return status is 0; otherwise the return status is 1. (My emphasis.)
So essentially in a boolean context ((expression)) gives the inverse of what $((expression)) would give.

Return value of function in IF statement shows strange behaviour

Can anyone explain what is going on here?
Why does an "IF" statement think the return is '1' (not Null) when i say 'Return 0' and the other way round.
I found that out while coding another script, so i developed this small script to test it:
#!/bin/bash
function testreturnzero()
{
echo $1
return 0
}
function testreturnone()
{
echo $1
return 1
}
if (testreturnzero 1) || (testreturnzero 2)
then
echo "zero returned '1'"
fi
if (testreturnone 1) || (testreturnone 2)
then
echo "one returned '1'"
fi
The IF which refers to the 'return 0' thinks its true (and doesn't process the second function), the IF which refers to 'return 1' thinks its false. Shouldn't it be the exact opposite?
1
zero returned '1'
1
2
I cant put the return value in a variable as I will have several of those checks.
In bash, a function returning 0 means success and returning a non-zero value means failure. Hence your testreturnzero succeeds and your testreturnone fails.
Does that help understanding why your ifs behave that way? (it should!).
The return code of the last executed command/function is stored in the special variable $?.
So:
testreturnzero 0
ret_testreturnzero=$?
testreturnone 1
ret_testreturnone=$?
echo "$ret_testreturnzero"
echo "$ret_testreturnone"
will output (the two last lines):
0
1
Now you may think of storing them in a variable (as here) and do your logic processing later. But there's a catch :). Because you didn't store true and false in variables, you stored 0 and 1 (bash can't store booleans in a variable). So to check success or failure later:
if ((ret_testreturnzero==0)); then
echo "testreturnzero succeeded"
fi
or
if ((ret_testreturnzero!=0)); then
echo "testreturnzero failed"
fi
In bash, the return code of a function is the same as a external program when you test the result.
So for test, a valid return code is 0 and an invalid is any other number
so, by doing
if ( testreturnone 1 ); then #it is ok
echo "error"; #it's supposed to happen, not an error
fi
You can explicitly test the value to the one you want to clear it up:
if [[ "$(testreturnzero 1)" = "1"); then #it is ok if you decide that 1 is the good value
echo "ok"; #But absolutly not the bash philosophy
fi

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

bash set -e and i=0;let i++ do not agree

the following script with debug option 'set -e -v' fails at the increment operator only when the variable has a prior value of zero.
#!/bin/bash
set -e -v
i=1; let i++; echo "I am still here"
i=0; let i++; echo "I am still here"
i=0; ((i++)); echo "I am still here"
bash (GNU bash, version 4.0.33(1)-release (x86_64-apple-darwin10) but also GNU bash, version 4.2.4(1)-release (x86_64-unknown-linux-gnu))
any ideas?
the answer to my question is not to use let (or shift, or...) but to use
i=$((i+1))
when trying to check a bash script by setting 'exit on non-zero status code' with
set -e
The bash manual states that set -e has the effect of 'Exit immediately if a simple command exits with a non-zero status.'.
Unfortunately let (and shift and ...) return the result of the computation ('If the last arg evaluates to 0, let returns 1; 0 is returned otherwise'). So instead of a status code one gets a return value of some sort. And sometimes this return value will be zero and sometimes one depending on the computation. Therefore set -e will cause the script to exit depending on the result of your computation!!! and there is nothing to do about it unless either you don't use it ever or resort to
let i++ || true
as pointed by arnaud576875 which btw adds extra CPU burden.
Using
let ++i
works only for the specific case that i is not -1, as with let i++ which works only for when i is not 0. Therefore half-solutions.
I love Unix though, I wouldn't have it any other way.
If the last argument of let evaluates to 0, let returns 1 (so, a non-zero status):
From the manual:
let arg [arg ...]
Each arg is an arithmetic expression to be evaluated. If the last arg evaluates to 0, let returns 1; 0 is returned otherwise.
i++ evaluates to zero when i is 0 (because it's a post-increment, so the previous value of i is returned), so let returns 1, and due to set -e, bash exists.
Here are some solutions:
let ++i # pre-increment, if you expect `i` to never be -1
let i++ 1 # add an expression evaluating to non-zero
let i++ || true # call true if let returns non-zero
Looking at the BASH manpage on the set -e:
Exit immediately if a simple command (see SHELL GRAMMAR above) exits with a non-zero status. [...]
So, if any statement returns a non-zero exit code, the shell will exit.
Taking a look at the BASH manpage, on the let command:
If the last arg evaluates to 0, let returns 1; 0 is returned otherwise.
But wait! The answer to i++ is a one and not a zero! It should have worked!
Again, the answer is with the BASH manpage on the increment operator:
id++ id--: variable post-increment and post-decrement
Okay, not so clear. Try this shell script:
#!/bin/bash
set -e -v
i=1; let ++i; echo "I am still here"
i=0; let ++i; echo "I am still here"
i=0; ((++i)); echo "I am still here"
Hmmm... that works as expected, and all I did was change i++ to ++i in each line.
The i++ is a post-increment operator. That means, it increments i after the let statement returns a value. Since i was zero before being incremented, the let statement returns a non-zero value.
However, the ++i is a pre-increment operator. That means it increments i before returning the exit status. Since i is incremented to a 1, the exit status becomes a zero.
I hope this makes sense.

Resources