Bash Random Variables = Not so random? - bash

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)

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 Iterate through repeated values in file

I have a file with this format:
User_ID , Place_ID , Rating
U32 , 1305 , 2
U32 , 1276 , 2
U32 , 1789 , 3
U65 , 1985 , 1
U65 , 1305 , 1
U65 , 1276 , 2
I would like to iterate through this file, sort by Place_ID, iterate through repeated values in Place_ID and add the ratings, once the last element of the Place_ID is added, check if value > x and if true, push the Place_ID into an array.
Ex: Place_ID 1305: 2 + 1 / 2 = 1.5 > 1 ----> ids+=($id)
Place_ID 1276: 2 + 2 / 2 = 2 > 1 -----> ids+=($id)
I have tried with
test5 () {
id=0
count=0
rating=0
ids=()
ratings=()
for i in `sort -t',' -k 2 ratings.csv`
do
aux=`echo "$i"| cut -f2 -d','`
if (( $id != $aux )); then
if (( $rating != 0 )); then
rating=`echo "scale=1; $rating / $count" | bc -l`
if (( $(echo "$rating >= 1" | bc -l) )); then
ids+=($id)
ratings+=($rating)
fi
fi
id=$aux
count=0
rating=0
else
rating=$(($rating + `echo "$i"| cut -f3 -d','`))
count=$(($count + 1))
fi
done
echo ${#ids[#]}
echo ${#ratings[#]}
}
EDIT: I think it works, but is there a way to make it better? Something that doesn't force me to use as many if's and count.
Thanks for the help.
This is another option using less if's:
#!/bin/bash
sum=()
count=()
while read -r line; do
place=$(echo "$line" | cut -d',' -f2)
rating=$(echo "$line" | cut -d',' -f3)
sum[$place]=$(echo "$rating + ${sum[$place]-0}" | bc -l)
count[$place]=$((count[$place] + 1))
done < <( sed 1d ratings.csv | sort -t',' -k 2 | tr -d '[:blank:]' )
ratings=()
for place in "${!sum[#]}"; do
ratings[$place]=$(echo "scale=1; ${sum[$place]} / ${count[$place]}" | bc -l)
done
# ratings at this point has the ratings for each place
echo ${!ratings[#]} # place ids
echo ${ratings[#]} # ratings
I'm assuming your ratings.csv has headers that is why this has sed 1d ratings.csv

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

Rounding up to 3 decimal points just truncates the rest

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

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