How to handle integers in bash with values larger than 2^63 - bash

It seems like bash's maximum signed integer value is 9223372036854775807 (2^63)-1. Is there a way for bash to handle larger values than this? I need to handle numbers up to 10000000000000000000000000001, but I'm not sure how to accomplish this in bash.
A=10000000000000000000000000000
echo $A
10000000000000000000000000000
let A+=1
echo $A
4477988020393345025
EDIT
Thanks Benjamin W. for your comment. Based on that I am trying the following strategy. Are there any perceived issues with this? Meaning, aside from some performance hit due to invoking bc, would there by complications from using bc to increment my variable?
A=10000000000000000000000000000
echo $A
10000000000000000000000000000
A=$(bc <<< "$A+1")
echo $A
10000000000000000000000000001
Also, I've tested some bash operations (greater than, less than, etc) and it seems it behaves as expected. E.g.:
A=10000000000000000000000000000
echo $A
10000000000000000000000000000
[[ "$A" -gt 10000000000000000000000000000 ]] && echo "A is bigger than 10000000000000000000000000000"
A=$(bc <<< "$A+1")
echo $A
10000000000000000000000000001
[[ "$A" -gt 10000000000000000000000000000 ]] && echo "A is bigger than 10000000000000000000000000000"
A is bigger than 10000000000000000000000000000

I'd recommend using bc with its arbitrary precision.
Bash overflows at 263:
$ A=$(( 2**63 - 1 ))
$ echo $A
9223372036854775807
$ echo $(( A+1 ))
-9223372036854775808
bc can handle this:
$ bc <<< "$A+1"
9223372036854775808
These numbers have to be handled with bc for everything from now on, though. Using [[ ]], they don't seem to overflow, but comparison doesn't work properly:
$ B=$(bc <<< "$A+1")
$ echo $B
9223372036854775808
$ set -vx
$ [[ $B -gt -$A ]] && echo true
[[ $B -gt -$A ]] && echo true
+ [[ 9223372036854775808 -gt -9223372036854775807 ]]
And in arithmetic context (( )), they overflow:
$ echo $(( B ))
-9223372036854775808
so the comparison doesn't work either:
$ (( B > A )) && echo true || echo false
false
Handling them with bc:
$ bc <<< "$B > $A"
1
and since within (( )) non-zero results evaluate to true and zero to false, we can use
$ (( $(bc <<< "$B > $A") )) && echo true
true

Related

How to check if a number is between an expected range?

I have tried two methods but it did not help:
Method 1
port=$1
if [[ $port -eq 3000 -o 3100 -o 3200 -o 3300 -o 3400 -o 3500 \
-o 3600 -o 3700 -o 3800 -o 3900]];then
echo "Yes"
else
echo "No"
fi
Method 2
port=$1
if [[ $port -eq 3[0-9]00 ]];then
echo "Yes"
else
echo "No"
fi
correct answer would be replacing =~ with == because otherwise entering the number 3400000, the output would be "ok".
if [[ $a == 3[0-9]00 ]];then echo "ok";else echo "not ok";fi
Just change it to a regex compare:
[[ $port =~ ^3[0-9]00$ ]]
POSIX. You can switch to case that will recognize your initial format:
case $a in
3[0-9]00) echo "ok";;
*) echo "not ok";;
esac
bash only:
Actually the format 3[0-9]00 is a regex format, and you can make it
work even with if by replacing -eq which is used for integer
comparison to =~ which is used for regex matching:
if [[ $a =~ 3[0-9]00 ]];then echo "ok";else echo "not ok";fi
Num-utils based answer, using numgrep, (and numrange), which prints found numbers to stdout and returns an exit code.
First look for a number that isn't there:
seq 2957 19 4000 |
numgrep /`numrange -n, '/3000..3900i100/'`/ && a=y || a=n ; echo $a
Output:
n
...and now find a number that is:
seq 2957 7 4000 |
numgrep /`numrange -n, '/3000..3900i100/'`/ && a=y || a=n ; echo $a
Output:
3300
y
The (( ... )) construct permits arithmetic expansion and evaluation. In its simplest form, a=$(( 5 + 3 )) would set a to 5 + 3, or 8. However, this double-parentheses construct is also a mechanism for allowing C-style manipulation of variables in Bash, for example, (( var++ )).
Information source: http://tldp.org/LDP/abs/html/dblparens.html
#!/bin/bash
foo=100
if (( foo >= 50 && foo <= 200 ));
then
echo "bar"
fi

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

