Bash wasn’t originally designed as a math-heavy language, but I still end up doing arithmetic in almost every script I write — counting iterations, calculating percentages, converting units, you name it. The tricky part is that Bash has several different ways to do math, and each has its own quirks. In this guide, I’ll walk through all of them so you know exactly which one to reach for.
Why Arithmetic in Bash Is a Bit Different
Unlike languages like Python or JavaScript, Bash treats everything as a string by default. If you write x = 5 + 3, Bash won’t automatically calculate anything — you need to explicitly tell it you want arithmetic evaluation. There are four main ways to do that:
$(( ))— arithmetic expansion(( ))— arithmetic evaluation (for conditions/increments)let— the older arithmetic commandexpr— an external command for expressions (largely legacy now)
Let’s go through each.
Using $(( )) — Arithmetic Expansion
This is the most common and recommended way to perform arithmetic in Bash.
Example 1: Basic Operations
#!/bin/bash
a=10
b=3
echo "Addition: $((a + b))"
echo "Subtraction: $((a - b))"
echo "Multiplication: $((a * b))"
echo "Division: $((a / b))"
echo "Modulus: $((a % b))"
echo "Exponentiation: $((a ** b))"
Output:
Addition: 13
Subtraction: 7
Multiplication: 30
Division: 3
Modulus: 1
Exponentiation: 1000
Notice that a / b gives 3, not 3.33. Bash only supports integer arithmetic natively — there’s no built-in floating-point support. I’ll show you how to work around that later using bc.
Example 2: Storing the Result in a Variable
#!/bin/bash
x=15
y=4
result=$((x + y))
echo "The result is: $result"
Output:
The result is: 19
Using (( )) — Arithmetic Evaluation
(( )) is typically used for evaluating expressions as a command, particularly for incrementing/decrementing variables or as a condition.
Example: Incrementing and Decrementing
#!/bin/bash
count=5
((count++))
echo "After increment: $count"
((count--))
echo "After decrement: $count"
Output:
After increment: 6
After decrement: 5
Example: Using (( )) in a Condition
#!/bin/bash
num=10
if (( num > 5 ))
then
echo "$num is greater than 5"
fi
Output:
10 is greater than 5
Notice that inside (( )), you don’t need $ before variable names, and you can use normal mathematical operators like >, <, == directly, unlike inside [ ].
Using let
let is an older command for performing arithmetic and assigning the result to a variable.
#!/bin/bash
let result=5+3
echo "Result: $result"
let "result = result * 2"
echo "Doubled: $result"
Output:
Result: 8
Doubled: 16
I rarely use let in new scripts since $(( )) and (( )) are more consistent and readable, but you’ll still see it in a lot of older scripts.
Using expr (Legacy)
expr is an external command, meaning it spawns a separate process every time you call it — which makes it slower than the other methods. It also requires spaces around every operator and symbol.
#!/bin/bash
result=$(expr 5 + 3)
echo "Result: $result"
Output:
Result: 8
Be careful: expr 5*3 will fail or behave strangely because * is interpreted as a glob by the shell unless escaped (expr 5 \* 3). This is one of many reasons expr has fallen out of favor.
Floating-Point Arithmetic with bc
Since Bash only handles integers natively, you need an external tool for decimal calculations. bc (basic calculator) is the standard choice.
Example 1: Simple Division
#!/bin/bash
result=$(echo "10 / 3" | bc -l)
echo "Result: $result"
Output:
Result: 3.33333333333333333333
The -l flag loads the math library, which also gives you access to functions like sqrt(), sin(), and more precision.
Example 2: Rounding to a Fixed Number of Decimal Places
#!/bin/bash
result=$(echo "scale=2; 10 / 3" | bc)
echo "Result: $result"
Output:
Result: 3.33
scale=2 tells bc to keep two digits after the decimal point.
Example 3: Calculating Percentages
#!/bin/bash
total=250
part=47
percentage=$(echo "scale=2; ($part / $total) * 100" | bc)
echo "Percentage: $percentage%"
Output:
Percentage: 18.80%
Alternative: Using awk for Floating-Point Math
If bc isn’t installed on a system (rare, but possible on minimal containers), awk is a solid fallback:
#!/bin/bash
result=$(awk "BEGIN {print 10/3}")
echo "Result: $result"
Output:
Result: 3.33333
How This Works Internally
$(( ))and(( ))are handled entirely by Bash itself — no external process is spawned, which makes them fast.- Bash arithmetic contexts treat all numbers as signed integers, typically using the system’s native long integer size (commonly 64-bit on modern systems). There’s no concept of floats inside
(( )). exprandbcare separate binaries on your system (/usr/bin/expr,/usr/bin/bc). Every call to them forks a new process, which is why they’re noticeably slower in tight loops.bcreads a small program-like syntax (its own scripting language) from standard input, which is why we pipe the expression into it usingecho "..." | bc.
Real-World Use Cases
- Calculating disk usage percentage:
used=$(df / | tail -1 | awk '{print $3}')
total=$(df / | tail -1 | awk '{print $2}')
percent=$(echo "scale=2; ($used/$total)*100" | bc)
echo "Disk usage: $percent%"
- Loop counters:
total=0
for i in {1..10}
do
((total += i))
done
echo "Sum of 1 to 10: $total"
- Converting units (e.g., Celsius to Fahrenheit):
celsius=25
fahrenheit=$(echo "scale=2; ($celsius * 9/5) + 32" | bc)
echo "$celsius°C is $fahrenheit°F"
- Calculating average response time from logs:
total_time=0
count=0
while read -r time
do
total_time=$(echo "$total_time + $time" | bc)
((count++))
done < response_times.txt
average=$(echo "scale=2; $total_time / $count" | bc)
echo "Average response time: $average ms"
Best Practices
- Use
$(( ))for all integer arithmetic — it’s the most readable, portable, and doesn’t require an external process. - Use
bc -lwhen you need decimal precision or mathematical functions like square roots or trigonometry. - Avoid
exprin new scripts; it’s slower and has confusing quoting rules. - Always account for division by zero — check your denominator before dividing.
Security Considerations
- Never pass unsanitized input directly into an arithmetic expression evaluated with
eval— arithmetic contexts can sometimes still lead to unexpected behavior if variables aren’t validated first. - Validate that user input intended for arithmetic is actually numeric before using it, using a regex check like
[[ $var =~ ^[0-9]+$ ]], to avoid script errors or crashes.
Optimization Tips
- Since
$(( ))and(( ))are Bash builtins, prefer them overexpror spawningbcwhen only integer math is needed — this avoids the overhead of forking a process. - For loops that only need integer math (e.g., counters), avoid calling
bcon every iteration; reservebcfor cases where floating-point precision is actually required. - Batch decimal calculations into a single
bccall when possible instead of calling it repeatedly inside a loop.
Troubleshooting Common Issues
- “division by zero” errors — always check the denominator isn’t zero before dividing, especially with dynamic values from logs or user input.
- Unexpected integer truncation — remember
$(( ))truncates decimals; usebcif you need the fractional part. expr: syntax error— usually caused by missing spaces around operators or unescaped shell metacharacters like*.bc: command not found— install it via your package manager (sudo apt install bcon Debian/Ubuntu) or fall back toawk.
Frequently Asked Questions
Q: Does Bash support floating-point arithmetic natively? A: No. Bash’s built-in arithmetic ($(( )), (( )), let) only supports integers. Use bc or awk for decimals.
Q: What’s the fastest way to do arithmetic in Bash? A: $(( )) and (( )), since they’re handled internally by Bash without spawning a new process.
Q: How do I round a number in Bash? A: Use bc with a specific scale, or printf "%.2f" for formatted rounding of a value already computed.
Q: Can I use variables without a $ sign inside (( ))? A: Yes — inside arithmetic contexts like (( )), Bash automatically treats bare variable names as their numeric values.
Common Mistakes to Avoid
- Expecting
$((10/3))to return a decimal — it returns3, not3.33. - Forgetting to escape
*when usingexpr(expr 5 \* 3). - Using
=instead of-eqor==for numeric comparisons outside arithmetic contexts. - Not validating input before performing arithmetic on it, which can cause the script to fail or behave unpredictably.
Summary
Bash gives you several ways to do math, but they’re not interchangeable. For everyday integer arithmetic, $(( )) and (( )) are fast, clean, and built right into the shell. When you need decimals or advanced math functions, reach for bc (or awk as a fallback). Avoid expr unless you’re maintaining older scripts that already use it. Once you know which tool fits which job, arithmetic in Bash becomes second nature.