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.

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.