Comparisons numbers syntax in 'if' (bash) [duplicate]

I'm unable to get numeric comparisons working:
echo "enter two numbers";
read a b;
echo "a=$a";
echo "b=$b";
if [ $a \> $b ];
then
echo "a is greater than b";
else
echo "b is greater than a";
fi;
The problem is that it compares the number from the first digit on, i.e., 9 is bigger than 10, but 1 is greater than 09.
How can I convert the numbers into a type to do a true comparison?
In Bash, you should do your check in an arithmetic context:
if (( a > b )); then
...
fi
For POSIX shells that don't support (()), you can use -lt and -gt.
if [ "$a" -gt "$b" ]; then
...
fi
You can get a full list of comparison operators with help test or man test.
Like this:
#!/bin/bash
a=2462620
b=2462620
if [ "$a" -eq "$b" ]; then
echo "They're equal";
fi
Integers can be compared with these operators:
-eq # Equal
-ne # Not equal
-lt # Less than
-le # Less than or equal
-gt # Greater than
-ge # Greater than or equal
See this cheatsheet.
There is also one nice thing some people might not know about:
echo $(( a < b ? a : b ))
This code will print the smallest number out of a and b
In Bash I prefer doing this as it addresses itself more as a conditional operation unlike using (( )) which is more of arithmetic.
[[ n -gt m ]]
Unless I do complex stuff like
(( (n + 1) > m ))
But everyone just has their own preferences. Sad thing is that some people impose their unofficial standards.
You can also do this:
[[ 'n + 1' -gt m ]]
Which allows you to add something else which you could do with [[ ]] besides arithmetic stuff.
The bracket stuff (e.g., [[ $a -gt $b ]] or (( $a > $b )) ) isn't enough if you want to use float numbers as well; it would report a syntax error. If you want to compare float numbers or float number to integer, you can use (( $(bc <<< "...") )).
For example,
a=2.00
b=1
if (( $(bc <<<"$a > $b") )); then
echo "a is greater than b"
else
echo "a is not greater than b"
fi
You can include more than one comparison in the if statement. For example,
a=2.
b=1
c=1.0000
if (( $(bc <<<"$b == $c && $b < $a") )); then
echo "b is equal to c but less than a"
else
echo "b is either not equal to c and/or not less than a"
fi
That's helpful if you want to check if a numeric variable (integer or not) is within a numeric range.
One-line solution.
a=2
b=1
[[ ${a} -gt ${b} ]] && echo "true" || echo "false"
gt reference: https://www.gnu.org/software/bash/manual/html_node/Bash-Conditional-Expressions.html
&& reference: https://www.gnu.org/software/bash/manual/html_node/Shell-Arithmetic.html
[[...]] construct reference: https://www.gnu.org/software/bash/manual/bash.html#index-_005b_005b
${} reference: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02 (2.6.2)
The format for parameter expansion is as follows:
${expression}
where expression consists of all characters until the matching '}'.
Any '}' escaped by a or within a quoted string, and
characters in embedded arithmetic expansions, command substitutions,
and variable expansions, shall not be examined in determining the
matching '}'.
The simplest form for parameter expansion is:
${parameter}
This code can also compare floats. It is using AWK (it is not pure Bash). However, this shouldn't be a problem, as AWK is a standard POSIX command that is most likely shipped by default with your operating system.
$ awk 'BEGIN {return_code=(-1.2345 == -1.2345) ? 0 : 1; exit} END {exit return_code}'
$ echo $?
0
$ awk 'BEGIN {return_code=(-1.2345 >= -1.2345) ? 0 : 1; exit} END {exit return_code}'
$ echo $?
0
$ awk 'BEGIN {return_code=(-1.2345 < -1.2345) ? 0 : 1; exit} END {exit return_code}'
$ echo $?
1
$ awk 'BEGIN {return_code=(-1.2345 < 2) ? 0 : 1; exit} END {exit return_code}'
$ echo $?
0
$ awk 'BEGIN {return_code=(-1.2345 > 2) ? 0 : 1; exit} END {exit return_code}'
$ echo $?
To make it shorter for use, use this function:
compare_nums()
{
# Function to compare two numbers (float or integers) by using AWK.
# The function will not print anything, but it will return 0 (if the comparison is true) or 1
# (if the comparison is false) exit codes, so it can be used directly in shell one liners.
#############
### Usage ###
### Note that you have to enclose the comparison operator in quotes.
#############
# compare_nums 1 ">" 2 # returns false
# compare_nums 1.23 "<=" 2 # returns true
# compare_nums -1.238 "<=" -2 # returns false
#############################################
num1=$1
op=$2
num2=$3
E_BADARGS=65
# Make sure that the provided numbers are actually numbers.
if ! [[ $num1 =~ ^-?[0-9]+([.][0-9]+)?$ ]]; then >&2 echo "$num1 is not a number"; return $E_BADARGS; fi
if ! [[ $num2 =~ ^-?[0-9]+([.][0-9]+)?$ ]]; then >&2 echo "$num2 is not a number"; return $E_BADARGS; fi
# If you want to print the exit code as well (instead of only returning it), uncomment
# the awk line below and comment the uncommented one which is two lines below.
#awk 'BEGIN {print return_code=('$num1' '$op' '$num2') ? 0 : 1; exit} END {exit return_code}'
awk 'BEGIN {return_code=('$num1' '$op' '$num2') ? 0 : 1; exit} END {exit return_code}'
return_code=$?
return $return_code
}
$ compare_nums -1.2345 ">=" -1.2345 && echo true || echo false
true
$ compare_nums -1.2345 ">=" 23 && echo true || echo false
false
If you have floats, you can write a function and then use that. For example,
#!/bin/bash
function float_gt() {
perl -e "{if($1>$2){print 1} else {print 0}}"
}
x=3.14
y=5.20
if [ $(float_gt $x $y) == 1 ] ; then
echo "do stuff with x"
else
echo "do stuff with y"
fi
I solved this by using a small function to convert version strings to plain integer values that can be compared:
function versionToInt() {
local IFS=.
parts=($1)
let val=1000000*parts[0]+1000*parts[1]+parts[2]
echo $val
}
This makes two important assumptions:
The input is a "normal SemVer string"
Each part is between 0-999
For example
versionToInt 12.34.56 # --> 12034056
versionToInt 1.2.3 # --> 1002003
Example testing whether npm command meets the minimum requirement...
NPM_ACTUAL=$(versionToInt $(npm --version)) # Capture npm version
NPM_REQUIRED=$(versionToInt 4.3.0) # Desired version
if [ $NPM_ACTUAL \< $NPM_REQUIRED ]; then
echo "Please update to npm#latest"
exit 1
fi
Just adding to all the above answers:
If you have more than one expression in single if statement, you can do something like this:
if (( $a % 2 == 0 )) && (( $b % 2 != 0));
then
echo "What you want to do"
fi
Hope this helps!

