Bash/Shell Script - arithmetic operator failing - bash

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

Related

Bash scripting - function for computing times tables

tableGenerator () {
if [ "$1" = "**" ];then
i=1
while [ $i -le 15 ]
do
echo "$number $1 $i =$(( $number $1 $i))"
i=$(($i+1))
done
else
i=1
while [ $i -le 15 ]
do
echo -n "$number $1 $i="
echo " scale=2 ;$number $1 $i " | bc
i=$(($i+1))
done
fi
So in the rest of the code i have the user select the number and operator, then I call the function passing in the user input. Now my question is , can i condense this function - using the same operation to handle floats and exponents??
I suppose the reason why you want to use bc in some cases is to have two decimals after the decimal point. In bc, the exponent operator is not ** as in bash but ^.
If these assumptions are correct, I would suggest you next code:
tableGenerator () {
if [ "$1" = "**" ]; then
op=^
else
op="$1"
fi
for i in {1..15}
do
echo -n "$number $op $i="
echo "scale=2; $number $op $i " | bc
done
}

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

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

how to extract numbers from this echo into separate variables?

Sorry about bits and snippit of information
So I am writing an average shell script program
so if use inputs
echo 1 3, .... | sh get_number
I would have to pull the numbers seperated by spaces from echo to be
var1 = 1, var2= 3, etc.
I tried
#!/bin/sh
sum=0
for i in $*
do
sum=`expr $sum + $i`
done
avg=`expr $sum / $n`
echo Average=$avg
but doesnt work....
do I include a read here?
also how would I do
sh get_number <file1>, <file2>... to grab numbers in them and sum them
in shell script?
Thanks
Sounds like you are looking for the read shell builtin:
% echo "1 2 3 4" | read a b stuff
% echo $b
2
% echo $stuff
3 4
To fix up your code:
for i in $*; do
sum=$(( sum + i ))
n=$(( n + 1 ))
done
echo "Average=$(( sum / n ))"
#!/bin/sh
while [ $# -gt 0 ]; do
(( i++ ))
(( sum += $1 ))
shift
done
echo "Average=$(( sum/i ))"
Note: This fails in dash which is the closest shell I could find to a real sh.
An example of reading values from files passed as command line arguments or from lines read from stdin:
add_to_sum() {
set $*
while [ $# -gt 0 ]; do
I=`expr $I + 1`
SUM=`expr $SUM + $1`
shift
done
}
I=0
SUM=0
if [ $# -gt 0 ]; then
# process any arguments on the command line
while [ $# -gt 0 ]; do
FILE=$1
shift
while read LINE; do
add_to_sum "$LINE"
done < "$FILE"
done
else
# if no arguments on the command line, read from stdin
while read LINE; do
add_to_sum "$LINE"
done
fi
# be sure not to divide by zero
[ $I -gt 0 ] && echo Average=`expr $SUM / $I`

How to compare two decimal numbers in bash/awk?

I am trying to compare two decimal values but I am getting errors.
I used
if [ "$(echo $result1 '>' $result2 | bc -l)" -eq 1 ];then
as suggested by the other Stack Overflow thread.
I am getting errors.
What is the correct way to go about this?
You can do it using Bash's numeric context:
if (( $(echo "$result1 > $result2" | bc -l) )); then
bc will output 0 or 1 and the (( )) will interpret them as false or true respectively.
The same thing using AWK:
if (( $(echo "$result1 $result2" | awk '{print ($1 > $2)}') )); then
if awk 'BEGIN{exit ARGV[1]>ARGV[2]}' "$z" "$y"
then
echo z not greater than y
else
echo z greater than y
fi
if [[ `echo "$result1 $result2" | awk '{print ($1 > $2)}'` == 1 ]]; then
echo "$result1 is greater than $result2"
fi
Following up on Dennis's reply:
Although his reply is correct for decimal points, bash throws (standard_in) 1: syntax error with floating point arithmetic.
result1=12
result2=1.27554e-05
if (( $(echo "$result1 > $result2" | bc -l) )); then
echo "r1 > r2"
else
echo "r1 < r2"
fi
This returns incorrect output with a warning although with an exit code of 0.
(standard_in) 1: syntax error
r1 < r2
While there is no clear solution to this (discussion thread 1 and thread 2), I used following partial fix by rounding off floating point results using awk followed by use of bc command as in Dennis's reply and this thread
Round off to a desired decimal place: Following will get recursive directory space in TB with rounding off at the second decimal place.
result2=$(du -s "/home/foo/videos" | tail -n1 | awk '{$1=$1/(1024^3); printf "%.2f", $1;}')
You can then use bash arithmetic as above or using [[ ]] enclosure as in following thread.
if (( $(echo "$result1 > $result2" | bc -l) )); then
echo "r1 > r2"
else
echo "r1 < r2"
fi
or using -eq operator where bc output of 1 is true and 0 is false
if [[ $(bc <<< "$result1 < $result2") -eq 1 ]]; then
echo "r1 < r2"
else
echo "r1 > r2"
fi
For shell script I couldn't use double brackets (()). So, what helped me was to split it in two rows and do the comparison in the classic way.
low_limit=4.2
value=3.9
result=$(echo "${value}<${low_limit}" | bc)
if [ $result = 1 ]; then
echo too low;
else
echo not too low;
fi
You can also echo an if...else statement to bc.
- echo $result1 '>' $result2
+ echo "if (${result1} > ${result2}) 1 else 0"
(
#export IFS=2 # example why quoting is important
result1="2.3"
result2="1.7"
if [ "$(echo $result1 '>' $result2 | bc -l)" -eq 1 ]; then echo yes; else echo no;fi
if [ "$(echo "if (${result1} > ${result2}) 1 else 0" | bc -l)" -eq 1 ];then echo yes; else echo no; fi
if echo $result1 $result2 | awk '{exit !( $1 > $2)}'; then echo yes; else echo no; fi
)
Can't bash force type conversion? For example:
($result1 + 0) < ($result2 + 0)
Why use bc ?
for i in $(seq -3 0.5 4) ; do echo $i ; if [[ (( "$i" < 2 )) ]] ; then echo "... is < 2";fi; done
The only problem : the comparison "<" doesn't work with negative numbers : they are taken as their absolute value.

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