Rounding up to 3 decimal points just truncates the rest - bash

read n
i=0
sum=0
while [ $i -lt $n ]
do
read X
sum=`expr $X + $sum `
i=`expr $i + 1 `
done
echo "scale = 3; $sum/$n" | bc -l
--my above code is rounding upto a lesser value, where i want the greater one
e.g. if the ans is 4696.9466 it is rounding up to 4696.946 whereas 4696.947 is what i want. So , suggest any edits

You may pipe your bc to printf :
echo "scale = 4; $sum/$n" | bc -l | xargs printf '%.*f\n' 3
From you example :
$ echo "scale = 4; 4696.9466" | bc -l | xargs printf '%.*f\n' 3
4696,947

Change last line of your script from echo "scale = 3; $sum/$n" | bc -l to
printf %.3f $(echo "$sum/$n" | bc -l)
printf will round it off correctly. For example,
$ sum=1345
$ n=7
$ echo "$sum/$n" | bc -l
192.14285714285714285714
$ printf %.3f $(echo "$sum/$n" | bc -l)
192.143

Related

Bash round whole numbers up, down or to nearest hundreds

There is a range of whole positive numbers like
827818
6574762
685038
55326902
What I need, for example, to round down to hundreds to get accordingly
827800
6574700
685000
55326900
Many ideas how to round up, down or nearest hundreds using Javascript, for example
Math.floor(number / 100) * 100;
but is it possible to do the same in Bash ?
It's not entirely clear what is meant by "in Bash", but perhaps one of :
$ cat input
827818
6574762
685038
55326902
$ awk '{printf "%d00\n", $0 / 100}' input
827800
6574700
685000
55326900
or (if all the values are greater than 100!):
while read x; do echo "${x%[0-9][0-9]}00"; done < input
If you want to handle the case where values are less than 100 and deal with negative values, you could do:
while read x; do if test "${x#-}" -gt 100; then echo "${x%[0-9][0-9]}00"; else echo 0; fi; done < input
but it's almost certainly better to stick with awk.
To round to the nearest 100, use (number + 50) / 100 * 100.
You can modify each line to this calculation, then run those expressions through bc to evaluate them.
sed 's|.*|(&+50)/100*100|' file | bc
For rounding down you can also rev the full number and use cut -c 1-2 on the output, which you can then rev again to obtain the full number's last two digits. Then you can do calc or bc (full number - first output) to round down to nearest 100.
Or you can remove the last two digits of the full number with bc using scale=0 (or no scale) and divide by 100, after which you can just multiply by 100 again;
DIV_100=$( echo "123456789/100" | bc ) && echo "$DIV_100*100" | bc
and in loop
while read -r line; do
echo "$line" > full_number_1.txt
FULL_NUMBER=$(<full_number_1.txt)
DIV_100=$( echo "$FULL_NUMBER / 100" | bc )
echo "$DIV_100 * 100" | bc >> output_file
done < input.txt
Or use echo ${...%??}
echo "${number%??}*100" | bc
..loop..
while read -r line; do
echo "${line%??}*100" | bc >> output
done < input.txt
Many options here for rounding down.
Rounding up will require some alterations but is equally possible in many ways. For example:
DIV_100=$( echo "123456789/100" | bc ) && echo "($DIV_100*100)+100" | bc
and in loop
while read -r line; do
echo "$line" > full_number_1.txt
FULL_NUMBER=$(<full_number_1.txt)
DIV_100=$( echo "$FULL_NUMBER / 100" | bc )
echo "($DIV_100 * 100)+100" | bc >> output_file
done < input.txt
or with echo ${...%??}
echo "(${number%??}*100)+100" | bc
..loop..
while read -r line; do
echo "(${line%??}*100)+100" | bc >> output
done < input.txt

Bash variable not saving new data given?