Bash/Shell Script - arithmetic operator failing

I'm trying to slow down my infinite loop if CPU load exceeds certain limit, but, its just not working out right, below is the code. The if condition always results true
c=1
while [ $c -le 1 ]
do
#echo "Welcome $c times"
#php BALHABLH.php
IN=$(cat /proc/loadavg);
set -- "$IN"
IFS=" "; declare -a Array=($*)
echo "${Array[#]}"
echo "${Array[0]}"
echo "${Array[1]}"
#var = ${Array[1]}
x=$(expr "${Array[1]}" )
if [ $x > 0.91 ]
then
echo "CPU LOAD > 0.91"
sleep 2
fi
(( c++ ))
done
You need to use bc for floating point comparison and use (( ... )) for arithmetic expressions:
if (( $(bc -l <<< "$x > 0.91") == 1 ))
Also don't use cat, use:
IN=$(</proc/loadavg)
Bash cannot use floating point arithmetic. You could do something like this:
if [ $( echo "$x > 0.91" | bc ) -eq 1 ]; then
Bash only handles integers. To handle floats pipe to bc like this:
[ $(echo " $x > 0.91" | bc -l) -eq 1 ]
bc returns 1 if the comparison is true. We compare with 1 (using the -eq operator).
Validation
$ cat test.sh
#!/bin/bash
x="$1"
if [ $(echo " $x > 0.91" | bc -l) -eq 1 ]; then
echo greater;
else
echo smaller;
fi
$ ./test.sh 0.5
smaller
$ ./test.sh 1.5
greater
You can also simplify your script a bit like this:
#!/bin/bash
c=10
for (( i=1;i<=c;i++ )); do
load=$(awk '{print $2}' /proc/loadavg)
echo "$i: load is $load"
if (( $(echo "$load > 0.91" | bc) == 1 )); then
echo "CPU LOAD > 0.91"
sleep 2
fi
done

