I'm reading a file with multiple columns, and dumping 2 columns of the file into into 2 different arrays. Now based on a condition, I need to get the difference between 2 values retrieved from the array. So my code looks like this -
if [ condition ]; then
VAL = (( ${local[$x]} - ${local[$y]} ))
fi
The thing is, while I'm able to echo and see both values ${local[$x]} and ${local[$y]}, the subtraction operation gives me a syntax error. I understand it's failing because the values currently held within the array involve floating point decimal values - like 3456712.126758, and the assignment throws errors with the decimal part. I understand arithmetic operations are not a strong point with the bash shell as floating point numbers are considered strings hence the issue.
Could you please help getting the right format please?
Should I do something like this
VAL= awk '{ print ${local[$x]} - ${local[$y]} }'
or
VAL=echo ${local[$x]} - ${local[$y]} | bc -l
I'm sure the syntax above is wrong, kindly help with the syntax, I need it assigned the subtracted result assigned to the field VAL.
Not only floating points, but also the spacing would lead to syntax errors. Bash variable assignments must have no spaces, as in val=x, and not val = x.
Uppercase variable names are reserved for environment variables, and it is recommended to use lowercase instead for your own variables. (Oh, and local is also a reserved word.)
Your assignment wouldn't work with proper spacing, either: the arithmetic expression
var=(( ${vals[$x]} - ${vals[$y]} )) # syntax error near unexpected token `('
is just evaluating its contents, but not returning anything. You could use the part after the = as a condition. To make it return something, you need arithmetic expansion (note the extra $):
var=$(( ${vals[$x]} - ${vals[$y]} )) # works for integers
^
In an arithmetic context, you don't even need to prepend $ to your variables:
var=$(( vals[x] - vals[y] ))
works just as well. Exception: in associative arrays, you still have to do it for indices:
$(( vals[$x] ))
And finally, as you noticed, this all doesn't work for floating point numbers. Instead of piping to bc, you can also use a here string and avoid spawning a subshell:
$ vals=(1.1 2.2)
$ x=0
$ y=1
$ echo $(( local[x] - local[y] )) # No '$' needed for variable expansion
bash: 1.1: syntax error: invalid arithmetic operator (error token is ".1") # But :(
$ bc -l <<< "local[x] - local[y]" # Requires '$' - these expand to nothing
0
$ bc -l <<< "${local[x]} - ${local[y]}" # Works!
-1.1
With awk:
awk -v a=${a} -v b=${b} 'BEGIN{print a - b}'
With bc:
echo "${a} - ${b}" | bc -l
See also other options here.
Related
I am trying to update a variable within a for loop, sequentially adding to the variable, and then printing it to a series of file names.
Actually, I want to sequentially update a series of file, from a tmp file distTmp.RST to sequentially dist1.RST, dist2.RST, etc..
The original distTmp.RST contains a line called "WWW". I want to replace the string with values called 21.5 in dist1.RST, 22.5 in dist2.RST, 23.5 in dist3.RST, etc...
My code is as follows:
#!/bin/bash
wm=1
wM=70
wS=1
F=20.5
for W in {${wm}..${wM}..${wS}}; do
F=$(${F} + 1 | bc)
echo ${F}
sed "s/WWW/"${F}"/g" distTmp.RST > dist${W}.RST
done
echo ${F}
========
But I am getting error message as follows:
change.sh: line 13: 20.5 + 1 | bc: syntax error: invalid arithmetic operator (error token is ".5 + 1 | bc")
Kindly suggest me a solution to the same.
Kindly suggest me a solution to the same.
This might do what you wanted. Using a c-style for loop.
#!/usr/bin/env bash
wm=1
wM=70
wS=1
F=20.5
for ((w=wm;w<=wM;w+=wS)); do
f=$(bc <<< "$F + $w")
echo "$f"
sed "s/WWW/$f/g" distTmp.RST > "dist${w}.RST"
done
The error from your script might be because the order of expansion. brace expansion expansion happens before Variable does.
See Shell Expansion
Use
F=$(echo ${F} + 1 | bc)
instead of F=$((${F} + 1 | bc)). The doubled-up parentheses are what caused your error. Double parentheses weren't in the original code, but I get a different error saying 20.5: command not found with the original code, so I tried doubling the parentheses and get the error in the question. Apparently, floating point numbers aren't supported by $(()) arithmetic evaluation expressions in Bash.
Given a shell variable whose value is a semantic version, how can I create another shell variable whose value is (tuple 1 × 1000000) + (tuple 2 × 1000) + (tuple 3) ?
E.g.
$ FOO=1.2.3
$ BAR=#shell magic that, given ${FOO} returns `1002003`
# Shell-native string-manipulation? sed? ...?
I'm unclear about how POSIX-compliance vs. shell-specific syntax comes into play here, but I think a solution not bash-specific is preferred.
Update: To clarify: this isn't as straightforward as replacing "." with zero(es), which was my initial thought.
E.g. The desired output for 1.12.30 is 1012030, not 100120030, which is what a .-replacement approach might provide.
Bonus if the answer can be a one-liner variable-assignment.
A perl one-liner:
echo $FOO | perl -pne 's/\.(\d+)/sprintf "%03d", $1/eg'
How it works:
perl -pne does a REPL with the supplied program
The program contains a replacement function s///
The search string is the regex \.(\d+) which matches a string beginning with dot and ends with digits and capture those digits
The e modifier of the s/// function evaluates the right-hand side of the s/// replacement as an expression. Since we captured the digits, they'll be converted into int and formatted into leading zeros with sprintf
The g modifier replaces all instances of the regex in the input string
Demo
Split on dots, then loop and multiply/add:
version="1.12.30"
# Split on dots instead of spaces from now on
IFS="."
# Loop over each number and accumulate
int=0
for n in $version
do
int=$((int*1000 + n))
done
echo "$version is $int"
Be aware that this treats 1.2 and 0.1.2 the same. If you want to always treat the first number as major/million, consider padding/truncating beforehand.
This should do it
echo $foo | sed 's/\./00/g'
How about this?
$ ver=1.12.30
$ foo=$(bar=($(echo $ver|sed 's/\./ /g')); expr ${bar[0]} \* 1000000 + ${bar[1]} \* 1000 + ${bar[2]})
$ echo $foo
1012030
I have the below script which is trying to get the pass percentage through shell script.
Script:
n= "$pass"/"$total";
"$n" * = 100;
echo "$n"
Output:
/tmp/jenkins3601870177535319162.sh: line 45: 20/25*=: No such file or
directory 20/25
I'm not sure the above calculation is correct. But I just have variable $pass which is having pass test case count and $total variable which had total test case count. Just wanna get percentage of the passed test cases using shell.
You have two primary problems: (1) there cannot be any spaces on either side of the '=' sign; and (2) shell uses integer math so 20/25 will equal 0.
The POSIX arithmetic operator is ((...)) where your expression goes within ((...)). Also, within ((...)) there is no need to derefernce the variable name by preceding it with a '$'.
To assign the result of the expression to a variable you precede the operator by the '$', e.g. n=$((pass/total)).
To get around the fact that shell uses integer math, you can multiply pass by 100 before you divide and at least get a whole-number percentage. For example:
#!/bin/sh
pass=20
total=25
n=$(((pass * 100)/total))
printf "pass percentage: %d\n" "$n"
If you run the script you will then get:
$ sh percent.sh
pass percentage: 80
Where 80% is what would be the percent result for 20/25. Look things over and let me know if you have further questions.
I'm trying to generate some quasi random numbers to feed into a monte carlo simulation. I'm using bash. I seem to have hit a syntax error which I've narrowed down to being in this bit of code.
randno4=($RANDOM % 100001)
upper_limit4=$(echo "scale=10; 1*75.3689"|bc)
lower_limit4=$(echo "scale=10; 1*75.1689"|bc)
range4=$(echo "scale=10; $upper_limit4-$lower_limit4"|bc)
t_twall=`echo "scale=10; ${lower_limit4}+${range4}*${randno3}/100001" |bc`
echo "$t_twall"
Does anyone know why I the below output and not a value between 75.3689 and 75.1689 as that is what I would be expecting?
(standard_in) 1: syntax error
The first line should looks like :
randno4=$((RANDOM % 100001))
(( )) is bash arithmetic, with the leading $ , the value is substituted : $(( ))
When you wrote
randno4=( )
you try to feed an ARRAY with a arithmetic expression with the wrong syntax.
See http://wiki.bash-hackers.org/syntax/arith_expr
And finally, like Etan Reisner said, You also use $randno3 in the t_twall assignment line which is undefined
Why can't I do this simple arithmetic operation and store it in a variable in bash shell? I've been struggling with this and playing around with () and $ symbols but no luck.
read t
let r=$(5/9)*$($t-32)
I get a: let: r=*: syntax error: operand expected (error token is "*")
When you are using the let statement, you don't need the dollar-sign, but single-quote the expression instead to keep the shell preprocessor from messing with your operators. Note that bash does not seem to be able to handle numbers which aren't integers, so the (5/9) expression will always be zero. Try the second let statement.
read -p 'Temp in Fahrenheit (no decimals): ' t
# let r='(5/9)*(t-32)' -- this doesn't work
let r='5*(t-32)/9'
echo "Centigrade: $r"
Try that :
read -p 'Type integer temp (Fahrenheit) >>> ' int
echo "$(( 5 * ( int - 32 ) / 9 )) Celcius"