Simple Math from Bash Command Line
Chances are, most shell scripts you write will require some math operations, even if it’s something as simple as incrementing a variable inside of a loop. As with everything else in Linux, there are multiple ways of accomplishing the same task. Here’s a quick look at some of the options.
Table of Contents
Shell Arithmetic
The simplest and most common arithmetic operation encountered in scripts is addition, such as incrementing a variable inside a loop. The three examples below are just different ways of doing the same thing: adding 1 to the variable j.
j=$((j+1)) ((j=j+1)) let "j=j+1"
I usually go with the second method as the most concise. Here we have a short while loop incrementing variable j and printing it.
j=0; while [ $j -lt 3 ]; do echo $((j=j+1)); done 1 2 3
Here’s an example of addition, subtraction, multiplication, exponentiation, modulo, and division:
a=17; b=3; for i in \+ \- \* \** \% /; do echo "$a $i $b = $((a $i b))"; done 17 + 3 = 20 17 - 3 = 14 17 * 3 = 51 17 ** 3 = 4913 17 % 3 = 2 17 / 3 = 5
As you can see from the last line, Bash doesn’t do floating-point operations, nor does it round up or down the remainder: it just drops it altogether.
Floating-Point Operations
awk
When ignoring decimals is not an option, you have a couple of popular tools: awk and bc. Here’s a quick example of dividing using awk with different precision settings:
a=17; b=3; awk -v a=$a -v b=$b 'BEGIN { printf "%.17g\n", a / b }' 5.666666666666667 a=17; b=3; awk -v a=$a -v b=$b 'BEGIN { printf "%.10g\n", a / b }' 5.666666667
While not designed as a math tool, awk is capable of several other operations:
int(x) -- the nearest integer to x sqrt(x) -- the positive square root of x exp(x) -- the exponential of x (e ^ x) log(x) -- the natural logarithm of positive x rand() -- a random number between zero and one srand(x) -- seed the rand() function sin(x) -- the sine of x cos(x) -- the cosine of x atan2(y,x) -- the arctangent of y/x
Here are a few examples:
a=17; b=3 awk -v a=$a -v b=$b 'BEGIN { print int(a / b) }' 5 awk -v a=$a 'BEGIN { print sqrt(a) }' 4.12311 awk -v a=$a 'BEGIN { print exp(a) }' 2.4155e+07 awk -v a=$a 'BEGIN { print log(a) }' 2.83321 awk 'BEGIN { print rand() }' 0.237788 echo | awk -v a=$a 'BEGIN{srand(a);}{print rand()}' 0.33897 awk -v a=$a 'BEGIN { print sin(a) }' -0.961397 awk -v a=$a 'BEGIN { print cos(a) }' -0.275163 awk -v a=$a -v b=$b 'BEGIN { print atan2(a,b) }' 1.39612
bc
While offering considerable functionality, awk is probably not the best choice for math from the command line. A better option is bc that is commonly used in scripts. Here are examples of addition, subtraction, multiplication, exponentiation, modulo, and division:
a=17; b=3; for i in \+ \- \* \^ \% /; do echo "oldscale=scale;scale=2;$a $i $b"|bc; done 20 14 51 4913 .02 5.66
You probably noticed that the modulo operation returned 0.02, which is incorrect. The reason for this is the scale=2
option I passed to bc. Unfortunately, the scale
should always be set to 0 when calculating modulo using bc:
echo "scale=0;$a % $b"|bc 2
Also, there seems to be an issue with the division operation as well: with the requested precision of two decimal placed (scale=2
), the answer should have been 5.67 and not 5.66. The problem here is that bc does not round a quotient up or down but truncates it instead. You can deal with this in two ways: either increase precision (higher scale
value) or write a separate function, as shown below.
round() { echo $(printf %.$2f $(echo "scale=$2;(((10^$2)*$1)+0.5)/(10^$2)"|bc)) }; # Round up to two decimal places round $a/$b 2 5.67
The bc utility also offers some advanced math libraries when using the -l
flag (which also sets scale
to 20, so keep that in mind):
s(x) The sine of x, x is in radians. c(x) The cosine of x, x is in radians. a(x) The arctangent of x, arctangent returns radians. l(x) The natural log of x. e(x) The exp function of e to the power of x. j(n,x) The bessel function of integer order n of x.
And some examples:
for i in s c a l e; do echo "scale=4;${i}($a)"|bc -l; done -.9614 -.2753 1.5120 2.8332 24154952.7535 echo "scale=4;j($b,$a)"|bc -l .1349 # Calculate pi: echo "4*a(1)" | bc -l 3.14159265358979323844 # Same as above, but rounded up to four decimal places printf "%.4f\n" $(bc -l <<< "4*a(1)") 3.1416 # Square root of 4 echo "e( l(4)/2 )" | bc -l 1.99999999999999999998 # or echo "sqrt(4)" | bc 2 # or rounded up to an integer printf "%.0f\n" $(bc -l <<< "e( l(4)/2 )") 2 # Fifth root of 4 rounded up to two decimal places printf "%.2f\n" $(bc -l <<< "e( l(4)/5 )") 1.32
Symbolic Math
Maxima
Many books have been written about Maxima, so I won’t be going in too deep and skip ahead to examples. Here we’re solving a first-order ordinary differential equation du/dt=e^-t + u, where t=3 and u=-0.2
apt install maxima ... maxima (%i1) diff_eq : 'diff(u,t)- u - exp(-t);' du - t (%o1) -- - u - %e dt (%i3) general_solution : ode2(diff_eq,u,t); - 2 t %e t (%o3) u = (%c - -------) %e 2 (%i4) specific_solution : ic1(general_solution,t = 3, u = -0.2),ratprint:false; (- t) - 6 3 2 t 6 %e ((2 %e - 5) %e + 5 %e ) (%o4) u = - --------------------------------------- 10 (%i5) rhs(specific_solution),t=3,ratsimp; 1 (%o5) - - 5 (%i6) diff_eq,specific_solution,diff,ratsimp; (%o6) 0 (%i7) us : rhs(specific_solution); (- t) - 6 3 2 t 6 %e ((2 %e - 5) %e + 5 %e ) (%o7) - --------------------------------------- 10 (%i8) plot2d(us,[t,0,7],[style,[lines,5]],[ylabel," "],[xlabel,"t0 = 3, u0 = -0.2, du/dt = exp(-t) + u"],[gnuplot_term,dumb]); 0 +--------------------------------------------+ |**** + + + ****+**** + + | -1 |-+ **** +-| -2 |-+ *** +-| | *** | -3 |-+ ** +-| | ** | -4 |-+ ** +-| -5 |-+ ** +-| | ** | -6 |-+ *+-| | ** | -7 |-+ +-| | **| -8 |-+ +-| -9 |-+ +-| | + + + + + + | -10 +--------------------------------------------+ 0 1 2 3 4 5 6 7 t0 = 3, u0 = -0.2, du/dt = exp(-t) + u
Sage
Another popular choice for symbolic math applications is SageMath or just Sage. Just as with Maxima, I am just doing an example (the same differential equation as in the Maxima sample above) to give you a feel of it. A word of caution: this might be a 2-GB install.
apt install sagemath ... sage t = var('t') u = function('u')(t) desolve(diff(u,t)==e^(-t)+u,u,ivar=t,contrib_ode=True) 1/2*(2*_C - e^(-2*t))*e^t f=desolve(diff(u,t)==e^(-t)+u,u,ivar=t,contrib_ode=True,ics=[3,-0.2]); f -1/10*((2*e^3 - 5)*e^(2*t) + 5*e^6)*e^(-t - 6)
As far as I can tell, Sage does not support dumb terminal plotting, and I am not running a graphical desktop on my Linux box, so no plot for you, sorry. But the solution is the same, of course, and so would be the plot.
On occasion, you may need to copy/paste results between Maxima and Sage. The easiest option is to disable the 2-D output in Maxima:
(%i16) specific_solution : ic1(general_solution,t = 3, u = -0.2),ratprint:false; (- t) - 6 3 2 t 6 %e ((2 %e - 5) %e + 5 %e ) (%o16) u = - --------------------------------------- 10 (%i17) display2d:false$ (%i18) specific_solution : ic1(general_solution,t = 3, u = -0.2),ratprint:false; (%o18) u = -(%e^((-t)-6)*((2*%e^3-5)*%e^(2*t)+5*%e^6))/10 # And now you can copy/paste this solution into Sage to get the same format # as the original Sage solution produced: sage: -(e^((-t)-6)*((2*e^3-5)*e^(2*t)+5*e^6))/10 -1/10*((2*e^3 - 5)*e^(2*t) + 5*e^6)*e^(-t - 6)
If your requirements exceed the capabilities offered by any of these tools, you’re probably too advanced for me. You may have to cough up a few hundred bucks and get yourself either Maple or Mathematica – both are available for Linux.