Bash comparison - bash

I have a variable which stores the output of a command. How do I compare it with a float?
To be more specific I am doing
x=$(tail -n 1 foo| cut -d ' ' -f2)
if (($x < 0)); then ...
where foo is a filename. On doing the above I get the following error
-0.08 < 0 : syntax error: invalid arithmetic operator (error token is "0.08 < 0")
The value I need to compare is -0.08, but the error token is different
What should I do for such comparisons?

bash doesn't support floating point arithmetics.
You can however use bc which is an external program to do arithmetics.
if (( $(bc <<< "$x < 0") )); then
printf "%f is less than 0\n" "$x";
fi
from the man page:
The relational operators are
expr1 < expr2
The result is 1 if expr1 is strictly less than expr2.
expr1 <= expr2
The result is 1 if expr1 is less than or equal to expr2.
expr1 > expr2
The result is 1 if expr1 is strictly greater than expr2.
expr1 >= expr2
The result is 1 if expr1 is greater than or equal to expr2.
expr1 == expr2
The result is 1 if expr1 is equal to expr2.
expr1 != expr2
The result is 1 if expr1 is not equal to expr2.
one can also use awk that also supports floating point arithmetics.

If ksh is available to you, you can use it to write your script instead of Bash since it supports floats. Zsh also supports floats.
#!/usr/bin/ksh
x=$(tail -n 1 foo| cut -d ' ' -f2)
if ((x < 0))
then
echo "less than"
fi

Related

Why are unset and empty variables numerically equal to zero with double brackets

