Performing Math Calculation in Bash

  • HOME
  • >
  • BASH
  • >
  • Performing Math Calculation in Bash
Last Updated: 
Tags:  bash math bc awk

Using Math in Bash scripts becomes a necessary evil when writing complex crontab reports, monitoring plugins, setup scripts with dynamic configurations, or any other kind of automation like showing a Raspbery PI CPU temperature. There is always some type of calculations to be made.

This post cover a few examples on how to do basic mathematical operations (elementary arithmetic like multiplication and addition) in Bash with integers or floating-points numbers.

Introduction: Integer and Floating Points

Before we get into the details on how to do Math in Bash, remember that an integer is a whole number that is not a fraction and is anywhere from zero to positive or negative infinity. For example 42, 36, and -12 are integers, while 3.14 and √2 are not. The set of integers include all negative whole numbers, zero, and all positive whole numbers. An integer and its opposite are the same distance from zero.

A floating-point number is a computing programming term to represent a real number with fractional part. For example 3.14, √2, -10.5, 4e-2 are floating points numbers. A floating-point is specified by a base (binary or decimal), a precision, and an exponent range. It is usually in binary made of 32 (simple precision) or 64 bits (double precision), thought the IEEE 754 standard mention more formats.

Format Total bits Significand bits Exponent bits Smallest number Largest number
Single precision 32 23 + 1 sign 8 ≈ 1.2 ⋅ 10-38 ≈ 3.4 ⋅ 1038
Double precision 64 52 + 1 sign 11 ≈ 2.2 ⋅ 10-308 ≈ 1.8 ⋅ 10308

You may see a floating-point being represented in the form of significand x base exponent. For example: 3.14 = 314 x 10-2

Doing Math in Bash with Integer

Using expr command line

The legacy way to do math calculations with integer, and only integer, has been for a long time to use the expr (evaluate expression) command line. Though, this method can be slow as it is not a shell builtin and will fork new process, hence not ideal in a large for-loop. Also, the expr behavior may vary between systems depending on the implementation of the tool.

# Substraction
[me@host ~]$ expr 1 + 1
# Addition
[me@host ~]$ expr 1 + 1
# Assign result to a variable
[me@host ~]$ myvar=$(expr 1 + 1)
[me@host ~]$ echo $myvar
# Addition with a variable
[me@host ~]$ expr $myvar + 1
# Division
[me@host ~]$ expr $myvar / 3
# Multiplication
[me@host ~]$ expr $myvar \* 3

⚠️ When doing a multiply by make sure to backslash (\) the asterisk (*) as it is also a wildcard used for expansion in Bash.

Using let builtin command

An alternative to expr command, is to use the Bash builtin command let which as a large list of supported operators.

id++, id-- variable post-increment, post-decrement
++id, --id variable pre-increment, pre-decrement
-, + unary minus, plus
!, ~ logical and bitwise negation
** exponentiation
*, /, % multiplication, division, remainder
+, - addition, subtraction
<<, >> left and right bitwise shifts
<=, >=, <, > comparison
==, != equality, inequality
& bitwise AND
^ bitwise XOR
| bitwise OR
&& logical AND
|| logical OR
expr ? expr : expr conditional operator
=, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |= assignment

[me@host ~]$ myvar=6 && echo $myvar
[me@host ~]$ let myvar+=1
[me@host ~]$ echo $myvar
[me@host ~]$ let myvar+1
[me@host ~]$ echo $myvar
[me@host ~]$ let myvar2=myvar+1
[me@host ~]$ echo $myvar2

You can combine expressions on a single line as let evaluate each argument as an arithmetic expression.

[me@host ~]$ let x=4 y=5 z=x*y u=z/2
[me@host ~]$ echo $x $y $z $u
4 5 20 10

Using Arithmetic Expansion with $((...)) or $[...]