Float conditional in bash

in bash I need to compare two float numbers, one which I define in the script and the other read as paramter, for that I do:
if [[ $aff -gt 0 ]]
then
a=b
echo "xxx "$aff
#echo $CX $CY $CZ $aff
fi
but I get the error:
[[: -309.585300: syntax error: invalid arithmetic operator (error token is ".585300")
What is wrong?
Thanks
Using bc instead of awk:
float1='0.43255'
float2='0.801222'
if [[ $(echo "if (${float1} > ${float2}) 1 else 0" | bc) -eq 1 ]]; then
echo "${float1} > ${float2}"
else
echo "${float1} <= ${float2}"
fi
use awk
#!/bin/bash
num1=0.3
num2=0.2
if [ -n "$num1" -a -n "$num2" ];then
result=$(awk -vn1="$num1" -vn2="$num2" 'BEGIN{print (n1>n2)?1:0 }')
echo $result
if [ "$result" -eq 1 ];then
echo "$num1 greater than $num2"
fi
fi
Both test (which is usually linked to as [)and the bash-builtin equivalent only support integer numbers.
Use bc to check the math
a="1.21231"
b="2.22454"
c=$(echo "$a < $b" | bc)
if [ $c = '1' ]; then
echo 'a is smaller than b'
else
echo 'a is larger than b'
fi
I would use awk for that:
e=2.718281828459045
pi=3.141592653589793
if [ "yes" = "$(echo | awk "($e <= $pi) { print \"yes\"; }")" ]; then
echo "lessthanorequal"
else
echo "larger"
fi
The simplest solution is this:
f1=0.45
f2=0.33
if [[ $f1 > $f2 ]] ; then echo "f1 is greater then f2"; fi
which (on OSX) outputs:
f1 is greater then f2
Here's another example combining floating point and integer arithmetic (you need the great little perl script calc.pl that you can download from here):
dateDiff=1.9864
nObs=3
i=1
while [[ $dateDiff > 0 ]] && [ $i -le $nObs ]
do
echo "$dateDiff > 0"
dateDiff=`calc.pl $dateDiff-0.224`
i=$((i+1))
done
Which outputs
1.9864 > 0
1.7624 > 0
1.5384 > 0

Resources