division in shell script [duplicate] - bash

This question already has answers here:
floating point accuracy with bash
(2 answers)
Closed 6 years ago.
I am trying to do a simple division of variables in a shell script, but it is somehow not working. May be there I am overlooking something really basic. Below is a script for calculating total number of reads from a bam file for bams files read from a list file. I need to assign the output to a variable and calculate a ratio. This is what I have:
normNum=100000
while IFS=$'\t' read -r bamfile name; do
#for i in $(ls *.bam)
echo $bamfile
mappedReads="$(samtools idxstats $bamfile | awk '{s+=$3} END {print s}')"
echo $normNum
echo $mappedReads
#scalingFactor="$((normNum / mappedReads))"
#echo $scalingFactor
scalingFactor=`printf "%0.3f\n" $((normNum / mappedReads))`
echo $scalingFactor
done < "${file}_temp"
The different prints are giving my correct number except for scalingFactor, which gives me 0.
merged_DupRem.bam
100000
24226512
0.000
Any pointers please?
Thanks..

Bash does integer math, not floating point. You will need to use awk or bc to provide floating point output. e.g. with bc
scalingFactor=$(printf "scale=3; %d/%d\n" $normNum $mappedReads | bc)

Related

How do I print the "zero" between the minus and the decimal places on Bash shell [duplicate]

This question already has answers here:
How do I get bc(1) to print the leading zero?
(13 answers)
Closed 2 years ago.
With dc external tool bash tend to not print at the right of the decimal point.
#!/bin/bash
a[0]=-0.5
`echo "scale=10;${a[0]}/1"|bc -l`
With the command represented above bash will print -.5000000000.
How can I add the zero between the minus signal and the point -0.5000000000
PS: I do print a[1]=0 with 10 decimal cases?
Instead of bc you may consider this awk that does floating point match and formatting using printf:
a[0]=-0.5
awk -v n="${a[0]}" 'BEGIN{printf "%.10f\n", n/1}'
-0.5000000000

Arithmetic operation and for loop in terminal [duplicate]

Here is my script:
d1=0.003
d2=0.0008
d1d2=$((d1 + d2))
mean1=7
mean2=5
meandiff=$((mean1 - mean2))
echo $meandiff
echo $d1d2
But instead of getting my intended output of:
0.0038
2
I am getting the error Invalid Arithmetic Operator, (error token is ".003")?
bash does not support floating-point arithmetic. You need to use an external utility like bc.
# Like everything else in shell, these are strings, not
# floating-point values
d1=0.003
d2=0.0008
# bc parses its input to perform math
d1d2=$(echo "$d1 + $d2" | bc)
# These, too, are strings (not integers)
mean1=7
mean2=5
# $((...)) is a built-in construct that can parse
# its contents as integers; valid identifiers
# are recursively resolved as variables.
meandiff=$((mean1 - mean2))
Another way to calculate floating numbers, is by using AWK rounding capability, for example:
a=502.709672592
b=501.627497268
echo "$a $b" | awk '{print $1 - $2}'
1.08218
In case you do not need floating point precision, you may simply strip off the decimal part.
echo $var | cut -d "." -f 1 | cut -d "," -f 1
cuts the integer part of the value. The reason to use cut twice is to parse integer part in case a regional setting may use dots to separate decimals and some others may use commas.
Edit:
Or, to automate the regional settings one may use locale.
echo $var | cut -d $(locale decimal_point) -f 1
You can change the shell which you are using. If you are executing your script with bash shell bash scriptname.sh try using ksh for your script execution. Bash doesn't support arithmetic operations that involve floating point numbers.
Big shout-out to the bc command - it totally saved my day! It's a simple answer, but it worked like a charm.
a=1.1
b=1.1
echo $a + $b | bc -l
# Output:
2.2
#SUM
sum=$(echo $a + $b | bc -l)
echo $sum
# Output
2.2
bc is a command-line calculator, which allows users to perform mathematical calculations on the terminal.

How to compare between two pings in shell script? [duplicate]