The recommended way to do operations with integer in Bash is to use the Arithmetic Expansion capability of the shell. The builtin shell expansion allow you to use the parentheses $((...)) or square brackets $[...] to do math. The shell expansion will return the result of the latest expression given. Note that the square brackets notation is being deprecated and should be avoided.

[me@host ~]$ myvar=3 && echo $myvar
[me@host ~]$ echo $((myvar+2))
[me@host ~]$ echo $[myvar+2]
[me@host ~]$ myvar=$((myvar+3))
[me@host ~]$ echo $myvar

This allow you to use C-style programming and easily increment or decrement a variable in Bash using the ++ or -- operators. All the operators listed in the let table above are fully available when using Arithmetic Expansion.

[me@host ~]$ myvar=3 && echo $myvar
[me@host ~]$ echo $((myvar++))
[me@host ~]$ echo $myvar
[me@host ~]$ echo $((++myvar))
[me@host ~]$ echo $myvar

In the previous section, we show an example with let containing multiple expressions on a single line. This is also possible with Arithmetic Expansion. The only difference is that multiple expressions must be seprated by a comma (,).

[me@host ~]$ echo $((x=4,y=5,z=x*y,u=z/2))
[me@host ~]$ echo $x $y $z $u
4 5 20 10

Using the compound command ((...)) or the declare builtin

In many cases you may not need to use the shell Arithmetic Expansion as you don't need the resulted value right away. In such cases you may prefere to use the compound command in bash which are simply the parentheses ((...)). It is the same as the Arithmetic Expansion without the dollar sign ($). Alternatively, you can also use the declare builtin command with the -i option.

For example, the dollar sign ($) when incrementing a counter in a for loop or executing an expression. For example, you can simply use ((++myvar)) or ((myvar=3*a)).

[me@host ~]$ ((x=4,y=5,z=x*y,u=z/2))
[me@host ~]$ echo $x $y $z $u
4 5 20 10

[me@host ~]$ declare -i x=4 y=5 z=x*y u=z/2
[me@host ~]$ echo $x $y $z $u
4 5 20 10

Arithmetic Expansion: syntax error: invalid arithmetic operator

When using Arithmetic Expansion, you may encounter the following error:

syntax error: invalid arithmetic operator (error token is ...)

This error is generally due to improperly formated variables or integer used in the expression. The most common mistake would be to try to use a floating-point which would fail as such.

[me@host ~]$ echo "$((20.0+7))"
-bash: 20.0/7: syntax error: invalid arithmetic operator (error token is ".0+7")

Another error would be when assigning a value to a variable with hidden characters in it and then use that variable in the arithmetic expansion.

[me@host ~]$ a=$' 3\r'
[me@host ~]$ echo "$((a+7))"
")syntax error: invalid arithmetic operator (error token is "

You will notice that in such case the error message even get mangled due to the \r in the variable. To prevent such issue, you will want to ensure the variable you use is properly formated by stripping out control characters which are mostly the character with ascii value from 1 (octal 001) to 31 (octal 037). You can do that by using the shell parameter expansion.

[me@host ~]$ echo "$((${a//[ $'\001'-$'\037']}+7))"

👉 Prior to Bash version 5, you may need to ensure the right collating order of the ascii values by using shopt -s globasciiranges. Though, since Bash version 5, you don't need to worry about the globasciiranges option which is now set by default. Read more about Bash 5 with the post What's New in GNU Bash 5?

Example: Using Arithmetic Expansion and printf to do math on a date

Below is a simple example at doing a date manipulation with a math substraction in shell by using the new $EPOCHSECONDS variable from GNU Bash version 5 and printf date formating. The example shows the current time minus 86400 seconds.

$ printf '%(%d)T\n' $EPOCHSECONDS
$ printf '%(%d)T\n' $((EPOCHSECONDS-86400))

👉 To read more on how to manipulate and format dates in bash see the post How To Format Date and Time in Linux, macOS, and Bash?.

Doing Floating-point Arithmetic in Bash

Using printf builtin command

