Bash can do math, but it doesn’t do it like a high-level language; it treats numbers as strings by default, so you need special syntax to get it to perform actual calculations.
Let’s see it in action. Imagine you have a script that needs to count some files and then perform an operation based on that count:
#!/bin/bash
# Count the number of .txt files in the current directory
file_count=$(ls *.txt 2>/dev/null | wc -l)
# We want to double this count
# This is where bash math comes in:
# $((...)) is the arithmetic expansion
doubled_count=$((file_count * 2))
echo "Original file count: $file_count"
echo "Doubled count: $doubled_count"
# Let's do a comparison
if (( doubled_count > 10 )); then
echo "We have more than 10 doubled files!"
fi
When you run this script in a directory with, say, 3 .txt files, the output will be:
Original file count: 3
Doubled count: 6
If you had 7 .txt files, the output would be:
Original file count: 7
Doubled count: 14
We have more than 10 doubled files!
The core of Bash arithmetic lies in a few key syntaxes. The most common is $((expression)). This is where you put your math. Inside these double parentheses, Bash evaluates arithmetic expressions. You can use standard operators: + for addition, - for subtraction, * for multiplication, / for division, and % for modulo (remainder).
For example, to add two numbers, 10 and 5:
result=$((10 + 5))
echo $result will output 15.
To multiply:
product=$((7 * 3))
echo $product will output 21.
Division in Bash truncates the decimal part. So, 10 / 3 will result in 3, not 3.333....
division=$((10 / 3))
echo $division will output 3.
Modulo gives you the remainder of a division:
remainder=$((10 % 3))
echo $remainder will output 1.
You can also use variables within these expressions. If you have variables a=15 and b=4:
sum=$((a + b))
echo $sum will output 19.
quotient=$((a / b))
echo $quotient will output 3.
modulo=$((a % b))
echo $modulo will output 3.
Bash also supports incrementing and decrementing variables. You can do this with ((var++)), ((var--)), ((++var)), and ((--var)). The ++var and --var versions increment or decrement before the value is used, while var++ and var-- increment or decrement after the value is used. However, the ((...)) construct itself is primarily for evaluation, not assignment in this context. A more direct assignment for increment/decrement is ((var = var + 1)) or ((var += 1)).
Consider this:
count=5
((count++))
echo $count will output 6.
((count++))
echo $count will output 7.
A slightly more nuanced way to increment is let. The let command is an older built-in that also performs arithmetic.
let x=10
let x=x+5
echo $x will output 15.
let y=20
let y*=2 # Equivalent to let y = y * 2
echo $y will output 40.
The ((...)) syntax is generally preferred for its conciseness and modern feel. It can also be used for conditional expressions in if statements, as shown in the first example with (( doubled_count > 10 )). This is a shorthand for if [ "$doubled_count" -gt 10 ].
Parentheses () can be used for grouping operations to control the order of evaluation, just like in standard math. For example:
result=$(( (10 + 5) * 2 ))
echo $result will output 30. Without the inner parentheses, result=$((10 + 5 * 2)), it would output 20.
Bash arithmetic is limited to integer arithmetic. There’s no built-in support for floating-point numbers. If you need to perform floating-point calculations, you’ll typically need to rely on external tools like bc (an arbitrary precision calculator language) or awk.
For example, to divide 10 by 3 and get a floating-point result using bc:
float_division=$(echo "scale=2; 10 / 3" | bc)
echo $float_division will output 3.33. The scale=2 part tells bc to keep two decimal places.
The ((...)) construct is powerful because it allows for direct evaluation of expressions without needing to assign intermediate variables for simple operations, and it integrates seamlessly with conditional logic. It also means you don’t have to worry about quoting as much as you would with $() for command substitution or ${} for variable expansion, as whitespace inside ((...)) is generally ignored.
When you use (( ... )), Bash implicitly converts variables to integers for calculation, and it handles signed integers. You can use unary operators like + and - for negation. For instance, negative_val=$(( -5 )) sets negative_val to -5.
The next common hurdle is handling larger numbers or needing more complex mathematical functions, which leads you to bc or awk.