This question already has answers here:
How do I use floating-point arithmetic in bash?
(23 answers)
Closed 2 years ago.
I am trying to collect the data from a string of like "2.0 / 3.0".But when I try the following I get an error.
for i in t01 t02 t03 t04 t05 t06 t07 t08 t09 t10 t11; do
if [ -e "$i/feedback.txt" ]; then
grade=`tail -1 $i/feedback.txt | tr -d [:blank:]`
if [[ $grade =~ ^[0-9]+\.?[0-9]*/[0-9]+\.?[0-9]*$ ]]; then
IFS='/' read -ra grArray <<< "$grade"
score=${grArray[0]}
max=${grArray[1]}
total_tmax=$total_tmax+$max
total_t=$total_t+$score
echo $i: $score / $max
else
echo $i: 0 / 0
Output
t01: 4 / 4
t02: 2 / 3
t03: 3 / 3
t04: 3 / 3
t02/pp_marks.sh: line 39: 13+3.0: syntax error: invalid arithmetic operator (error token is ".0")
As per comments, bash does not allow floating point arithmetic, only integer. For simple tasks, you can use the bc tool (for more complex operation, consider using a scripting engine like awk, python or perl). Note that with bc you have to specify the precision.
total_tmax=$(bc <<<"scale=1 ; $total_tmax+$max")
total_t=$(bc <<< "scale=1 ; $total_t+$score")
echo "$i: $score / $max"
See Also: How do I use floating-point division in bash?
Related
This question already has answers here:
How do I use floating-point arithmetic in bash?
(23 answers)
Closed 4 years ago.
I am attempting to find the sum of a list of numbers' reciprocals. To illustrate what I am trying to do, here's a basic example:
With the file:
1
2
3
4
I would be trying to find the sum of 1/1, 1/2, 1/3 and 1/4. Is there a simple bash one-liner to do this? (I am new to bash, so explanations would be welcome!)
You could do something like this:
sed 's|^|1/|' file | paste -sd+ | bc -l
sed 's|^|1/|' prepends 1/ to every line
paste -sd+ joins all lines with a plus sign creating an arithmetic expression 1/1+1/2+1/3+1/4
bc -l evaluates that arithmetic expression and outputs the result
If you're looking for an arithmetical progression, you can use this bash one-liner using the bc command
d=0; for c in {1..4}; do d=`echo "$d + 1/$c" | bc -l`; done; echo "$d"
Its output is 1 + 0.5 + 0.3333 + 0.25 =
2.08333333333333333333
It works by
Setting a variable named d to 0
Creating a for loop that counts from 1 to 4
In the for loop it sets the d variable to the new value $d + 1/$c passed to the bc -l command executing the arithmetic
And outputs the value with an echo command
This question already has answers here:
How can I increment a number in a while-loop while preserving leading zeroes (BASH < V4)
(3 answers)
incrementing a number in bash with leading 0
(8 answers)
Closed 4 years ago.
I have the following code in a bash script, where "values" is a variable of newline separated numbers, some of which have leading 0's, and I am trying to iterate through each value in values and add each value to the variable "sum".
sum=0
while read line; do
sum=$(( sum + line ))
done <<< "$values"
this code segment gives me the error: "value too great for base (error token is "09")", which as I understand, is because the bash arithmetic expression interprets the value "line" to be an octal value because it has a leading zero.
How can I allow for bash to interpret the value of line to be its decimal value? (e.g. 09 -> 9) for the value "line" within this bash arithmetic expression?
You can override the "leading 0 means octal" by explicitly forcing base ten with 10#:
sum=$(( 10#$sum + 10#$line ))
Note that, while you can usually leave the $ off variable references in arithmetic contexts, in this case you need it. Also, if the variable has leading spaces (in front of the first "0"), it won't parse correctly.
To trim a single leading zero:
"${line#0}"
To trim any number of leading zeros:
"${line##+(0)}"
For example:
$ line=009900
$ echo "${line##+(0)}"
9900
You can just get rid of the leading zeros, with something like:
shopt extglob on
x="${x##+(0)}"
[[ -z "${x}" ]] && x=0
This will remove all leading zeros and then catch the case where it was all zeros (leading to an empty string), restoring it to a single zero.
The following function (and test code) will show this in action:
#!/bin/bash
stripLeadingZeros() {
shopt -s extglob
retVal="${1##+(0)}"
[[ -z "${retVal}" ]] && retVal=0
echo -n "${retVal}"
}
for testdata in 1 2 3 4 5 0 09 009 00000009 hello 00hello ; do
result="$(stripLeadingZeros ${testdata})"
echo "${testdata} -> ${result}"
done
The output of that is:
1 -> 1
2 -> 2
3 -> 3
4 -> 4
5 -> 5
0 -> 0
09 -> 9
009 -> 9
00000009 -> 9
hello -> hello
00hello -> hello
This question already has answers here:
How do I use floating-point arithmetic in bash?
(23 answers)
Closed 4 years ago.
I was taking a challenge on hackerrank .
The aim is:
read (int) N; then read N integers and print their avg to three decimal places.
Here's the code:
#!/bin/bash
#file name:rdlp.sh
read N
s=0
i=1
while (($i<=$N))
do
read a
s=$((s+a))
i=$((i+1))
done
s=$s/$N
echo "scale=3;$s"|bc -l
fi
When I run the code for some inputs:
3 #(value of N)
4 #(N = 3 integers)
4
3
Then the output is 3.666, but it should be 3.667.
So the QUESTION is that is there anyway to get it right (correct rounding off), or does it work like that only?
(the question came off when the above code was run for Testcase2 of the challenge at hackerrank)
bc rounds down with scale=x.
You can printf:
$ printf "%.3f\n" $(echo "scale=1000; 11/3"|bc -l)
3.667
or some tricky bc by adding 0.0005:
$ echo "scale=1000; v=11/3; v=v+0.0005; scale=3; v/1" | bc -l
3.667
This question already has answers here:
How can I compare two floating point numbers in Bash?
(22 answers)
Floating point comparison in shell
(7 answers)
Closed 4 years ago.
So want to turn this function, whose output is a number. The number is curled through some json file thats irrelevant.
#error input 1
if (($(masterfunc) >= 1)); then
#I've also tried
#error input 2
if (($(masterfunc | bc -l) >= 1)); then
I get these this error, which I'm assuming is because its outputing it as a letter or command and not as a number.
#error output 1
((: 1.00048333447157914468 >= 1: syntax error: invalid arithmetic
operator (error token is ".00048333447157914468 >= 1")
#error output 2
((: .99989817794934530799 >= 1: syntax error: operand expected (error
token is ".99989817794934530799 >= 1")
I'm assuming this is some floating point arithmetic problem, but then it should of been solved through bc?
I'm new to bash so if the problem is some unrelated syntax error I apologize.
This is actually rather complicated. The shell doesn't understand real numbers at all, so you have to get something else (like bc) to do the comparison and output something simpler that bash can understand. The simplest way I see to do this is:
if [ $(echo "$(masterfunc) >= 1" | bc) -eq 1 ]; then
Explanation, step by step:
echo "$(masterfunc) >= 1" runs the masterfunc function, adds ">= 1" to its output, and sends the result (something like "1.00048333447157914468 >= 1") to standard output.
echo "$(masterfunc) >= 1" | bc runs the above and pipes it to bc, which will do the comparison and print "1" if the masterfunc output is greater than or equal to 1, "0" if it's less. Note: you can try running this by hand and see how it works.
This "1"/"0" output is more along the lines of what bash can understand, but we still need to actually tell bash what to make of it.
[ $(echo "$(masterfunc) >= 1" | bc) -eq 1 ] runs the above, captures its output with $( ), and embeds that in a test expression. Basically, depending on the output from bc, this is equivalent to either [ 1 -eq 1 ] or [ 0 -eq 1 ].
Use this:
if (( $(printf '%s >= 1\n' "$(masterfunc)" | bc -l) )); then ...
I am writing a simple bash script to count the number of occurrences of random draws of cards. I store those in an array, and when printing out the results, for every 10 times that card is pulled, I print one single '*' in a sort of Histogram style of output.
Although, I keep receiving this error when compiling on Terminal:
"task1.sh: line 29: % 10 : syntax error: operand expected (error token is "% 10 ")
task1.sh: line 33: % 10: syntax error: operand expected (error token is "% 10")"
Can't seem to figure out why though. Thank you in advance for any help.
#!/bin/bash
randomdraw(){
Suits="Clubs Diamonds Hearts Spades"
suit=($Suits)
Denominations="2 3 4 5 6 7 8 9 10 Jack Queen King Ace"
denomination=($Denominations)
num_suits=${#suit[*]}
num_denominations=${#denomination[*]}
declare -a numoccurences
declare -a suitoccurences
for ((x=0 ; $x<$loopnum ; x=$x+1));
do
(( numoccurences[$(( RANDOM%num_denominations ))]++ ))
(( suitoccurences[$(( RANDOM%num_suits ))]++ ))
done
}
echo "How Many Random Draws?: "
read loopnum
randomdraw loopnum
for ((x=0 ; $x<$num_denominations ; x=$x+1));
do
let "rounder=$(( ${numoccurences[x]} % 10 ))"
if [ $rounder -ge 5 ];
then
let "starnum=$(( $(( ${numoccurences[x]} / 10 )) + 1 ))"
else
let "starnum=$(( ${numoccurences[x]} / 10 ))"
fi
echo "${denomination[x]}: "
for ((k=0 ; $k<$starnum ; k=$k+1));
do
echo "*"
done
done
Your num_denominations array is mostly empty and the
let "rounder=$(( ${numoccurences[x]} % 10 ))"
is evaluated to
let "rounder=$(( % 10 ))"
Print numoccurences and suitoccurences before asking for loop number for debugging.
You should try to be consistent in the way you write arithmetic expressions in bash. You don't need to use $ to introduce a variable inside an arithmetic expression. And you don't need to use ${array[idx]} either. There's no reason to use let if you have arithmetic evaluation, either. So instead of
let "rounder=$(( ${numoccurences[x]} % 10 ))"
You could write:
(( rounder = numoccurences[x] % 10 ))
These don't quite do the same thing. In the first one, ${numoccurences[x]} will be substituted with nothing if numoccurrences doesn't have a value corresponding to the key $x. In the second one, numoccurrence[x] will be replaced by 0, which is what you actually want. (That has nothing to do with the unnecessary let, since the $((...)) arithmetic expression is evaluated before let is run.)
There are many other places in that script where you would be well advised to simplify your style. For example,
let "starnum=$(( $(( ${numoccurences[x]} / 10 )) + 1 ))"
would be more robust and more readable as
(( starnum = numoccurences[x] / 10 + 1 ))