A noteworthy but unconventional way to do floating-point arithmetic in native bash is to combine Arithmetic Expansion with printf using a scientifc notation. Since, you can't do floating-point in bash, you would just apply a given multiplier by a power of 10 to your math operation inside an Arithmetic Expansion, then use printf to display the float.

For example, the operation 2/3 in Artithmetic Expansion as $((2/3)) would return 0. In order to get the floating number using printf you would use a formula like below where <precision> would be the floating-oint precision to display and <multiplier> the power of ten muliplier. A multiplier of 3 would mean 10**3 which is 1000.

printf %.<precision>f "$((10**<multiplier> * 2/3))e-<multiplier>

Note that the floating point precision in %.<precision>f shouldn't be higher than the multiplier itself as it will just fill with zeros.

[me@host ~]$ printf %.3f "$((10**3 * 2/3))e-3"
[me@host ~]$ printf %.1f "$((10**3 * 2/3))e-3"
[me@host ~]$ printf %.5f "$((10**3 * 2/3))e-3"

Using GNU awk command line

Another way to do floating-point arithmetic is to use GNU awk:

[me@host ~]$ awk "BEGIN {print 100/3}"

You can use the printf function to adjust the precision of the results:

[me@host ~]$ awk "BEGIN {printf \"%.2f\n\", 100/3}"

When using negative values, make sure to leave white space between signs.

[me@host ~]$ awk "BEGIN {print -8.4--8}"
awk: cmd. line:1: BEGIN {print -8.4--8}
awk: cmd. line:1:                    ^ syntax error
[me@host ~]$ awk "BEGIN {print -8.4 - -8}"

Using GNU bc command line

You can't do floating-point arithmetic natively in Bash, you will have to use a command line tool, the most common one being "bc - An arbitrary precision calculator language". To start the interactive mode, you simply need to type bc in your command prompt.

[me@host ~]$ bc
bc 1.06
Copyright 1991-1994, 1997, 1998, 2000 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.

Of course you can also use the STDIN to send your formula to bc then get the output on STDOUT.

[me@host ~]$ echo "3.4+7/8-(5.94*3.14)" | bc

or by using the here-doc notation:

[me@host ~]$ bc <<< "3.4+7/8-(5.94*3.14)" 

I encourage you too take a look at the man pages to get more detail on how it works (man bc).

There are four special variables, scale, ibase, obase, and lastscale defines how some operations use digits after the decimal point.  The default value of scale is 0. ibase and obase define the conversion base for input and output numbers.  The default for both input and output is base 10. last (an extension) is a variable that has the value of the last printed number.

The "scale" variable is really important for the precision of your results, especially when using integers only.

👉 Note: you can also use bc -l to use mathlib and see the result at max scale.

[me@host ~]$ echo "2/3" | bc
[me@host ~]$ echo "scale=2; 2/3" | bc
[me@host ~]$ echo "(2/3)+(7/8)" | bc
[me@host ~]$ echo "scale=2;(2/3)+(7/8)" | bc
[me@host ~]$ echo "scale=4;(2/3)+(7/8)" | bc
[me@host ~]$ echo "scale=6;(2/3)+(7/8)" | bc
[me@host ~]$ echo "(2/3)+(7/8)" | bc -l

👉 If you want to go further, check my post Advanced Math Calculation in Bash using GNU bc.

Related bash posts that you may like
What is the Bash Null Command?
Learn about the Bash null command, also known as the POSIX shell colon command. This post cover concrete use cases and pitfalls to avoid.
How To Format Date and Time in Linux, macOS, and Bash?
Find out how to manipulate date and time on linux and macOS systems as well as natively in the Bash shell. This post covers all you need to know to format a date from your shell.
How To Use Option as Meta Key in macOS Terminal?
The Meta Key is a modifier key that can be quite helpful to improve your productivity while working in a terminal and bash. This post cover how to enable from the command line the Meta Key in macOS Terminal.