C style arithmetic with floating point values in Bash [duplicate] - bash

This question already has answers here:
How do I use floating-point arithmetic in bash?
(23 answers)
Closed 4 years ago.
How can I have the right result from this bash script?
#!/bin/bash
echo $(( 1/2 ))
I get 0 as result! So I tried to use these but without success:
$ echo $(( 1/2.0 ))
bash: 1/2.0 : syntax error: invalid arithmetic operator (error token is ".0 ")
$ echo $(( 1.0/2 ))
bash: 1.0/2 : syntax error: invalid arithmetic operator (error token is ".0/2 ")

bash is not the right tool alone to use floats, you should use bc with it :
bc <<< "scale=2; 1/2"
.50
If you need to store the result in a variable :
res=$(bc <<< "scale=2; 1/2")
echo $res

I once stumbled on a nice piece of code, which is somewhat utilizing suggestion sputnick made, but wraps it around a bash function:
function float_eval()
{
local stat=0
local result=0.0
if [[ $# -gt 0 ]]; then
result=$(echo "scale=$float_scale; $*" | bc -q 2>/dev/null)
stat=$?
if [[ $stat -eq 0 && -z "$result" ]]; then stat=1; fi
fi
echo $result
return $stat
}
Then, you can use it as:
c=$(float_eval "$a / $b")

Related

Can a conditional statement be used inside a command in bash? [duplicate]

This question already has answers here:
Ternary operator (?:) in Bash
(23 answers)
Bash command groups: Why do curly braces require a semicolon?
(1 answer)
Closed 2 years ago.
Can a conditional statement be inserted in, for example, a print command like echo with bash?
E.g. (does not work)
$ cat t.sh
#!/bin/bash
string1="It's a "
string2opt1="beautiful"
string2opt2="gross"
string3=" day."
read c
echo -n "$string1 $( [[ $c -eq 0 ]] && { echo -n "$string2opt1" } || { echo "$string2opt2" } ) $string3"
I know that this is possible with semicolons/non-one-liners; I am just wondering if there is a more elegant or accepted way of doing this.
To clarify, you want to achieve this:
#!/bin/bash
string1="It's a"
string2opt1="beautiful"
string2opt2="gross"
string3="day."
read -r c
if [[ $c -eq 0 ]]; then
echo "$string1" "$string2opt1" "$string3"
else
echo "$string1" "$string2opt2" "$string3"
fi
but in a one-liner. Does this work for you?
#!/bin/bash
string1="It's a"
string2opt1="beautiful"
string2opt2="gross"
string3="day."
read -r c
echo "$string1" "$([[ $c -eq 0 ]] && echo "$string2opt1" || echo "$string2opt2")" "$string3"

How does less than operator behave differently from -le in BASH? [duplicate]

This question already has answers here:
If statement with arithmetic comparison behaviour in bash
(3 answers)
Closed 2 years ago.
#!/bin/bash
a=10
b=30
[ $a -eq $b ]
echo $?
echo $(($a==$b))
This code outputs 1 then 0.
Why does $(($a==$b)) output 0?
I have tried this with -le and < also .
[ $a -le $b ] ouputs 0
$(($a < $b)) outputs 1
I have used both ksh and bash to check
You can't compare the $? of one operation with the output of another. These are different things, because for exit status success is 0 and nonzero results reflect failure, but in a numeric context (such as that $(( )) enters), truth is 1 and failure is 0.
Compare your test with:
#!/bin/bash
a=10
b=30
[ "$a" -eq "$b" ]
echo "Exit status using -eq: $?"
(( a == b ))
echo "Exit status using (( )): $?"
Output is correctly:
Exit status using -eq: 1
Exit status using (( )): 1
The changes I made (other than switching from $(( )) to (( ))) around quotes, $s, etc. were cosmetic with your current data -- they could change behavior with unexpected values, so I recommend using those practices in your code, but they don't actually modify immediate output.

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

Bad substitution error in ksh [duplicate]

This question already has answers here:
Cannot debug simple ksh programme
(2 answers)
Closed 8 years ago.
The following KornShell (ksh) script should check if the string is a palindrome. I am using ksh88, not ksh93.
#!/bin/ksh
strtochk="naman"
ispalindrome="true"
len=${#strtochk}
i=0
j=$((${#strtochk} - 1))
halflen=$len/2
print $halflen
while ((i < $halflen))
do
if [[ ${strtochk:i:1} == ${strtochk:j:1} ]];then
(i++)
(j--)
else
ispalindrome="false"
break
fi
done
print ispalindrome
But I am getting bad substitution error at the following line : if [[ ${strtochk:i:1} == ${strtochk:j:1} ]];then
Can someone please let me know what I am doing wrong?
The substring syntax in ${strtochk:i:1} and ${strtochk:j:1} is not available in ksh88. Either upgrade to ksh93, or use another language like awk or bash.
You can replace your test with this portable line:
if [ "$(printf "%s" "$strtochk" | cut -c $i)" =
"$(printf "%s" "$strtochk" | cut -c $j)" ]; then
You also need to replace the dubious
halflen=$len/2
with
halflen=$((len/2))
and the ksh93/bash syntax:
$((i++))
$((j--))
with this ksh88 one:
i=$((i+1))
j=$((j-1))
How about this KornShell (ksh) script for checking if an input string is a palindrome.
isPalindrome.ksh
#!/bin/ksh
#-----------
#---Main----
#-----------
echo "Starting: ${PWD}/${0} with Input Parameters: {1: ${1} {2: ${2} {3: ${3}"
echo Enter the string
read s
echo $s > temp
rvs="$(rev temp)"
if [ $s = $rvs ]; then
echo "$s is a palindrome"
else
echo "$s is not a palindrome"
fi
echo "Exiting: ${PWD}/${0}"

Floating point comparison with variable in bash [duplicate]

This question already has answers here:
Bash: Integer expression expected [duplicate]
(2 answers)
Closed 7 years ago.
I want to compare a floating point variable to an integer.
I know this is not the best to do with bash, but my whole script is already written in bash.
$number can be any integer. If it below or equal 50, I want output1, for all others I want an output with the other variable k. This is what I have so far:
number=43
test=$(echo "scale=2; $number/50" | bc -l)
echo "$test"
for k in {1..5}
do
if ["$test" -le 1]
then echo "output"
elif ["$test" -gt $k]
then echo "output$k"
fi
done
If I try with test=0.43, the first loop does not even work. I think it has to do with an integer and a floating point comparison but cannot make it work.
Anything I am missing?
PS:this [0.43: command not found is what the terminal outputs.
Bash can't handle floats. Pipe to bc instead:
if [ $(echo " $test > $k" | bc) -eq 1 ]
The error you see though is because the test command (i.e. the [) needs spaces before and after
It is even better to use (( ... )) since you compare numbers like this:
if (( $(bc <<< "$test > $k") ))
The part in the loop should look like this:
if (( $(bc <<< "$test <= 1") ))
then
echo "output"
elif (( $(bc <<< "$test > $k") ))
then
echo "output$k"
fi
Relational expressions evaluate to 0, if the relation is false, and 1 if the relation is true [source]. Note however that is a behavior of GNU bc, and it is not POSIX compiant.
Kind of an old question, but it bears an additional answer I think.
While piping to a higher precision calculator (bc or dc) works, it is at the cost of a fork and a an extra process, since those calculators are not built in to bash. One thing that IS built in, though, is printf. So if you can deal with your numbers being within a particular number of decimal places, you can "fake" floating point comparisons, with a function like this:
#!/usr/bin/env bash
function [[[ () {
local LANG=C lhs rhs
printf -v lhs '%07.3f' "$1"; lhs=${lhs/./}
printf -v rhs '%07.3f' "$3"; rhs=${rhs/./}
case "$2" in
-lt) return $(( ! ( 10#$lhs < 10#$rhs ) )) ;;
-le) return $(( ! ( 10#$lhs <= 10#$rhs ) )) ;;
-eq) return $(( ! ( 10#$lhs == 10#$rhs ) )) ;;
-ge) return $(( ! ( 10#$lhs >= 10#$rhs ) )) ;;
-gt) return $(( ! ( 10#$lhs > 10#$rhs ) )) ;;
esac
}
number=${1:-43}
test=$(dc -e "2k $number 50 / p")
echo "$test"
for k in {1..5}; do
if [[[ "$test" -le 1 ]]]; then
echo "output"
elif [[[ "$test" -gt "$k" ]]]; then
echo "output $k"
fi
done
A few things to consider here.
I've named the function [[[ to be cute. You can name it whatever you like. ntest or mynumericcomparison or even [[[.
printf is an internal function within bash, so despite the fact that it's on your path, it doesn't cost a fork.
As it stands, the function supports numbers up to 999.999. If you need higher numbers (or more precision), adjust the printf formats.
The 10# at the beginning of each variable inside the case statement is to force the comparison to happen at base 10, since a zero-padded number might otherwise be interpreted as octal.
See also: http://mywiki.wooledge.org/BashFAQ/022

Resources