How to do exponentiation in Bash - bash

I tried
echo 10**2
which prints 10**2. How to calculate the right result, 100?

You can use the let builtin:
let var=10**2 # sets var to 100.
echo $var # prints 100
or arithmetic expansion:
var=$((10**2)) # sets var to 100.
Arithmetic expansion has the advantage of allowing you to do shell arithmetic and then just use the expression without storing it in a variable:
echo $((10**2)) # prints 100.
For large numbers you might want to use the exponentiation operator of the external command bc as:
bash:$ echo 2^100 | bc
1267650600228229401496703205376
If you want to store the above result in a variable you can use command substitution either via the $() syntax:
var=$(echo 2^100 | bc)
or the older backtick syntax:
var=`echo 2^100 | bc`
Note that command substitution is not the same as arithmetic expansion:
$(( )) # arithmetic expansion
$( ) # command substitution

Various ways:
Bash
echo $((10**2))
Awk
awk 'BEGIN{print 10^2}' # POSIX standard
awk 'BEGIN{print 10**2}' # GNU awk extension
bc
echo '10 ^ 2' | bc
dc
dc -e '10 2 ^ p'

Actually var=$((echo 2^100 | bc)) doesn't work - bash is trying to do math inside (()). But a
command line sequence is there instead so it creates an error
var=$(echo 2^100 | bc) works as the value is the result of the command line executing inside
()

Related

Using bc in bash for loop array doesn't calculate first input?