The following code (tested with bash, zsh, and ksh, results may vary for other shells) returns a is 0 (num). The same happens for a=''. Results for assigning a=0 or a=1 are predictable. Quoting expressions does not change the result. So, why do double brackets treat null and empty variables as being numerically equal to zero?
unset a
#a=''
#a=0
#a=1
if [[ $a == 1 ]] ; then
echo 'a is 1 (string)'
fi
if [[ "$a" == 0 ]] ; then
echo 'a is 0 (string)'
fi
if [[ $a -eq 1 ]] ; then
echo 'a is 1 (num)'
fi
if [[ "$a" -eq 0 ]] ; then
echo 'a is 0 (num)'
fi
I'm deliberately avoiding the broader issues of single and double brackets, since it's very well covered other places on this site and elsewhere. Surprisingly, I've been unable to find anything that documents this particular behavior.
Further evidence:
unset a ; if [[ $a -gt -1 ]] ; then echo 'a > -1' ; fi
unset a ; if [[ $a -lt 1 ]] ; then echo 'a < 1' ; fi
I don't believe it is explicitly documented, but it's the -eq operator that forces a quasi-arithmetic context for its operands. Consider:
$ [[ "(3 + 5)" -eq 8 ]] && echo qed
qed
The behavior for variables is documented under ARITHMETIC EVALUATION:
A shell variable that is null or unset evaluates to
0 when referenced by name without using the parameter expansion syntax.
though it's not obvious that this should also apply to a string that results from a parameter expansion, and indeed the behavior is different in an arithmetic expression or arithmetic command itself:
$ unset a
$ (( a == 0 )) && echo zero
zero
$ (( $a == 0 )) && echo zero
bash: ((: == 0 : syntax error: operand expected (error token is "== 0 ")
Given that you already have $((...)) and ((...)) available for arithmetic, it's best to avoid -eq and the other arithmetic comparison operators inside [[; either use [ "$a" -eq 0 ] (which will raise an error if a is null or unset) or use (( a == 0 ).
It's well documented in the manual: 6.5 Shell Arithmetic
Within an expression, shell variables may also be referenced by name without using the parameter expansion syntax. A shell variable that is null or unset evaluates to 0 when referenced by name without using the parameter expansion syntax.
and
A null value evaluates to 0.
Your tests are running into the 2nd case.
I can't tell you the design rationale behind it. It is handy in practice though:
unset a
(( a++ ))
echo $a # => 1
Also, other languages do the same thing:
$ awk 'BEGIN {if (unset_variable == 0) print "zero"}'
zero
$ perl -E 'if ($unset_variable == 0) {say "zero"}'
zero
So, why do double brackets treat null and empty variables as being numerically equal to zero?
From bash shell manual 6.5 Shell Arithmetic:
A shell variable that is null or unset evaluates to 0 when referenced by name without using the parameter expansion syntax
The quotes inside bash extension [[ compound command do not matter, because word splitting expansion is not performed inside [[.

Why does this if statement get evaluated to true in bash?

# prints "here" when run in bash
if [[ ((9 > 220)) ]]; then echo "here"; fi
I'm confused why the above if statement gets evaluated to true. Wouldn't ((9 > 220)) evaluate to false which would make the if statement false?
The code below behaves as expected. I'm confused why using the double parentheses in a double brackets "if" isn't working above though.
# doesn't print anything
if ((9 > 220)); then echo "here"; fi
There's a fundamental difference between those two compound commands inside a conditional construct.
[[ expression ]] compound command
In your first example you're using the [[ expression ]] command:
if [[ ((9 > 220)) ]]; then echo "here"; fi
where parenthesis are treated merely as grouping operators, used to override the normal precedence of other operators (like !, &&, ||, >, -gt, -e, etc.). The > operator in this case is a lexicographic greater than.
This is nicely described in man bash:
[[ expression ]]
Return a status of 0 or 1 depending on the evaluation of the conditional expression expression. Expressions are composed of the primaries described below under CONDITIONAL EXPRESSIONS. Word splitting and pathname expansion
are not performed on the words between the [[ and ]]; tilde expansion, parameter and variable expansion, arithmetic expansion, command substitution, process substitution, and quote removal are performed. Conditional operators such as -f must be unquoted to be recognized as primaries.
When used with [[, the < and > operators sort lexicographically using the current locale.
So, to compare integers in the [[ compound command, you can use the conditional expression operators, the same one used by test and [ commands. For example like this:
if [[ 9 -gt 220 ]]; then echo "here"; fi
The result is the same like when the -gt operator is grouped with parenthesis:
if [[ ((9 -gt 220)) ]]; then echo "here"; fi
Alternatively, you can use the arithmetic expansion and exploit the fact that the boolean results are represented as "0" or "1":
if [[ $((9 > 200)) == 1 ]]; then echo "here"; fi
(( expression )) compound command
In your second example, you're using the (( expression )) command:
if ((9 > 220)); then echo "here"; fi
where the expression is evaluated according to the rules of the shell arithmetic. The man bash says:
((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. This is exactly equivalent
to let "expression".
It is because using double brackets makes it a lexicographical comparison. That means that it checks for which sequence is longer, like sorting alphabetically. More information about lexicographical comparisons here
Inside of [[ ]], the parentheses don't trigger an arithmetic context, they're interpreted purely for grouping/precedence1. All of these are equivalent:
$ [[ 0 < 1 ]] && echo yes
yes
$ [[ (0 < 1) ]] && echo yes
yes
$ [[ ((0 < 1)) ]] && echo yes
yes
$ [[ (((0 < 1))) ]] && echo yes
yes
$ [[ ((((0 < 1)))) ]] && echo yes
yes
If we have unbalanced parentheses, Bash complains about that:
$ [[ ((((0 < 1))) ]] && echo yes
bash: unexpected token `]]', expected `)'
bash: syntax error near `]]'
This all being said, < and > within [[ ]] are for lexicographical string comparison, so the statement above just checks if 0 is lexicographically sorted before 1 (it is).
Observe:
$ [[ 11 < 2 ]] && echo yes
yes
To compare numbers, you have to use -gt, -lt, -ge, -le, eq, ne instead of >, <, >=, <=, =, !=:
$ [[ 11 -lt 2 ]] && echo yes || echo no
no
Since you're already using (( )), you could use just that for comparing numbers:
$ (( 11 < 2 )) && echo yes || echo no
no
This is the simplest and clearest method in my opinion, if you know you have Bash at your disposal.
1 See the manual about [[ ]]:
Expressions may be combined using the following operators, listed in
decreasing order of precedence:
( expression )
Returns the value of expression. This may be used to override the normal precedence of operators.
Hat tip to glenn jackman for pointing to the manual in his comment to another answer.
The double parentheses is an arithmetic context. If you use the brackets, you have to use -lt for less than and -gt for greater than. See Comparing numbers in Bash.

How to use arithmetic in an if statement for Bash

Hi Guys I am trying to perform a check in my bash script that needs to meet three conditions, I was able to make the first 2 conditions work in the if statement as I wanted, but when I wanted to implement a third check with some arithmetic using the && operator, the script does not even launch.
DOUBLE_CHECK=0
if [[ -z "$avail" && "$WIFI_ID" == "some_str" && 'expr $DOUBLE_CHECK % 2' -eq "0"]]; then
sudo caffeinate xterm -geometry 70x20+0+0 -fa monospace -fs 8 -e './script1.sh' & disown
fi
(($DOUBLE_CHECK++))
The Idea is I want the third check to have a number that increments over time inside my while loop and then checked, whenever it is divisible by 2 it passes the 3rd condition of the if statement
As noted in a separate answer, you could use backticks or preferably $() to expand the expression to the output of the inner command, like this :
if [[ -z "$avail" && $WIFI_ID == "some_str" && $(expr $DOUBLE_CHECK % 2) -eq "0" ]]
Please note I have added a (required) space before the final ]].
Another possibility is to use an arithmetic expression :
if [[ -z $avail && $WIFI_ID == "some_str" ]] && ((DOUBLE_CHECK % 2 == 0))
Please note I have removed harmless but unnecessary quotes : the double-bracketed conditional expression is not a normal command, it is special shell syntax, and it does not perform word splitting. I have left quotes around some_str, because equality/inequality comparisons will perform pattern matching on the right-hand expression if it is not quoted. There is also no $ before the variable name in the arithmetic expression : it works, but is not required inside (()) or $(()).
The expression could also be expressed as :
if [[ -z $avail && $WIFI_ID == "some_str" ]] && ! ((DOUBLE_CHECK % 2))
The reason for this is that (( )) has a return code of 0 if its numerical result is non-zero, and a non-zero return code otherwise.
There are a couple small issues
Your third condition should be evaluated using back-ticks ('`'), instead of quotes. Single-quotes in bash mean string literal. So, 'expr $DOUBLE_CHECK %2' -eq "0" will always be false
The check -eq is used to compare numbers. Your statement is using strings (to compare strings, use the normal == syntax)
Please try,
DOUBLE_CHECK=0
if [[ -z "$avail" ]] && [[ "$WIFI_ID" == "some_str" ]] && [[ `expr $DOUBLE_CHECK % 2` -eq 0 ]]; then
sudo caffeinate xterm -geometry 70x20+0+0 -fa monospace -fs 8 -e './script1.sh' & disown
fi
(($DOUBLE_CHECK++))
You can use [[ .. ]] for string logic (where = and == are equivalent) and (( ... )) for numeric comparison, and do away with expr:
if [[ -z "$avail" && "$WIFI_ID" = "some_str" ]] && (( DOUBLE_CHECK % 2 == 0 )); then
# your logic here
fi
Note that prefixing variables with $ is optional inside (( ... )) and quoting variables is optional inside [[ ... ]] since doesn't do word splitting and globbing there.

bash if statement is hit when it is passed zero i.e success

new_session should be echo'ed as setsid.but it is printing NULL
#!/bin/bash
x=0
if $x;
then
new_session="setsid"
fi
echo $new_session
The (( )) operator evaluates expressions as C arithmetic in bash, and has a Boolean return i.e. (( 0 )) is false, and (( 1 )) is true.
You might need something like below:-
#!/bin/bash
_is_linux=0
if ((_is_linux));then
new_session="setsid"
fi
(or) more simply
#!/bin/bash
_is_linux=0
((_is_linux)) && new_session="setsid"

Bash Comparing Whole number to Decimal Errors

I have a bash script in which I am attempting to compare a variable containing a whole number
VAR1=1
The real number to compare to, can be a decimal
VAR2=1.5
When I try:
if [[ $VAR1 -ge $VAR2]];
I am presented with a syntax error: invalid arithmetic operator
The problem is, when I try the >= string comparison, the result is always false irregardles of whether it logically is or not.
My question is, how can I fix this and do the arithmatic comparison?
Code Block
if [ $(bc -l <<<"$CPUUSAGE >= $MAXCPU") || $(bc -l <<<"$FREEMEM <= $MAXMEM") || $NUMHTTPD -ge $MAXHTTPD || $NUMMYSQL -ge $MAXMYSQL || $NUMPROCS -ge $MAXPROCESSES ]];
then
SendMessage;
sync ; echo 3 > /proc/sys/vm/drop_caches;
echo "Message Sent";
fi;
Bash doesn't support floating point numbers.
Try bc:
(( $(bc -l <<<"$v1 >= $v2") )) && echo "v1 is greater than or equal to v2"
I have used some bashisms here, notably the (( arithmetic context )) and <<< as an alternative to echoing the string to bc. The output of bc will be 1 or 0, depending on whether the statement is true or false. The message will only be echoed if the result is true.
The -l switch is shorthand for --mathlib, which as hek2mgl rightly asserts, is needed when working with floating point numbers.
If you want a fully-fledged if statement, you can do that as well:
if (( $(bc -l <<<"$v1 >= $v2") )); then
echo "v1 is greater than or equal to v2"
else
echo "v1 is less than v2"
fi
For the example in your question, you could use this:
if (( $(bc -l <<<"$CPUUSAGE >= $MAXCPU || $FREEMEM <= $MAXMEM") )) || [[ $NUMHTTPD -ge $MAXHTTPD || $NUMMYSQL -ge $MAXMYSQL || $NUMPROCS -ge $MAXPROCESSES ]]; then echo; fi
I've combined the two conditions in bc to save you calling the tool twice. I've also wrapped that part in an arithmetic context and used an extended test [[ for the rest.
bash does not support floating point operations. You could use bc for that:
if [ $(bc --mathlib <<< "$var1 >= $var2") = "1" ] ; then
echo "$var2 is greater than or equal to $var2"
fi
Note that unless you pass the --mathlib option, even bc would not support floating point operations.
AWK can do the trick too:
#!/bin/sh
VAR1=1
VAR2=1.5
if awk "BEGIN {exit $VAR1 >= $VAR2 ? 0 : 1}"
then
echo "$VAR1 is greater than or equal to $VAR2"
else
echo "$VAR2 is greater than or equal to $VAR1"
fi

Resources