"Despite producing floating point results. Bash does not support other type of arguments than integers, so you need to rather invoke external tools like bc for your math or stick to integers only." 4.5: syntax error: invalid arithmetic operator (error token is ".5") - but the code still seems to work, why?
Usually I use the bc external tool to fix it, but now I have a function and I do not know where to use it exactly, can you help me?
#!/bin/bash
function crossProduct {
declare -a v1=("${!1}")
declare -a v2=("${!2}")
#Note: Can't pass by reference, so the global variable must be used
vectResult[0]=$(( (v1[1] * v2[2]) - (v1[2] * v2[1]) ))
vectResult[1]=$(( - ((v1[0] * v2[2]) - (v1[2] * v2[0])) ))
vectResult[2]=$(( (v1[0] * v2[1]) - (v1[1] * v2[0]) ))
}
vect1[0]=0.3
vect1[1]=-0.3
vect1[2]=0.1
vect2[0]=0.4
vect2[1]=0.9
vect2[2]=2.3
crossProduct vect1[#] vect2[#]
echo ${vectResult[0]} ${vectResult[1]} ${vectResult[2]}
You can pass references, namely as local -n arr=$1:
$ function _tmp {
local -n arr=$1
for i in ${arr[#]}; do echo $i; done
}
$ TMP=(1 2 3)
$ _tmp TMP
1
2
3
Now to the bc question; it parses a string and returns the value of it. Therefore you should use it as:
# make sure you declare vectResult first.
function crossProduct {
declare -a v1=("${!1}")
declare -a v2=("${!2}")
vectResult[0]=$( echo "(${v1[1]} * ${v2[2]}) - (${v1[2]} * ${v2[1]}) " | bc)
vectResult[1]=$( echo "- ((${v1[0]} * ${v2[2]}) - (${v1[2]} * ${v2[0]}))" | bc )
vectResult[2]=$( echo "(${v1[0]} * ${v2[1]}) - (${v1[1]} * ${v2[0]})" | bc )
}
Combining the two as how I would implement it:
#!/bin/bash
function crossProduct {
local -n v1=$1
local -n v2=$2
local result=()
# You should remain to use bc because Bash only does integers.
result+=($( echo "(${v1[1]} * ${v2[2]}) - (${v1[2]} * ${v2[1]}) " | bc))
result+=($( echo "-((${v1[0]} * ${v2[2]}) - (${v1[2]} * ${v2[0]}))" | bc ))
result+=($( echo "(${v1[0]} * ${v2[1]}) - (${v1[1]} * ${v2[0]})" | bc ))
echo "${result[#]}"
}
vect1[0]=0.3
vect1[1]=-0.3
vect1[2]=0.1
vect2[0]=0.4
vect2[1]=0.9
vect2[2]=2.3
vectResult=($(crossProduct vect1 vect2))
echo ${vectResult[0]} ${vectResult[1]} ${vectResult[2]}
exit 0
This produces -.6 -.6 .3
Related
hello i want to calcul the distance between me and the ISS so this my code
i want to print this sentence :
The ISS is currently located at -30.1461, -50.7975: 10010km from us!
my code :
#!/bin/bash
pi = 3.14
earthRadiumKm=6371
lat2=48.813875
on2=2.392521
com=$( curl -s 'aletum.jails.simplerezo.com/etna-iss.json' | sed s/\"//g | awk\ -v RS =',' -F: '{print $1 $2 $3}' )
lat1=$( echo $com | cut c-60-66)
lon1=$( echo $com | cut c-42-50)
dLat=$( echo "($lat2 - $lat1) * $pi / 180" | bc -l)
dLat=$( echo "($lon2 - $lon1) * $pi / 180" | bc -l)
l1=$( echo "($lat1) * $pi / 180" | bcc -l)
l2=$( echo "($lat2) * $pi / 180" | bcc -l)
a=$( echo "sinus($dLat / 2) * sinus(dLat / 2) + sinus(dLon / 2) * sinus(dLon / 2) * cosine($l1) * consine($l2) | bc -l)
result=$( echo "2 * atan2(sqrt($a), sqrt(1-$a)) * $earthRaduisKm" | bc -1)
echo "The ISS is currently located at $dLat, $dLon, : ${result}KM from us!"
my Problem is my calcul are false because the result echo 0.6 km and that is impossible i thinks it's because i don't know how to use atan2
Fun little project. I have no idea if your math is right, but your code is quite terribly wrong:
no spaces around the = in an assignment: pi = 3.14 => pi=3.14
spelling error for variable names: earthRadiumKm=... => $earthRaduisKm
incorrect bc function names
incorrect system command names: awk\ -v, bcc -l
unclosed quoted string
Refactored:
#!/bin/bash
read lon1 lat1 time < <(
curl -s 'aletum.jails.simplerezo.com/etna-iss.json' |
jq -r '"\(.iss_position.longitude) \(.iss_position.latitude) \(.timestamp)"'
)
lon2=2.392521
lat2=48.813875
{ read dLat; read dLon; read result; } < <(
bc -l <<END
pi = (4*a(1/5) - a(1/239))*4
earthrad = 6371
dlat = ($lat2 - $lat1) * pi/180
dlon = ($lon2 - $lon1) * pi/180
a = s(dlat/2) * s(dlat/2) + s(dlon/2) * s(dlon/2) * c($lat1 * pi/180) * c($lat2 * pi/180)
result = 2 * a( sqrt(a) / sqrt(1-a) ) * earthrad
dlat
dlon
result
END
)
printf "At %s,\n the ISS is currently located at %.4f,%.4f : %.2f KM from us\n" \
"$(date -d "#$time" "+%F %T %Z")" \
"$dLat" "$dLon" "$result"
result
At 2018-09-12 08:53:32 EDT,
the ISS is currently located at 0.5916,-2.2398 : 11296.11 KM from us
Notes:
repeat, I have no idea if your math is right
Process Substitutions to execute some code and read the results into variables.
use jq to parse JSON
I pushed all the math into a single bc call.
bc -l uses s for sine, c for cosine and a for arctan: read the bc man page
bc has no arctan2 function
I use a formula to get pi a little more precise
bc variable names must be all lowercase.
I'm simply trying to multiplying some float variables using bc:
#!/bin/bash
a=2.77 | bc
b=2.0 | bc
for cc in $(seq 0. 0.001 0.02)
do
c=${cc} | bc
d=$((a * b * c)) | bc
echo "$d" | bc
done
And this does not give me an output. I know it's a silly one but I've tried a number of combinations of bc (piping it in different places etc.) to no avail.
Any help would be greatly appreciated!
bc is a command-line utility, not some obscure part of shell syntax. The utility reads mathematical expressions from its standard input and prints values to its standard output. Since it is not part of the shell, it has no access to shell variables.
The shell pipe operator (|) connects the standard output of one shell command to the standard input of another shell command. For example, you could send an expression to bc by using the echo utility on the left-hand side of a pipe:
echo 2+2 | bc
This will print 4, since there is no more here than meets the eye.
So I suppose you wanted to do this:
a=2.77
b=2.0
for c in $(seq 0. 0.001 0.02); do
echo "$a * $b * $c" | bc
done
Note: The expansion of the shell variables is happening when the shell processes the argument to echo, as you could verify by leaving off the bc:
a=2.77
b=2.0
for c in $(seq 0. 0.001 0.02); do
echo -n "$a * $b * $c" =
echo "$a * $b * $c" | bc
done
So bc just sees numbers.
If you wanted to save the output of bc in a variable instead of sending it to standard output (i.e. the console), you could do so with normal command substitution syntax:
a=2.77
b=2.0
for c in $(seq 0. 0.001 0.02); do
d=$(echo "$a * $b * $c" | bc)
echo "$d"
done
To multiply two numbers directly, you would do something like:
echo 2.77 * 2.0 | bc
It will produce a result to 2 places - the largest number of places of the factors. To get it to a larger number of places, like 5, would require:
echo "scale = 5; 2.77 * 2.0" | bc
This becomes more important if you're multiplying numerals that each have a large number of decimal places.
As stated in other replies, bc is not a part of bash, but is a command run by bash. So, you're actually sending input directly to the command - which is why you need echo. If you put it in a file (named, say, "a") then you'd run "bc < a". Or, you can put the input directly in the shell script and have a command run the designated segment as its input; like this:
cat <<EOF
Input
EOF
... with qualifiers (e.g. you need to write "" as "\", for instance).
Control flow constructs may be more problematic to run in BC off the command line. I tried the following
echo "scale = 6; a = 2.77; b = 2.0; define f(cc) { auto c, d; c = cc; d = a*b*c; return d; } f(0); f(0.001); f(0.02)" | bc
and got a syntax error (I have a version of GNU-BC installed). On the other hand, it will run fine with C-BC
echo "scale = 6; a = 2.77; b = 2.0; define f(cc) { auto c, d; c = cc; d = a * b * c; return d; } f(0); f(0.001); f(0.02)" | cbc
and give you the expected result - matching the example you cited ... listing numbers to 6 places.
C-BC is here (it's operationally a large superset of GNU-BC and UNIX BC, but not 100% POSIX compliant):
https://github.com/RockBrentwood/CBC
The syntax is closer to C, so you could also write it as
echo "scale = 6, a = 2.77, b = 2.0; define f(cc) { return a * b * cc; } f(0); f(0.001); f(0.02)" | cbc
to get the same result. So, as another example, this
echo "scale = 100; for (x = 0, y = 1; x < 50; y *= ++x); y" | cbc
will give you 50 factorial. However, comma-expressions, like (x = 0, y = 1) are not mandated for bc by POSIX, so it will not run in other bc dialects, like GNU BC.
I'm writing a shell script (Bash on Mac OS X) to rename a bunch of image files. I want the results to be:
frame_001
frame_002
frame_003
etc.
Here is my code:
let framenr=$[1 + (y * cols * resolutions) + (x * resolutions) + res]
echo $framenr:
let framename=$(printf 'frame_%03d' $framenr)
echo $framename
$framenr looks correct, but $framename always becomes 0. Why?
The let command forces arithmetic evaluation, and the referenced "variable" does not exist, so you get the default value 0.
y=5
x=y; echo $x # prints: y
let x=y; echo $x # prints: 5
Do this instead:
framenr=$(( 1 + (y * cols * resolutions) + (x * resolutions) + res ))
echo $framenr:
# or
framename=$(printf 'frame_%03d' $framenr)
echo $framename
And there's printf -v to avoid subshell invocation:
# with bash 3.1+
printf -v framename 'frame_%03d' $framenr
See the manual for printf -v, available from bash 3.1+.
I recall reading somewhere that $[ ] is deprecated. Use $(( )) instead.
Title says it all really, but I'm currently using a simple function with a case statement to convert human-readable file size strings into a size in bytes. It works well enough, but it's a bit unwieldy for porting into other code, so I'm curious to know if there are any widely available commands that a shell script could use instead?
Basically I want to take strings such as "100g" or "100gb" and convert them into bytes.
I'm currently doing the following:
to_bytes() {
value=$(echo "$1" | sed 's/[^0123456789].*$//g')
units=$(echo "$1" | sed 's/^[0123456789]*//g' | tr '[:upper:]' '[:lower:]')
case "$units" in
t|tb) let 'value *= 1024 * 1024 * 1024 * 1024' ;;
g|gb) let 'value *= 1024 * 1024 * 1024' ;;
m|mb) let 'value *= 1024 * 1024' ;;
k|kb) let 'value *= 1024' ;;
b|'') let 'value += 0' ;;
*)
value=
echo "Unsupported units '$units'" >&2
;;
esac
echo "$value"
}
It seems a bit overkill for something I would have thought was fairly common for scripts working with files; common enough that something might exist to do this more quickly.
If there are no widely available solutions (i.e - majority of unix and linux flavours) then I'd still appreciate any tips for optimising the above function as I'd like to make it smaller and easier to re-use.
See man numfmt.
# numfmt --from=iec 42 512K 10M 7G 3.5T
42
524288
10485760
7516192768
3848290697216
# numfmt --to=iec 42 524288 10485760 7516192768 3848290697216
42
512K
10M
7.0G
3.5T
toBytes() {
echo $1 | echo $((`sed 's/.*/\L\0/;s/t/Xg/;s/g/Xm/;s/m/Xk/;s/k/X/;s/b//;s/X/ *1024/g'`))
}
Here's something I wrote. It supports k, KB, and KiB. (It doesn't distinguish between powers of two and powers of ten suffixes, though, as in 1KB = 1000 bytes, 1KiB = 1024 bytes.)
#!/bin/bash
parseSize() {(
local SUFFIXES=('' K M G T P E Z Y)
local MULTIPLIER=1
shopt -s nocasematch
for SUFFIX in "${SUFFIXES[#]}"; do
local REGEX="^([0-9]+)(${SUFFIX}i?B?)?\$"
if [[ $1 =~ $REGEX ]]; then
echo $((${BASH_REMATCH[1]} * MULTIPLIER))
return 0
fi
((MULTIPLIER *= 1024))
done
echo "$0: invalid size \`$1'" >&2
return 1
)}
Notes:
Leverages bash's =~ regex operator, which stores matches in an array named BASH_REMATCH.
Notice the cleverly-hidden parentheses surrounding the function body. They're there to keep shopt -s nocasematch from leaking out of the function.
don't know if this is ok:
awk 'BEGIN{b=1;k=1024;m=k*k;g=k^3;t=k^4}
/^[0-9.]+[kgmt]?b?$/&&/[kgmtb]$/{
sub(/b$/,"")
sub(/g/,"*"g)
sub(/k/,"*"k)
sub(/m/,"*"m)
sub(/t/,"*"t)
"echo "$0"|bc"|getline r; print r; exit;}
{print "invalid input"}'
this only handles single line input. if multilines are needed, remove the exit
this checks only pattern [kgmt] and optional b. e.g. kib, mib would fail. also currently is only for lower-case.
e.g.:
kent$ echo "200kb"|awk 'BEGIN{b=1;k=1024;m=k*k;g=k^3;t=k^4}
/^[0-9.]+[kgmt]?b?$/&&/[kgmtb]$/{
sub(/b$/,"")
sub(/g/,"*"g)
sub(/k/,"*"k)
sub(/m/,"*"m)
sub(/t/,"*"t)
"echo "$0"|bc"|getline r
print r; exit
}{print "invalid input"}'
204800
Okay, so it sounds like there's nothing built-in or widely available, which is a shame, so I've had a go at reducing the size of the function and come up with something that's only really 4 lines long, though it's a pretty complicated four lines!
I'm not sure if it's suitable as an answer to my original question as it's not really what I'd call the simplest method, but I want to put it up in case anyone thinks it's a useful solution, and it does have the advantage of being really short.
#!/bin/sh
to_bytes() {
units=$(echo "$1" | sed 's/^[0123456789]*//' | tr '[:upper:]' '[:lower:]')
index=$(echo "$units" | awk '{print index ("bkmgt kbgb mbtb", $0)}')
mod=$(echo "1024^(($index-1)%5)" | bc)
[ "$mod" -gt 0 ] &&
echo $(echo "$1" | sed 's/[^0123456789].*$//g')"*$mod" | bc
}
To quickly summarise how it works, it first strips the number from the string given and forces to lowercase. It then use awk to grab the index of the extension from a structured string of valid suffixes. The thing to note is that the string is arranged to multiples of five (so it would need to be widened if more extensions are added), for example k and kb are at indices 2 and 7 respectively.
The index is then reduced by one and modulo'd by five so both k and kb become 1, m and mb become 2 and so-on. That's then used to raised 1024 as a power to get the size in bytes. If the extension was invalid this will resolve to a value of zero, and an extension of b (or nothing) will evaluate to 1.
So long as mod is greater than zero the input string is reduced to only the numeric part and multiplied by the modifier to get the end result.
This is actually how I would probably have solved this originally if I were using a language like PHP, Java etc., it's just a bit of a weird one to put together in a shell script.
I'd still very much appreciate any simplifications though!
Another variation, adding support for decimal values with a simpler T/G/M/K parser for outputs you might find from simpler Unix programs.
to_bytes() {
value=$(echo "$1" | sed -e 's/K//g' | sed -e 's/M//g' | sed -e 's/G//g' | sed -e 's/T//g' )
units=$(echo -n "$1" | grep -o .$ )
case "$units" in
T) value=$(bc <<< "scale=2; ($value * 1024 * 1024 * 1024 * 1024)") ;;
G) value=$(bc <<< "scale=2; ($value * 1024 * 1024 * 1024)") ;;
M) value=$(bc <<< "scale=2; ($value * 1024 * 1024)") ;;
K) value=$(bc <<< "scale=2; ($value * 1024)") ;;
b|'') let 'value += 0' ;;
*)
value=
echo "Unsupported units '$units'" >&2
;;
esac
echo "$value"
}
I know it's really stupid question, but I don't know how to do this in bash:
20 / 30 * 100
It should be 66.67 but expr is saying 0, because it doesn't support float.
What command in Linux can replace expr and do this equalation?
bc will do this for you, but the order is important.
> echo "scale = 2; 20 * 100 / 30" | bc
66.66
> echo "scale = 2; 20 / 30 * 100" | bc
66.00
or, for your specific case:
> export ach_gs=2
> export ach_gs_max=3
> x=$(echo "scale = 2; $ach_gs * 100 / $ach_gs_max" | bc)
> echo $x
66.66
Whatever method you choose, this is ripe for inclusion as a function to make your life easier:
#!/bin/bash
function pct () {
echo "scale = $3; $1 * 100 / $2" | bc
}
x=$(pct 2 3 2) ; echo $x # gives 66.66
x=$(pct 1 6 0) ; echo $x # gives 16
just do it in awk
# awk 'BEGIN{print 20 / 30 * 100}'
66.6667
save it to variable
# result=$(awk 'BEGIN{print 20 / 30 * 100}')
# echo $result
66.6667
I generally use perl:
perl -e 'print 10 / 3'
As reported in the bash man page:
The shell allows arithmetic expressions to be evaluated, under certain circumstances...Evaluation is done in fixed-width integers with no check for overflow, though division by 0 is trapped and flagged as an error.
You can multiply by 100 earlier to get a better, partial result:
let j=20*100/30
echo $j
66
Or by a higher multiple of 10, and imagine the decimal place where it belongs:
let j=20*10000/30
echo $j
66666
> echo "20 / 30 * 100" | bc -l
66.66666666666666666600
This is a simplification of the answer by paxdiablo. The -l sets the scale (number of digits after the decimal) to 20. It also loads a math library with trig functions and other things.
Another obvious option:
python -c "print(20 / 30 * 100)"
assuming you are using Python 3. Otherwise, use python3.