I'm struggling to pass a multi-row array of floating-point numbers through a for loop, have it run a calculation, then define a new variable from the outputs.
Here's a simplified version of my Bash:
inputs=$(echo "12.12
34.34")
New_array=$( for var in "${inputs[#]}"; do
echo "${var}*2"| bc -l;
done )
I would expect the result from echo "$New_array" to be this:
$ echo "$New_array"
24.24
68.68
But I get this?
$ echo "$New_array"
12.12
68.68
Or whilst I've been troubleshooting (e.g. removing the $New_array variable):
(standard_in) 1: syntax error
I believe the problem has something to do with line return being read as an input for the first loop? But the solutions I've tried haven't worked so far.
Where am I going wrong?
This is probably what you are trying to do:
#!/bin/bash
inputs=(12.12 34.34)
for var in "${inputs[#]}"; do
New_array+=("$(bc -l <<< "$var * 2")")
done
printf '%s\n' "${New_array[#]}"
Note that inputs=(12.12 34.34) creates an array named inputs having elements 12.12 and 34.34. New_array+=(...) appends an element (here, the output of the command bc -l <<< "$var * 2") to the array New_array. Alternatively, this could be done at once using printf's implicit loop and mapfile builtin of bash:
mapfile -t New_array < <(printf '%s * 2\n' "${inputs[#]}" | bc -l)
If it is guaranteed that the elements of the array don't contain whitespace and glob characters (this is probably the case here), then this could be an alternative way:
New_array=($(printf '%s * 2\n' "${inputs[#]}" | bc -l))

variable error in bash when doing calculation

I assigned output of piping into a variable, but when I try to use the variable to do math, it won't allow me:
%%bash
cd /data/ref/
grep -v ">" EN | wc -c > ref
cat ref
cd /example/
grep -v ">" SR | wc -l > sample
cat sample
echo $((x= cat sample, y= cat ref, u=x/y, z=u*100))
I get this error:
41858
38986
bash: line 7: x= cat sample, y= cat ref, u=x/y, z=u*100: syntax error in expression (error token is "sample, y= cat ref, u=x/y, z=u*100"
You received that error because you passed an invalid arithmetic expression into a bash arithetic expansion. Only an arithmetic expression is allowed for this place. What you try to do seems like this:
ref="$(grep -v ">" /data/ref/EN | wc -c)"
sample="$(grep -v ">" /example/SR | wc -l)"
# this is only integer division
#u=$(( sample / ref ))
#z=$(( 100 * u ))
# to do math calculations, you can use bc
u=$(bc <<< "scale=2; $sample/$ref")
z=$(bc <<< "scale=2; 100*$u")
printf "%d, %d, %.2f, %.2f\n" "$ref" "$sample" "$u" "$z"
so hopefully you get an output like this:
41858, 38986, 0.93, 93.00
Notes:
There is no need to cd before executing a grep, it accepts the full path with the target filename as an argument. So without changing directory, you can grep various locations.
In order to save the output of your command (which is only a number) you don't need to save it in a file and cat the file. Just use the syntax var=$( ) and var will be assigned the output of this command substitution.
Have in mind that / will result to 0 for the division 38986/41858 because it's the integer division. If you want to do math calculations with decimals, you can see this post for how to do them using bc.
To print anything, use the shell builtin printf. Here the last two numbers are formatted with 2 decimal points.

How can I increment an infix variable in Bash?

I have a string foo-0 that I want to convert to bar1baz, i.e., parse the trailing index and add a prefix/suffix. The part before the trailing index (in this case foo- can also contain numeric characters, but those should not be changed.
I tried the following:
echo foo-0 | cut -d'-' -f 2 | sed 's/.*/bar&baz/'
but that gives me only a partial solution (bar0baz). How can I increment the infix variable?
EDIT: the solutions below only work partially for what I am trying to achieve. This is my fault because I simplified the example above too much for the sake of clarity.
The final goal is to set an environmental variable (let's call it MY_ENV) to the output value using bash with the following syntax:
/bin/sh -c "echo $var | ... (some bash magic to replace the trailing index) | ... (some bash magic to set MY_ENV=the output of the pipe)"
Side note: The reason I am using /bin/sh -c "..." is because I want to use the command in a Kubernetes YAML.
Partial solution (using awk)
This works:
echo foo-0 | awk -F- '{print "bar" $2+1 "baz"}'
This doesn't (output is 1baz):
/bin/sh -c "echo foo-0 | awk -F- '{print \"bar\" $2+1 \"baz\"}'
Partial solution (using arithmetic context and parameter expansion)
$ var=foo-0
$ echo "bar$((${var//[![:digit:]]}+1))baz"
This does not work if var contains other numeric characters before the trailing index (e.g. for var foo=r2a-foo-0.
You may use awk:
awk -F- '{print "bar" $2+1 "baz"}' <<< 'foo-0'
bar1baz
You could use an arithmetic context and parameter expansion:
$ var=foo-0
$ echo "bar$((${var//[![:digit:]]}+1))baz"
bar1baz
Unrolled, from the inside:
${var//[![:digit:]]} removes all non-digits from var:
$ echo "${var//[![:digit:]]}"
0
$((blah+1)) adds 1 to the variable blah:
$ blah=0
$ echo "$((blah+1))"
1
or, instead of blah, we can use the result of the inner substitution:
$ echo "$(( ${var//[![:digit:]]} + 1 ))"
1
and finally, putting this between bar and baz, you get bar1baz.
Amending for the other case brought up: assuming there might be other digits and we want to increment only the trailing ones, e.g.,
var=2a-foo-21
To do this, we can use nested parameter expansion with extended globs (shopt -s extglob) and the +(pattern) pattern, which matches one or more of pattern. Observe:
$ echo "${var#"${var%%+([[:digit:]])}"}"
21
The outer expansion is ${var#pattern}, which removes the shortest match of pattern from the beginning of $var. For pattern, we use
"${var%%+([[:digit:]])}"
which is "remove the longest match of +([[:digit:]]) (one or more digits) from the end of $var". This leaves us with just the trailing digits, and incrementing them and adding string before and after looks something like this:
$ echo "bar$((${var#"${var%%+([[:digit:]])}"}+1))baz"
bar22baz
This is so unreadable that I'd suggest using regex instead:
$ re='([[:digit:]]+)$'
$ [[ $var =~ $re ]]
$ echo "bar$((${BASH_REMATCH[1]}+1))baz"
bar22baz

Bash how to store a division to a variable

I am trying to calculate below formula and store the value to a variable.
The pseudo code should look like:
a=10
b=5
c=$(((($a-$b)/52)) | bc -l)
echo $c
The result is empty. I couldn't figure out the syntax using bc. Please help me use bc instead of awk or other method.
There are two things you need to be aware of. The first is that bc uses standard input for expressions so you would need to actually pipe your expression through it, or use the <<< redirection operator, one of:
c=$(echo "($a - $b) / 52" | bc)
c=$(bc <<< "($a - $b) / 52")
The <<< method is specific to bash and ksh (an possibly others, but I'm not really au fait with them). The other method can be used in most shells.
Secondly, you should be careful when using big numbers for this since bc has the annoying habit of splitting them across lines:
pax$ x=999999999999999999999999999999999999999999999999999999999999999999999
pax$ echo "$x / 7" | bc
14285714285714285714285714285714285714285714285714285714285714285714\
2
In order to avoid this, you need to change the line length:
pax$ echo "$x / 7" | BC_LINE_LENGTH=0 bc
142857142857142857142857142857142857142857142857142857142857142857142
You can use this:
a=10
b=5
c=$(bc -l <<< "($a-$b)/52")
echo "$c"
.09615384615384615384
Or by setting a scale of 3:
c=$(bc -l <<< "scale=3; ($a-$b)/52")
echo "$c"
.096

How do I avoid the usage of the "for" loop in this bash function?

I am creating this function to make multiple grep's over every line of a file. I run it as following:
cat file.txt | agrep string1 string2 ... stringN
function agrep () {
for a in $#; do
cmd+=" | grep '$a'";
done ;
while read line ; do
eval "echo "\'"$line"\'" $cmd";
done;
}
The idea is to print every line that contains all the strings: string1, string2, ..., stringN. This already works but I want to avoid the usage of the for to construct the expression:
| grep string1 | grep string2 ... | stringN
And if it's possible, also the usage of eval. I tried to make some expansion as follows:
echo "| grep $"{1..3}
And I get:
| grep $1 | grep $2 | grep $3
This is almost what I want but the problem is that when I try:
echo "| grep $"{1..$#}
The expansion doesn't occur because bash cant expand {1..$#} due to the $#. It just works with numbers. I would like to construct some expansion that works in order to avoid the usage of the for in the agrep function.
agrep () {
if [ $# = 0 ]; then
cat
else
pattern="$1"
shift
grep -e "$pattern" | agrep "$#"
fi
}
Instead of running each multiple greps on each line, just get all the lines that match string1, then pipe that to grep for string2, etc. One way to do this is make agrep recursive.
agrep () {
if (( $# == 0 )); then
cat # With no arguments, just output everything
else
grep "$1" | agrep "${#:2}"
fi
}
It's not the most efficient solution, but it's simple.
(Be sure to note Rob Mayoff's answer, which is the POSIX-compliant version of this.)
awk to the rescue!
you can avoid multiple grep calls and constructing the command by switching to awk
awk -v pat='string1 string2 string3' 'BEGIN{n=split(pat,p)}
{for(i=1;i<=n;i++) if($0!~p[i]) next}1 ' file
enter your space delimited strings as in the example above.
Not building a string for the command is definitely better (see chepner's and Rob Mayoff's answers). However, just as an example, you can avoid the for by using printf:
agrep () {
cmd=$(printf ' | grep %q' "$#")
sh -c "cat $cmd"
}
Using printf also helps somewhat with special characters in the patterns. From help printf:
In addition to the standard format specifications described in printf(1),
printf interprets:
%b expand backslash escape sequences in the corresponding argument
%q quote the argument in a way that can be reused as shell input
%(fmt)T output the date-time string resulting from using FMT as a format
string for strftime(3)
Since the aim of %q is providing output suitable for shell input, this should be safe.
Also: You almost always want to use "$#" with the quotes, not just plain $#.

Resources