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 ...
Related
Background
Beginner here trying to learn some Bash basics.
My question is my attempt at modifying an existing textbook example to include simple input validation. The validation works if one or more inputs are inappropriate but when all three inputs are appropriate, I instead get a syntax error warning and then an answer.
Code is at the end, but below are my thoughts. Appreciate any corrections wherever they are wrong.
Much thanks.
Thoughts
I have three conditions to check for. If any one of them fails, a message is displayed and the program terminates. This works. However, if all 3 conditions are met, then I receive a syntax error.
I thought the error might be related to expansion so I manually ran the commands and supplied hard inputs using the command line. E.g. bc <<< "0.5 > 0" and these seemed to work as intended.
Subsequently, it seems that my problems only arise when I involve the $interest variable with its decimal points. However, I used BC because my understanding is that Bash only does Integers. What else have I missed out?
Code
# loan-calc: script to calculate monthly loan payments
# formulae may be mathematically wrong, example copied from textbook
# reference only for bash scripting, not math
PROGNAME="${0##*/}"
usage () {
cat <<- EOF
Usage: $PROGNAME PRINCIPAL INTEREST MONTHS
Where:
PRINCIPAL is the amount of the loan.
INTEREST is the APR as a number (7% = 0.07)
MONTHS is the length of the loan's term.
EOF
}
read -p "Enter principal amount > " principal
read -p "Enter interest rate (E.g. 7.5% = 0.075) > " interest
read -p "Enter term length in months > " months
# Principal, interest rate, and months must be more than 0.
if (( "$principal <= 0" | bc )) || (( "$months <= 0" | bc )) || (( "$interest <= 0" | bc )); then
usage
exit 1
fi
result=$(bc <<- EOF
scale = 10
i = $interest / 12
p = $principal
n = $months
p * ((i * ((1 + i) ^ n)) / (((1 + i) ^ n) - 1))
EOF
)
printf "%0.2f\n" $result
Shell Arithmetic
(( 1 | bc )) does not pipe 1 into bc. When you are evaluating expressions inside of (( expression )), | is the bitwise OR operator, not a pipe.
(( bc )) evaluates to 1 every time, so all of your conditional tests are just OR'ing the number with 1, not piping the number into bc.
Your expression inside of the parentheses should be the output from echoing the mathematical string into bc using a pipe, e.g. (( $(echo "$variable <= 0"| bc) )).
This can be wrapped in a named function so the if statement is more readable.
notValid() {
(( $(echo "$1 <= 0" | bc) ))
}
# Principal, interest rate, and months must be more than 0.
if notValid "$principal" || notValid "$months" || notValid "$interest"; then
usage
exit 1
fi
I would like to compare a number, which is the output of a command, with a constant and do some manipulation. That is, if $id < 10, I want to see 590$id and if it is above 10, I want to see 59$id.
I found that expr doesn't working here:
ID=3
NUM=59$ID
if [ `expr $ID` -lt 10]; then
NUM=590$ID
fi
echo $NUM
The output of the code is 593 and not 5903. Even, $(($ID + 5900)) -lt 5910 writes 593.
How can I fix that?
Could you please try following.
cat script.sh
#!/bin/bash
ID=$(printf "%02d" 3 )
##NUM=59$ID ##Commented this to check if, condition is getting satisfied or not. Doesn't seem to be fit here.
(( $ID < 10 )) && NUM="59$ID"
echo "$NUM"
Output will be 5903 after running above code.
Don't use expr. It's old and tricky.
Don't use backticks `. They are discouraged and $( ... ) is preferred.
For arithmetic comparisons use arithmetic expansions. Just
if (( ID < 10 )); then
Note that bash is space aware and your script has a syntax error, it is missing a space - the 10]; should be 10 ];.
Note that by convention uppercase variables should be used for exported variables.
Looking at your code I think you just want:
NUM=$((5900 + ID))
I have a side-project in BASH for fun, and I have this code snippet (ARRAY[0] is 8):
while [ $ALIVE == true ]; do
$ARRAY[0] = ${ARRAY[0]} - 1
echo ${ARRAY[0]}
done
However, it comes back with this error:
line 16: 8[1]: command not found
I just started working in BASH, so I might be making an obvious mistake, but I've searched and searched for an answer to a problem like this and came up with no result.
The smallest change is simply:
ARRAY[0]=$(( ${ARRAY[0]} - 1 ))
Note:
No $ before the name of the variable to assign to (foo=, not $foo=)
No spaces around the = on the assignment
$(( )) is the syntax to enter a math context (and expand to the result of that operation).
I need to do an operation but something is wrong in my code in bash
I have 4 variables, km1, km2, km3, km4.
I want to sum the 4 variables except when the value is "CLOSED"
3.200
CLOSED
1.800
0.600
When I do the following sum, there is an error...I thing my variables are not numeric, any help? How can I force them to be numeric and then do the sum?
let km=$km1+$km3+$km4
echo $km
./sum.sh: line 41: let: km=3.200: syntax error: invalid arithmetic operator (error token is ".200")
km1=3.200
km2=CLOSED
km3=1.800
km4=0.600
total=`LC_ALL=C echo "$km1 $km2 $km3 $km4"|awk '{sum += $1+$2+$3+$4}END {print sum}'`
Not that good with awk but i think the above can help. total the is sum of all vars
There are 2 issues with you code. The first one is that you are trying to work with values other than integers. Bash only does integers. You can round up the values to integers using bc (An arbitrary precision calculator language). The second issue is that you are trying to do math on strings. So consider the code below:
#!/bin/bash
km1=3.200;
km2="CLOSED";
km3=1.800;
km4=0.600;
km1=$(echo "$km1/1" | bc)
km3=$(echo "$km3/1" | bc)
km4=$(echo "$km4/1" | bc)
array=($km1 $km2 $km3 $km4)
for i in ${array[#]}; do
case $i in
*[0-9]*)
(( result+=$i ))
esac
done
echo $result
In a text file, test.txt, I have the next information:
sl-gs5 desconnected Wed Oct 10 08:00:01 EDT 2012 1001
I want to extract the hour of the event by the next command line:
hour=$(grep -n sl-gs5 test.txt | tail -1 | cut -d' ' -f6 | awk -F ":" '{print $1}')
and I got "08". When I try to add 1,
14 echo $((hour+1))
I receive the next error message:
./test2.sh: line 14: 08: value too great for base (error token is "08")
If variables in Bash are untyped, why?
See ARITHMETIC EVALUATION in man bash:
Constants with a leading 0 are interpreted as octal numbers.
You can remove the leading zero by parameter expansion:
hour=${hour#0}
or force base-10 interpretation:
$((10#$hour + 1))
what I'd call a hack, but given that you're only processing hour values, you can do
hour=08
echo $(( ${hour#0} +1 ))
9
hour=10
echo $(( ${hour#0} +1))
11
with little risk.
IHTH.
You could also use bc
hour=8
result=$(echo "$hour + 1" | bc)
echo $result
9
Here's an easy way, albeit not the prettiest way to get an int value for a string.
hour=`expr $hour + 0`
Example
bash-3.2$ hour="08"
bash-3.2$ hour=`expr $hour + 0`
bash-3.2$ echo $hour
8
In Short: In order to deal with "Leading Zero" numbers (any 0 digit that comes before the first non-zero) in bash
- Use bc An arbitrary precision calculator language
Example:
a="000001"
b=$(echo $a | bc)
echo $b
Output: 1
From Bash manual:
"bc is a language that supports arbitrary precision numbers with interactive execution
of statements. There are some similarities in the syntax to the C programming lan-
guage. A standard math library is available by command line option. If requested, the
math library is defined before processing any files. bc starts by processing code from
all the files listed on the command line in the order listed. After all files have
been processed, bc reads from the standard input. All code is executed as it is read.
(If a file contains a command to halt the processor, bc will never read from the standard input.)"
Since hours are always positive, and always 2 digits, you can set a 1 in front of it and subtract 100:
echo $((1$hour+1-100))
which is equivalent to
echo $((1$hour-99))
Be sure to comment such gymnastics. :)
The leading 0 is leading to bash trying to interpret your number as an octal number, but octal numbers are 0-7, and 8 is thus an invalid token.
If I were you, I would add some logic to remove a leading 0, add one, and re-add the leading 0 if the result is < 10.
How about sed?
hour=`echo $hour|sed -e "s/^0*//g"`