bash [[ for numerical comparison? - bash

We have some scripts that do things like
e=$?
if [[ $e == 123 ]]; then exit 1; fi
They're more complicated than that, it's just an example. My question is using double brackets acceptable to make numerical comparisons this way, is there any disadvantage? I would think it should be double parentheses if (( $e == 123 )) but I don't want to go changing a lot of scripts over nothing.
Thanks

There are a lot of key differences doing it, because == checks for exact string equality, but -eq evaluates both expressions arithmetically before checking for equality.
$ [[ " 1 " -eq 1 ]] && echo equal || echo not
equal
$ (( " 1 " == 1 )) && echo equal || echo not
equal
$ [[ " 1 " = 1 ]] && echo equal || echo not
not
Also, the empty string happens to be numerically equal to zero:
$ [[ "" -eq 0 ]] && echo equal || echo not
equal
$ [[ "" == 0 ]] && echo equal || echo not
not
And a whole other class of differences appears when you bring the comparison operators in - considering < vs -lt, for instance:
$ [[ 2 -lt 10 ]] && echo less || echo not
less
$ (( 2 < 10 )) && echo less || echo not
less
$ [[ 2 < 10 ]] && echo less || echo not
not
This is because the string 2 is alphabetically after the string 10 (since 1 comes before 2), but the number 2 is numerically less than the number 10.
Credits to the original cross site duplicate, with a few updates Is there any major difference when comparing a variable as a string or as an int?
The verdict is to use $((..)) for arithmetic comparisons strictly to avoid interpreting the operands as strings.

Related

Bash nested conditionals show unexpected behavior

please take a look at this:
This should echo X10, but echoes jackpot... can anyone see why it doesn't behave as it should?
Probably just some mistake I made that do not throw errors?
dice1=1
dice2=40
#These two lines are just tests to see if my brain still function:
echo "Is dice 1 less than 2? $(($dice1 < 2))"
echo "Is dice 2 between 6 and 54? $(($dice2 > 5 && $dice2 < 55))"
if [[ $dice1 == 1 ]]
then
if [[ $dice2 < 6 ]]
then
#dice1 has to be equal 1 and dice2 less than 6:
echo "jackpot"
else
#Since dice2 is larger than 5, if smaller than 55
#it should be between 6 and 54...
if [[ $dice2 < 55 ]]
then
echo "X10"
else
echo "X5"
fi
fi
else
echo "Dice one is not equal 1."
fi
When used with [[, the < and > operators sort lexicographically using the current locale.
I see two options.
Do the comparison in arithmetic context:
if (( $dice1 == 1 ))
then
if (( $dice2 < 6 ))
or the old-fashioned way:
if [[ $dice1 -eq 1 ]]
then
if [[ $dice2 -lt 6 ]]

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

Bash programming: evaluation in conditional statements

In bash scripting the if condition statement is not working properly with using "&&"
ARGCOUNT=$#
if (( "$ARGCOUNT" != "2" )) ;then
echo "number of arguments must be two"
fi
DFLAG=$1
HFLAG=$2
if (((( $DFLAG = "Mon" )) || (( $DFLAG = "MON" )) || (( $DFLAG = "mon" ))) && ((( HFLAG = "2" )) || (( HFLAG = "3" )) || (( HFLAG = "4" ))));then
echo " CS599 "
cd CS599
elif (((( $DFLAG = "Wed" )) || (( $DFLAG = "WED" )) || (( $DFLAG = "wed" ))) && ((( HFLAG = "2" )) || (( HFLAG = "3" )) || (( HFLAG = "4" ))));then
cd CS699
echo " CS699 "
elif (((( $DFLAG = "Fri" )) || (( $DFLAG = "FRI" )) || (( $DFLAG = "fri" ))) && ((( HFLAG = "2" )) || (( HFLAG = "3" )) || (( HFLAG = "4" ))));then
cd CS799
echo " CS799 "
else
echo "."
fi
my program is executing only else statement irrespective of arguments. means it evaluating if block false.
What is the problem ?
The parenthesis you use are for arithmetic evaluation. I think you are over using them, and it makes your script complicated.
This snippet below does work:
#!/bin/bash
ARGCOUNT=$#
if [ "$ARGCOUNT" -ne 2 ] ;then echo "number of arguments must be two"; fi
# put DFLAG in lower case (see man bash).
DFLAG=${1,,}
HFLAG=$2
if [ "$DFLAG" = 'mon' -a "$HFLAG" -ge 2 -a "$HFLAG" -le 4 ]; then
echo ok
else
echo failed
fi
As you can see, I optimized your expression:
Except for the case of $ARGCOUNT (which is safe because you initialized it to $#), don't forget to encase variable with double quote to avoid expansion.
In the declaration of DFLAG, I used the convert to lower case string operator (?). With that you won't have to check for each permutation of case in DFLAG. This might not work in bash3.
If you use the test or [ builtin, you can use -a between each expression to do a and.
Arithmetic evaluation with the test/[ builtin use the following operators: -ne (inequality), -eq (equality)-ge (greater or equal), -le (lesser or equals), -lt (lesser), -gt (greater).
As said in another answer, you can replace "$DFLAG" = 'mon' by "$DFLAG" == 'mon'. But this is not POSIX conformant (as said in my comment below) and I'm not enough knowledgeable on that to know if it's a good idea or not.
On a side note, if $HFLAG condition should always be the same, you can write your code like this:
if [ "$HFLAG" -ge 2 -a "$HFLAG" -le 4 ]; then
case "$DFLAG" in
mon|Mon|MON)
echo "monday";
;;
fry|Fry|FRY)
echo "friday";
;;
*)
echo "other"
;;
esac
fi
If that case, I putted back all permutation of case in case you were in bash3, to show you an example to do without ${DFLAG,}.
If you are looking for what mistake you did which is making condition to go to else part ...then it's simple mistake which almost every programmer do once in life ... using single "=" instead of "==" during comparison. Modify it accordingly and you should get expected flow/result in your script.
One eg.
$DFLAG = "Mon"
change to below notice the double equal sign
"$DFLAG" == "Mon"
First, you should go easy on the parenthesis. Those are fragile things.
Using Bash syntax (non-POSIX, less portable), you can write:
ARGCOUNT=$#
DFLAG=${1,,} # lower case
HFLAG=$2
# don't need the quotes in (( )) as we test arithmetic value
(( $ARGCOUNT != 2 )) && echo "number of arguments must be two"
shopt -s extglob # for #(1|2|3) below, see http://mywiki.wooledge.org/glob#extglob
if [[ $DFLAG = "mon" && $HFLAG == #(2|3|4) ]]; then
echo " CS599 "
cd CS599
if [[ $DFLAG = "wed" && $HFLAG == #(2|3|4) ]]; then
cd CS699
echo " CS699 "
if [[ $DFLAG = "fri" && $HFLAG == #(2|3|4) ]]; then
cd CS799
echo " CS799 "
else
echo "."
fi
Now you see how much you repeat yourself and can improve the algorithm. For instance, test HFLAG, if valid test DFLAG otherwise …
Read
Tests And Conditionals
Arithmetic Expression
Globbing extglob

Shell equality operators (=, ==, -eq)

What is the difference between =, == and -eq in shell scripting?
Is there any difference between the following?
[ $a = $b ]
[ $a == $b ]
[ $a -eq $b ]
Is it simply that = and == are only used when the variables contain numbers?
= and == are for string comparisons
-eq is for numeric comparisons
-eq is in the same family as -lt, -le, -gt, -ge, and -ne
== is specific to bash (not present in sh (Bourne shell), ...). Using POSIX = is preferred for compatibility. In bash the two are equivalent, and in sh = is the only one that will work.
$ a=foo
$ [ "$a" = foo ]; echo "$?" # POSIX sh
0
$ [ "$a" == foo ]; echo "$?" # bash-specific
0
$ [ "$a" -eq foo ]; echo "$?" # wrong
-bash: [: foo: integer expression expected
2
(Note: make sure to quote the variable expansions. Do not leave out the double-quotes above.)
If you're writing a #!/bin/bash script then I recommend using [[ instead. The double square-brackets [[...]] form has more features, a more natural syntax, and fewer gotchas that will trip you up. For example, double quotes are no longer required around $a:
$ [[ $a == foo ]]; echo "$?" # bash-specific
0
See also:
What's the difference between [ and [[ in Bash?
It depends on the Test Construct around the operator. Your options are double parentheses, double brackets, single brackets, or test.
If you use ((…)), you are testing arithmetic equality with == as in C:
$ (( 1==1 )); echo $?
0
$ (( 1==2 )); echo $?
1
(Note: 0 means true in the Unix sense and a failed test results in a non-zero number.)
Using -eq inside of double parentheses is a syntax error.
If you are using […] (or single brackets) or [[…]] (or double brackets), or test you can use one of -eq, -ne, -lt, -le, -gt, or -ge as an arithmetic comparison.
$ [ 1 -eq 1 ]; echo $?
0
$ [ 1 -eq 2 ]; echo $?
1
$ test 1 -eq 1; echo $?
0
The == inside of single or double brackets (or the test command) is one of the string comparison operators:
$ [[ "abc" == "abc" ]]; echo $?
0
$ [[ "abc" == "ABC" ]]; echo $?
1
As a string operator, = is equivalent to ==. Also, note the whitespace around = or ==: it’s required.
While you can do [[ 1 == 1 ]] or [[ $(( 1+1 )) == 2 ]] it is testing the string equality — not the arithmetic equality.
So -eq produces the result probably expected that the integer value of 1+1 is equal to 2 even though the right-hand side is a string and has a trailing space:
$ [[ $(( 1+1 )) -eq "2 " ]]; echo $?
0
While a string comparison of the same picks up the trailing space and therefore the string comparison fails:
$ [[ $(( 1+1 )) == "2 " ]]; echo $?
1
And a mistaken string comparison can produce a completely wrong answer. 10 is lexicographically less than 2, so a string comparison returns true or 0. So many are bitten by this bug:
$ [[ 10 < 2 ]]; echo $?
0
The correct test for 10 being arithmetically less than 2 is this:
$ [[ 10 -lt 2 ]]; echo $?
1
In comments, there is a question about the technical reason why using the integer -eq on strings returns true for strings that are not the same:
$ [[ "yes" -eq "no" ]]; echo $?
0
The reason is that Bash is untyped. The -eq causes the strings to be interpreted as integers if possible including base conversion:
$ [[ "0x10" -eq 16 ]]; echo $?
0
$ [[ "010" -eq 8 ]]; echo $?
0
$ [[ "100" -eq 100 ]]; echo $?
0
And 0 if Bash thinks it is just a string:
$ [[ "yes" -eq 0 ]]; echo $?
0
$ [[ "yes" -eq 1 ]]; echo $?
1
So [[ "yes" -eq "no" ]] is equivalent to [[ 0 -eq 0 ]]
Last note: Many of the Bash specific extensions to the Test Constructs are not POSIX and therefore may fail in other shells. Other shells generally do not support [[...]] and ((...)) or ==.
== is a bash-specific alias for = and it performs a string (lexical) comparison instead of a numeric comparison. eq being a numeric comparison of course.
Finally, I usually prefer to use the form if [ "$a" == "$b" ]
Several answers show dangerous examples. The OP's example, [ $a == $b ], specifically used unquoted variable substitution (as of the October 2017 edit). For [...] that is safe for string equality.
But if you're going to enumerate alternatives like [[...]], you must inform also that the right-hand-side must be quoted. If not quoted, it is a pattern match! (From the Bash man page: "Any part of the pattern may be quoted to force it to be matched as a string.").
Here in Bash, the two statements yielding "yes" are pattern matching, other three are string equality:
$ rht="A*"
$ lft="AB"
$ [ $lft = $rht ] && echo yes
$ [ $lft == $rht ] && echo yes
$ [[ $lft = $rht ]] && echo yes
yes
$ [[ $lft == $rht ]] && echo yes
yes
$ [[ $lft == "$rht" ]] && echo yes
$

If arguments won't read

I'm writing a VERY simple bash script with an "IF" statment. I've written many before, but for some reason it will not read the "${hitA1} == 0" argument. ${hitA1} is read from a file, and it is actually reading it, and it does actually equal 0. Instead it goes to the second argument of "${hitA1} != 0". Any ideas?
Code:
CorHitA1=0
MissA1=0
for i in 44 50 53 58 71
do
late=`expr $i + 1`
hitA1=`sed -n ${i}p _TMP_Acc1.txt`
hitlateA1=`sed -n ${late}p _TMP_Acc1.txt`
if [[ ${hitA1} == 0 ]]; then
echo "HEY!!!!!"
CorHitA1=`expr ${CorHitA1} + 1`
elif [[ ${hitA1} != 0 ]]; then
echo "Nope..."
echo ${hitA1}
fi
echo "CorHitA1 = " ${CorHitA1}
done
With bash, you should use (( for arithmetic tests:
if ((hitA1 == 0)); then ...
(With arithmetic evaluation, you don't need the $ nor do you need to quote the variable.)
Or you can use the -eq operator with [[:
if [[ $hitA1 -eq 0 ]]; then ...
If you don't do one of the above, and the line in the file you're extracting has whitespace in it, then [[ $hitA1 == 0 ]] will return false because == is string equality, and a string with whitespace is not the same as a string without.

Resources