This question already has answers here:
Unable to compare ping times on fly
(3 answers)
How can I compare numbers in Bash?
(10 answers)
Closed 4 years ago.
I am trying to find out which search engine will give the fastest ping for me and then get the result in a single output but I am not able to figure out how to put if statement for that in shell script.
My code is below
png=10000
for item in ${array[*]}
do
png_cal=`ping -c 4 "$item" | tail -1| awk '{print $4}' | cut -d '/' -f 2`
if [[ $png < $png_cal ]];then
png=${png_cal}
link=${item}
fi
done
and my program is not going in the loop again after first loop.
Your immediate problem is how to compare floating point numbers in Bash, whereas your code would only compare integers.
I got it to work like this:
array=(www.google.com www.facebook.com www.linkedin.com www.stackoverflow.com)
fastest_response=2147483647 # largest possible integer
for site in ${array[*]}
do
this_response=`ping -c 4 "$site" | awk 'END { split($4,rt,"/") ; print rt[1] }'`
if (( $(bc -l <<< "$this_response < $fastest_response") )) ; then
fastest_response=$this_response
fastest_site=$site
fi
echo "Got $this_response for $site ; fastest so far $fastest_site"
done
echo $fastest_site
Further explanation:
See this related Stack Overflow answer for why the expression to compare floats in Bash is so complicated.
I simplified the calls to tail, awk etc by just doing all that in awk, which is cleaner.
Notice I gave the variables more meaningful names. It's so much easier to think about code when the variable names announce themselves precisely for what they really are.
Instead of 10,000 I chose to use the largest possible integer in Bash. It's just a style thing as it seemed less arbitrary than 10,000.
I also made the script communicate a bit with the user, because the user won't want to sit there waiting for pings without knowing what's going on.
And the winner is:
$ bash test.sh
Got 21.786 for www.google.com ; fastest so far www.google.com
Got 20.879 for www.facebook.com ; fastest so far www.facebook.com
Got 20.555 for www.linkedin.com ; fastest so far www.linkedin.com
Got 21.368 for www.stackoverflow.com ; fastest so far www.linkedin.com
www.linkedin.com

Bash expr with 0 before decimals [duplicate]

This question already has answers here:
How do I get bc(1) to print the leading zero?
(13 answers)
Closed 8 years ago.
I am summing two small decimal numbers contained in two variables:
SEEupper=`expr $SEEmedian+$SEEthre | bc`
but since the result is a number smaller than 1, like 0.XXXX, the output is: '.XXXX'.
Is there any way to have an output with the '0' before the dot and the decimals?
workaround: ... | sed -e "s|^\.|0.|"
if you can use python:
SEEupper=`python -c "print $SEEmedian +$SEEthre"`
Yes, with the internal bash command printf:
printf "%g" $SEEupper

Shell loops using non-integers?

I wrote a .sh file to compile and run a few programs for a homework assignment. I have a "for" loop in the script, but it won't work unless I use only integers:
#!/bin/bash
for (( i=10; i<=100000; i+=100))
do
./hw3_2_2 $i
done
The variable $i is an input for the program hw3_2_2, and I have non-integer values I'd like to use. How could I loop through running the code with a list of decimal numbers?
I find it surprising that in five years no one ever mentioned the utility created just for generating ranges, but, then again, it comes from BSD around 2005, and perhaps it wasn't even generally available on Linux at the time the question was made.
But here it is:
for i in $(seq 0 0.1 1)
Or, to print all numbers with the same width (by prepending or appending zeroes), use -w. That helps prevent numbers being sent as "integers", if that would cause issues.
The syntax is seq [first [incr]] last, with first defaulting to 1, and incr defaulting to either 1 or -1, depending on whether last is greater than or less than first. For other parameters, see seq(1).
you can use awk to generate your decimals eg steps of0.1
num=$(awk 'BEGIN{for(i=1;i<=10;i+=0.1)print i}')
for n in $num
do
./hw3_2_2 $n
done
or you can do it entirely in awk
awk 'BEGIN{cmd="hw3_2_2";for(i=1;i<=10;i+=0.1){c=cmd" "i;system(cmd) } }'
The easiest way is to just list them:
for a in 1.2 3.4 3.11 402.12 4.2 2342.40
do
./hw3_2_2 $a
done
If the list is huge, so you can't have it as a literal list, consider dumping it in a file and then using something like
for a in $(< my-numbers.txt)
do
./hw3_2_2 $a
done
The $(< my-numbers.txt) part is an efficient way (in Bash) to substitute the contents of the names file in that location of the script. Thanks to Dennis Williamson for pointing out that there is no need to use the external cat command for this.
Here's another way. You can use a here doc to include your data in the script:
read -r -d '' data <<EOF
1.1
2.12
3.14159
4
5.05
EOF
for i in "$data"
do
./hw3_2_2 "$i"
done
Similarly:
array=(
1.1
2.12
3.14159
4
5.05
)
for i in "${array[#]}"
do
./hw3_2_2 "$i"
done
I usually also use "seq" as per the second answer, but just to give an answer in terms of a precision-robust integer loop and then bc conversion to a float:
#!/bin/bash
for i in {2..10..2} ; do
x=`echo "scale=2 ; ${i}/10" | bc`
echo $x
done
gives:
.2
.4
.6
.8
1.0
bash doesn't do decimal numbers. Either use something like bc that can, or move to a more complete programming language. Beware of accuracy problems though.

Resources