I wrote a Bash function:
CAPACITY=0
USED=0
FREE=0
df | grep /$ | while read LINE ; do
CAPACITY=$(echo "${CAPACITY}+$(echo ${LINE} | awk '{print $2}')" | bc )
USED="$[${USED}+$(echo ${LINE} | awk '{print $3}')]"
FREE="$[${FREE}+$(echo ${LINE} | awk '{print $4}')]"
done
echo -e "${CAPACITY}\t${USED}\t${FREE}"
for i in /home /etc /var /usr; do
df | grep ${i}[^' ']*$ | while read LINE ; do
CAPACITY=$[${CAPACITY}+$(echo ${LINE} | awk '{print $2}')]
USED=$[${USED}+$(echo ${LINE} | awk '{print $3}')]
FREE=$[${FREE}+$(echo ${LINE} | awk '{print $4}')]
done
done
if [ "${1}" = "explode?" ] ; then
if [ $[${USED}*100/${CAPACITY}] -ge 95 ] ; then
return 0
else
return 1
fi
elif [ "${1}" = "check" ] ; then
echo -e "Capacity = $(echo "scale=2; ${CAPACITY}/1024/1024" | bc)GB\nUsed = $(echo "scale=2; ${USED}/1024/1024" | bc)GB\nAvaliable = $(echo "scale=2; ${FREE}/1024/1024" | bc)GB\nUsage = $(echo "scale=2; ${USED}*100/${CAPACITY}" | bc)%"
fi
}
Note the 2 different methods to store the data in the CAPACITY/USED/FREE vars in the first 'while' loop and the echo right after it to debug the code.
Seems as though while running the script the data inputted into the variables in the loop isn't saved.
Here's the output while running the script with 'set -x':
+ CAPACITY=0
+ USED=0
+ FREE=0
+ df
+ grep '/$'
+ read LINE
++ bc
+++ echo /dev/vda1 52417516 8487408 43930108 17% /
+++ awk '{print $2}'
++ echo 0+52417516
+ CAPACITY=52417516
++ echo /dev/vda1 52417516 8487408 43930108 17% /
++ awk '{print $3}'
+ USED=8487408
++ echo /dev/vda1 52417516 8487408 43930108 17% /
++ awk '{print $4}'
+ FREE=43930108
+ read LINE
+ echo -e '0\t0\t0'
0 0 0
Why the heck don't the variables store the new numbers even though it clearly shows a new number was stored?
Why ... don't the variables store the new numbers even though it clearly shows a new number was stored?
Because the right part of | is run in a subshell, so the changes are not propagated to the parent shell.
$ a=1
$ echo a=$a
a=1
$ true | { a=2; echo a=$a; }
a=2
$ echo a=$a
echo a=1
For more info read bashfaq I set variables in a loop that's in a pipeline. Why do they disappear after the loop terminates?. The common solution is to use a process substitution:
while IFS= read -r line; do
blabla
done < <( blabla )
The $[ is deprecated. Use $((...)) instead. bash hackers wiki obsolete and deprecated syntax.
In bash just use arithmetic expansion (( for numbers comparison. if (( used * 100 / capacity >= 96 )); then.
By convention upper case variables are used for exported variables. Use lower case variable names for script local variables.
The is no need to grep the output of df. Just df /home /etc /var /usr. Or really just read -r capacity used free < <(df /home /etc /var /usr | awk '{ capacity += $1; used += $3; free += $4 } END{print capacity, used, free}').

Product of a digit with a shell script

How do I get the product of a 1 number in bash? (12345 > 1x2x3x4x5)
I am trying to get a script to do multiplication, I have tried escaping the * char but no luck, I have also tried fiddling with expr.
echo -n "Enter number "
read n
len=$(echo $n | wc -c)
len=$(( $len - 1 ))
for (( i=1; i <= $len; i++ ))
do
prod=$(($prod \* $(echo $n | cut -c $i) ))
done
echo "Product of $n is $prod"
You can get the length of a string from parameter expansion, no need to call external tools.
#!/bin/bash
read -p 'Enter number: ' n
product=1
for (( i=0; i < ${#n}; ++i )) ; do
d=${n:i:1}
(( product *= d ))
done
printf '%d\n' $product
And, for fun, using Perl:
perl -MList::Util=product -le 'print product(split //, shift)' -- "$n"
gawk (GNU awk) solution:
echo 12345 | awk -v FPAT="[0-9]" '{ r=$1; for(i=2;i<=NF;i++) r=r*$i }END{ print r }'
The output:
120
FPAT="[0-9]" - represents field value so that each field contains a digit
As I understood you want to get value of digits multiplication in number
Suppose you have number:
number=12345
You have to insert * between digits:
echo $number | sed 's/\([0-9]\)/\1*/g'
We will get string:
1*2*3*4*5*
We don't need last asteriks - let's remove it:
echo $number | sed 's/\([0-9]\)/\1*/g' | sed 's/.$//g'
We will get this:
1*2*3*4*5
We can now redirect it to calc:
echo $number | sed 's/\([0-9]\)/\1*/g' | sed 's/.$//g' | calc -p
It's stdout is:
120
\* is wrong in an arithmetic expression, it should be * alone. But even then running your code gives:
$ bash product.sh
Enter number 12
product.sh: line 10: * 1 : syntax error: operand expected (error token is "* 1 ")
Product of 12 is
The reason for the error is that $prod variable is not set to an
initial value before so it's expanded to an empty value, for example
try it in your terminal:
$ echo $prod
$
In your script you should set prod to an initial value before using
it for the first time. It should be:
echo -n "Enter number "
read n
len=$(echo $n | wc -c)
len=$(( $len - 1 ))
prod=1
for (( i=1; i <= $len; i++ ))
do
prod=$(($prod * $(echo $n | cut -c $i) ))
done
echo "Product of $n is $prod"
There are a few more problems with your code:
always put a shebang line at the top
always double quote the variables
using $ on variables is not necessary in arithmetic expressions in Bash

Bash Random Variables = Not so random?

I wanted to generate a random decimal that was rounded to the 10ths place between .1 and 1.
I did this with the command
`echo "scale=1; $(( ( RANDOM % 10 ) + 1 ))/10" | bc -l`
Now if you set it as a variable, say
var=`echo "scale=1; $(( ( RANDOM % 10 ) + 1 ))/10" | bc -l`
and have your script echo var like so,
#!/bin/bash
var=`echo "scale=1; $(( ( RANDOM % 10 ) + 1 ))/10" | bc -l`
echo $var
echo $var
echo $var
It repeats the same decimal, say .4, but if you
#!/bin/bash
echo `echo "scale=1; $(( ( RANDOM % 10 ) + 1 ))/10" | bc -l`
echo `echo "scale=1; $(( ( RANDOM % 10 ) + 1 ))/10" | bc -l`
echo `echo "scale=1; $(( ( RANDOM % 10 ) + 1 ))/10" | bc -l`
It will give you three random numbers using the same command as
$var=`echo "scale=1; $(( ( RANDOM % 10 ) + 1 ))/10" | bc -l`
Why does Bash not generate three new numbers if given the same command but as a variable?
The following command sets a value for var:
var=`echo "scale=1; $(( ( RANDOM % 10 ) + 1 ))/10" | bc -l`
Once the value is set, it does not change. Thus, however many times you view it, it will be the same:
echo $var
echo $var
echo $var
The only way to get a new value is for bash to evaluate RANDOM again.
Using a function instead
You might prefer a function that would return a different random variable with each invocation:
$ var() { echo "scale=1; $(( ( RANDOM % 10 ) + 1 ))/10" | bc -l; }
$ var
.3
$ var
.4
$ var
1.0
$ var
.4
It is because
var=`echo "scale=1; $(( ( RANDOM % 10 ) + 1 ))/10" | bc -l`
executes the echo command and stores its output into a variable. The command itself is only evaluated once by the bash interpreter.
If you want a compact way to generate a random number, I suggest using a function:
#!/bin/bash
myRandom(){
echo "scale=1; $(( ( RANDOM % 10 ) + 1 ))/10" | bc -l
}
echo $(myRandom)
echo $(myRandom)
echo $(myRandom)

Assigning variables to values in a text file with 3 columns, line by line

I've got a .txt file with three columns, each separated by a tab, and 264 rows called PowerCoords.txt. Each row contains an x (column 1), y (column2) and z (column3) coordinate. I want to go through this file, line by line, assign each value to X,Y, and Z, and then input those variables into another function.
I'm new to bash, and I don't understand how to specify that I want the value in Row 1, Column 2 to be the variable Y, and so on...
I know this is likely super simple and I could do it in a flash in Matlab, but I'm trying to keep everything in bash.
while read x y z; do
echo x=$x y=$y z=$z
done < input.txt
The above requires that none of your columns contain any whitespace.
EDIT:
In response to comments, here is one technique to handle numbering the lines:
nl -ba < input.txt | while read line x y z rest; do
~/data/standard/MNI152_T1_2mm -mul 0 \
-add 1 -roi $x 1 $y 1 $z 1 0 1 point -odt float > NewFile$line
done
William Pursell's answer is much smarter, but in my straight-forward beginners mind I tried following some time ago:
#!/bin/bash
data="data.dat"
datalength=`wc $data | awk '{print $1;}'`
for (( i=1; i<=$datalength; i++ )) ;do
x=`cat $data | awk '{print $1;}' | sed -n "$i"p | sed -e 's/[eE]+*/\\*10\\^/'` ; x=`echo "$x" | bc -l` ; echo "x$i=$x";
y=`cat $data | awk '{print $2;}' | sed -n "$i"p | sed -e 's/[eE]+*/\\*10\\^/'` ; y=`echo "$y" | bc -l` ; echo "y$i=$y";
z=`cat $data | awk '{print $3;}' | sed -n "$i"p | sed -e 's/[eE]+*/\\*10\\^/'` ; z=`echo "$z" | bc -l` ; echo "z$i=$z";
# do something with xyz:
fslmaths ~/data/standard/MNI152_T1_2mm -mul 0 -add 1 -roi $x 1 $y 1 $z 1 0 1 point -odt float > NewFile$i
done
The bc and the sed -e 's/[eE]+*/\\*10\\^/' have to be added if you like to use floating point numbers and for the case that input also uses exponential notation.
I had a similar problem but for lots of input data those bash scripts are very slow. I migrated to perl then. In perl it would look like this:
#!/usr/bin/perl -w
use strict;
open (IN, "data.dat") or die "Error opening";
my $i=0;
for my $line (<IN>){
$i++;
open(OUT, ">NewFile$i.out");
chomp $line;
(my $x,my $y,my $z) = split '\t',$line;
print "$x $y $z\n";
# do something with xyz:
my $f= fslmaths ~/data/standard/MNI152_T1_2mm -mul 0 -add 1 -roi $x 1 $y 1 $z 1 0 1 point -odt float
print OUT "f= $f\n";
close OUT;
}
close IN;

Resources