How to use bc in a bash while loop condition statement? - bash

I'm writing what should be a simple bash script to calculate the minimum value p needs to be in the formula C=1-(p^n). C and p are input by the user and are floating point numbers.
For example if C=0.36 and p=0.8, then my program should return 2 for the value of n, since 1-(0.8^2)=0.36
My code is below. Please note: I have put |bc in many different places in my while loop condition statement and each time I get an error. The last place I tried was, as you can see, between the two closing brackets.
I receive the following error:
operand expected (error token is ".36")
#!/bash/bin
echo "Enter I/O fraction time:"
read ioINPUT
echo "Enter expect CPU utilization:"
read uINPUT
n=1
function cpuUTIL {
x='scale=3; 1-$ioINPUT**$n'|bc
}
while [[ cpuUTIL -lt $uINPUT ]|bc]; do
n+=1
x='scale=3; 1-$ioINPUT**$n'|bc
done
echo $c
How do I properly use bc in a bash script while loop condition statement?

A few issues have been pointed out in comments. Here is a fixed up version of what you were trying:
#!/bin/bash
cpuUTIL () {
bc <<< "scale = 3; 1 - $ioINPUT ^ $n"
}
read -p "Enter I/O fraction time: " ioINPUT
read -p "Enter expect CPU utilization: " uINPUT
n=1
while (( $(bc <<< "$(cpuUTIL) < $uINPUT") )); do
(( ++n ))
x=$(bc <<< "scale = 3; 1 - $ioINPUT ^ $n")
done
echo $x
Comments:
#!/bin/bash, not #!/bash/bin
echo and read can be shortened to read -p
function cpuUTIL is less portable than cpuUTIL ()
Single quotes prevent parameter expansion; you have to use double quotes for your bc commands
I've used here-strings (<<<) instead of pipes to avoid creating a subshell
"Power of" in bc is ^ and not **
The while condition is complex: is uses nested command substitution within (( )), which translates the output of the bc statement (1 if the comparison holds, 0 if it doesn't) to true/false for Bash
n+=1 appends the string 1 to the contents of variable n (unless it was declared as an integer with declare -i, but that's rather exotic), so you have to use something like an arithmetic context, (( ++n ))
The final statement should probably be echo $x
But, as demonstrated by karakfa's answer, you can do it directly. In bc, it would look like this:
$ p=0.8
$ C=0.36
$ bc -l <<< "scale = 3; l(1-$C) / l($p)"
2.000
bc -l defines the math library, which contains (among a few others) the natural logarithm l(x).

you can iterate and find a value, but why? here is a better method
awk -v p=0.8 -v C=0.36 'BEGIN{print log(1-C)/log(p)}'
also the value will be, in general floating point, you can get the floor by wrapping the log ratio with int()

Using GNU bc:
#!/usr/bin/bc -ql
scale=3
print "Enter I/O fraction time: "
p=read()
print "Enter expected CPU utilization: "
c=read()
l(1-c)/l(p)
quit
Save the above to foo.bc, run chmod +x foo.bc, then execute with ./foo.bc.
Or without prompts, from bash:
bc -l <<< "p=0.8;c=0.36;scale=3;l(1-c)/l(p)"

Related

bash getting numbers from a file and make the average of them

Write a script that expects a file as its first argument. Some lines of the
file will consist of integers 0 - 1000.
The script should select the lines matching the previous criteria and print out their average to stdout (average of n integers is their sum divided by n).
And the file given looks like this:
22
78907
77 88 99 0000
need 11 gallons of water
0
roses are red
11
Example output:
11
Explanation: (22 + 11 + 0) / 3 = 11
I have tried already with this code:
#!/bin/bash
sum=0
ind=0
while IFS='' read -r line || [[ -n "$line" ]]; do
if [[ $line =~ ^[a-zA-Z\ ]+$ ]]
then
${sum}=${sum}+${#line}
${ind}=${ind}+1
echo ${sum}
fi
done < "$1"
value=${sum}/${ind}
echo ${value}
the print of this code is always 0/0 and some errors like:
./test1: line 9: 0=0+13: command not found
./test1: line 10: 0=0+1: command not found
Any ideas?
Part of the issue with your script is answered here.. Your variable assignments are incorrect. You only use the $ to refer to a variable that has already been assigned. The assignment process drops the dollar sign.
The other issue you're having is that your arithmetic is not being expressed within an arithmetic expression.
Note that you can use use arithmetic expansion to handle your variables:
if [[ $line =~ ^[a-zA-Z\ ]+$ ]]; then
(( sum += ${#line} ))
(( ind++ ))
printf '%s\n' "$sum"
fi
and later ...
value="$(( sum / ind ))"
printf '%s\n' "$value"
Beware that bash can only deal with integer math, floats are truncated. For more advanced math, consider using bc or dc (which are not built in to bash, they are separate tools that may need to be installed on your system) or another language like awk or perl which can do the same thing with better performance and more precise math.
That said, you can "fake" a couple of decimal places with a few extra lines of code and string manipulation, if you really need to:
$ sum=100; ind=7
$ printf -v x '%d' "$((${sum}00/${ind}))"
$ printf '%d.%d\n' "${x%??}" "${x:$((${#x}-2))}"
14.28
The first printf has division which multiplies the dividend by 100 (by adding two zeroes after it). The resultant quotient is then split with the second printf to insert the decimal point. This is a hack. Use tools that support real math.

Adding a list of space separated numbers

Currently stuck in a situation where I ask the user to input a line of numbers with a space in between, then have the program display those numbers with a delay, then add them. I have everything down, but can't seem to figure out a line of code to coherently calculate the sum of their input, as most of my attempts end up with an error, or have the final number multiplied by the 2nd one (not even sure how?). Any help is appreciated.
echo Enter a line of numbers to be added.
read NUMBERS
COUNTER=0
for NUM in $NUMBERS
do
sleep 1
COUNTER=`expr $COUNTER + 1`
if [ "$NUM" ]; then
echo "$NUM"
fi
done
I've tried echo expr $NUM + $NUM to little success, but this is really all I can some up with.
Start with
NUMBERS="4 3 2 6 5 1"
echo $NUMBERS
Your script can be changed into
sum=0
for NUM in ${NUMBERS}
do
sleep 1
((counter++))
(( sum += NUM ))
echo "Digit ${counter}: Sum=$sum"
done
echo Sum=$sum
Another way is using bc, usefull for input like 1.6 2.3
sed 's/ /+/g' <<< "${NUMBERS}" | bc
Set two variables n and m, store their sum in $x, print it:
n=5 m=7 x=$((n + m)) ; echo $x
Output:
12
The above syntax is POSIX compatible, (i.e. works in dash, ksh, bash, etc.); from man dash:
Arithmetic Expansion
Arithmetic expansion provides a mechanism for evaluating an arithmetic
expression and substituting its value. The format for arithmetic expan‐
sion is as follows:
$((expression))
The expression is treated as if it were in double-quotes, except that a
double-quote inside the expression is not treated specially. The shell
expands all tokens in the expression for parameter expansion, command
substitution, and quote removal.
Next, the shell treats this as an arithmetic expression and substitutes
the value of the expression.
Two one-liners that do most of the job in the OP:
POSIX:
while read x ; do echo $(( $(echo $x | tr ' ' '+') )) ; done
bash:
while read x ; do echo $(( ${x// /+} )) ; done
bash with calc, (allows summing real, rational & complex numbers, as well as sub-operations):
while read x ; do calc -- ${x// /+} ; done
Example input line, followed by output:
-8!^(1/3) 2^63 -1
9223372036854775772.7095244707464171953

Compare Decimals in Bash while Loop

In the below code, ShellCheck throws an error in the while clause.
count=10.0
while [ $count -le 20.0 ]
do
echo "Hello"
count=$(bc<<< "scale=4; (count+0.1)")
done
ShellCheck says:
Decimals not supported, either use integers or bc
I am not quite sure how to use bc in a while loop.
while [ $(bc <<< "scale=4; (count -le 20.0)" ]
How do I compare decimal numbers in a while clause? Any advice?
Bash doesn't support floating point arithmetic.
You can either use bc:
count="10.0"
limit="12.0"
increment="0.1"
while [ "$(bc <<< "$count < $limit")" == "1" ]; do
echo "Hello"
count=$(bc <<< "$count+$increment")
done
or awk:
while awk 'BEGIN { if ('$count'>='$limit') {exit 1}}'; do
echo "Hello"
count=$(bc <<< "$count+$increment")
done
I just wonder: why not (directly) count from 10.0 to 12.0 ?
for i in $(seq 10.0 0.1 12.0); do
echo "Hello"
done
Bash doesn't support floating pointing arithmetic. You can use bc for that comparison too:
count=10.0
while : ;
do
out=$(bc -l<<< "$count<20.0")
[[ $out == 0 ]] && { echo "Reached limit" ; exit 0; }
echo "Hello"
count=$(bc<<< "scale=4; ($count+0.1)")
done
Note that I added the missing $ to count inside the loop where you update count.
While bash doesn't handle floating point numbers, the seq utility does. [Note 1]
The basic syntax is seq FIRST INCREMENT LAST, so in your case you could use
for count in "$(seq 10.0 0.1 20.0)"; do
# something with $count
done
If you provide two arguments, they are assumed to be FIRST and LAST, with INCREMENT being 1. If you provide only one argument, it is assumed to be LAST, with both FIRST and INCREMENT being 1. As in your example, the sequence is inclusive so both FIRST and LAST will be produced provided that INCREMENT evenly divides FIRST−LAST.
You can also include an explicit format:
$ seq -f "%06.3f" 1 .5 2
01.000
01.500
02.000
One downside of this technique is that it precomputes the entire collection of values. If the loop will execute hundreds of thousands of times, that might use up a lot of memory, in which case you could use a pipe or process substitution instead:
while read count; do
# something with count
done < <(seq 10.0 0.000001 20.0)
Notes
seq is not Posix but it is almost always present; it's part of GNU coreutils and a similar utility, available in Mac OS X) has been in NetBSD since 3.0 and FreeBSD since 9.0.

I am getting expr syntax errors in bash shell for a simple program

#!/bin/bash
clear
echo "Enter a number"
read a
s = 0
while [ $a -gt 0 ]
do
r = ` expr $a % 10 `
s = ` expr $s + $r `
a = ` expr $a / 10 `
done
echo "sum of digits is = $s"
This is my code guys .
I am getting a bunch of expr syntax errors.
I am using the bash shell.
Thanks!
Your error is caused by the spaces surrounding the = in the assignments, the following replacements should work (I prefer $() to using backticks since they're much easier to nest):
s=0
r=$(expr $a % 10)
s=$(expr $s + $r)
a=$(expr $a / 10)
For example, s = 0 (with the spaces) does not set the variable s to zero, rather it tries to run the command s with the two arguments, = and 0.
However, it's not really necessary to call the external expr1 to do mathematical manipulation and capture the output to a variable. That's because bash itself can do this well enough without resorting to output capture (see ARITHMETIC EVALUATION in the bash man page):
#!/bin/bash
clear
read -p "Enter a number: " number
((sum = 0))
while [[ $number -gt 0 ]]; do
((sum += number % 10))
((number /= 10))
done
echo "Sum of digits is $sum"
You'll notice I've made some other minor changes which I believe enhances the readability, but you could revert back to the your original code if you wish and just use the ((expression)) method rather than expr.
1 If you don't mind calling external executables, there's no need for a loop in bash, you could instead use sneakier methods:
#!/bin/bash
clear
read -p "Enter a number: " number
echo "Sum of digits is $(grep -o . <<<$number | paste -sd+ | bc)"
But, to be brutally honest, I think I prefer the readable solution :-)

BASH - Refer to parameters using variable

I was trying to make a small script that calculates a binary number to decimal. It works like this:
-Gets '1' or '0' digits as SEPARATE parameters. e.g. "./bin2dec 1 0 0 0 1 1 1".
-For each parameter digit: if it is '1', multiplies it with the corresponding power of 2 (in the above case, the most left '1' will be 64), then adds it in a 'sum' variable.
Here's the code (it is wrong in the noted point):
#!/bin/bash
p=$((2**($#-1))) #Finds the power of two for the first parameter.
sum=0 #The sum variable, to be used for adding the powers of two in it.
for (( i=1; i<=$#; i++ )) #Counts all the way from 1, to the total number of parameters.
do
if [ $i -eq 1 ] # *THIS IS THE WRONG POINT* If parameter content equals '1'...
then
sum=$(($sum+$p)) #...add the current power of two in 'sum'.
fi
p=$(($p/2)) #Divides the power with 2, so to be used on the next parameter.
done
echo $sum #When finished show the 'sum' content, which is supposed to be the decimal equivalent.
My question is in the noted point (line #10, including blank lines). There, I’m trying to check if EACH parameter's content equals 1. How can I do this using a variable?
For example, $1 is the first parameter, $2 is the 2nd and so on. I want it to be like $i, where 'i' is the variable that is increased by one each time so that it matches the next parameter.
Among other things, I tried this: '$(echo "$"$i)' but didn't work.
I know my question is complicated and I tried hard to make it as clear as I could.
Any help?
How about this?
#!/usr/bin/bash
res=0
while [[ $# -gt 0 ]]
do
res=$(($res * 2 + $1))
shift
done
echo $res
$ ./bin2dec 1 0 0 1
9
shift is a command that moves every parameter passed to the script to the left, decreasing it's value by 1, so that the value of $2 is now at $1 after a shift. You can actually a number with shift as well, to indicate how far to shift the variables, so shift is like shift 1, and shift 2 would give $1 the value that used to be at $3.
How about this one?
#!/bin/bash
p=$((2**$#))
while(($#)); do
((sum+=$1*(p/=2)))
shift
done
echo "$sum"
or this one (that goes in the other direction):
#!/bin/bash
while(($#)); do
((sum=2*sum+$1))
shift
done
echo "$sum"
Note that there are no error checkings.
Please consider fedorqui's comment: use echo $((2# binary number )) to convert from binary to decimal.
Also note that this will overflow easily if you give too many arguments.
If you want something that doesn't overflow, consider using dc:
dc <<< "2i101010p"
(setting input radix to 2 with 2i and putting 101010 on the stack and printing it).
Or if you like bc better:
bc <<< "ibase=2;101010"
Note that these need the binary number to be entered as one argument, and not as you wanted with all digits separated. If you really need all digits to be separated, you could also use these funny methods:
Pure Bash.
#!/bin/bash
echo $((2#$(printf '%s' "$#")))
With dc.
#!/bin/bash
dc <<< "2i$(printf '%s' "$#")p"
With bc.
#!/bin/bash
bc <<< "ibase=2;$(printf '%s' "$#")"
Abusing IFS, with dc.
#!/bin/bash
( IFS=; echo "2i$*p" ) | dc
Abusing IFS, with bc.
#!/bin/bash
( IFS=; echo "ibase=2;$*" ) | bc
So we still haven't answered your question (the one in your comment of the OP): And to find out if and how can we refer to a parameter using a variable. It's done in Bash with indirect expansion. You can read about it in the Shell Parameter Expansion section of the Bash reference manual. The best thing is to show you in a terminal:
$ a="hello"
$ b=a
$ echo "$b"
a
$ echo "${!b}"
hello
neat, eh? It's similar with positional parameters:
$ set param1 param2 param3
$ echo "$1, $2, $3"
param1, param2, param3
$ i=2
$ echo "$i"
2
$ echo "${!i}"
param2
Hope this helps!
To reference variables indirectly by name, use ${!name}:
i=2
echo "${!i} is now equivalent to $2"
Try this one also:
#!/bin/bash -
( IFS=''; echo $((2#$*)) )

Resources