The Problem with bc
People reach for bc (basic calculator) for arithmetic in bash:
result=$(echo "10 + 5" | bc)echo $result # 15This works but spawns a subprocess. It’s slow, verbose, and unnecessary for simple math.
Bash has built-in arithmetic. You just have to know the syntax.
Arithmetic Expansion: $(( ))
Use double parentheses for math:
#!/bin/bash
a=10b=5result=$((a + b))echo $result # 15That’s it. $(( math )) evaluates the expression and returns the result.
Supported operators:
# Basic$((10 + 5)) # 15$((10 - 5)) # 5$((10 * 5)) # 50$((10 / 5)) # 2 (integer division)$((10 % 3)) # 1 (modulo)$((2 ** 3)) # 8 (exponentiation)
# Assignmentx=5$((x++)) # post-increment$((++x)) # pre-increment$((x += 3)) # add and assign$((x -= 2)) # subtract and assign
# Comparison$((5 > 3)) # 1 (true)$((5 < 3)) # 0 (false)$((5 == 5)) # 1 (true)$((5 != 3)) # 1 (true)Important: arithmetic in bash is integer only. 10 / 3 is 3, not 3.333....
Using Arithmetic in Conditions
Test numbers directly:
if (( count > 10 )); then echo "More than 10"fi(( expression )) is a test. Non-zero is true, zero is false.
Loop with arithmetic:
for (( i=1; i<=5; i++ )); do echo $idone
# Output:# 1# 2# 3# 4# 5This is bash’s C-style for loop. Much cleaner than seq.
Real Examples
Disk Usage Check
#!/bin/bash
max_usage=80current=$( df / | tail -1 | awk '{print $5}' | sed 's/%//' )
if (( current > max_usage )); then echo "WARNING: Disk $current% full"fiExtract the percentage, compare. If over 80%, warn.
Loop and Calculate
#!/bin/bash
total=0for file in *.log; do size=$( stat -f%z "$file" 2>/dev/null || stat -c%s "$file" ) ((total += size))done
echo "Total size: $((total / 1024 / 1024)) MB"Add up file sizes, convert bytes to MB with division.
Countdown Timer
#!/bin/bash
duration=10while (( duration > 0 )); do echo "Countdown: $duration" sleep 1 ((duration--))done
echo "Done!"Calculate Process Load Average
#!/bin/bash
# Get load average (multiply by 100 to convert to percentage)load=$( uptime | awk -F'load average:' '{print $2}' | cut -d',' -f1 | xargs )threshold=2
if (( ${load%.*} >= threshold )); then echo "System is under heavy load: $load"fiExtract the integer part of load, compare to threshold.
Floating Point (When You Need It)
Bash doesn’t do floats natively. But you can fake it with bc when necessary:
# Convert bytes to GB with decimalsbytes=5368709120gb=$( echo "scale=2; $bytes / 1024 / 1024 / 1024" | bc )echo "Size: $gb GB" # Size: 5.00 GBscale=2 means 2 decimal places. But use this sparingly—it’s slow.
For percentages and rounding, integer math is usually enough:
# Progress bar: 3 out of 10 itemsitems_done=3items_total=10percent=$(( items_done * 100 / items_total ))echo "Progress: $percent%" # Progress: 30%Pitfall: Variable Expansion in $(( ))
Inside arithmetic expansion, you can use variables without $:
x=5result=$((x + 10)) # Worksresult=$(($x + 10)) # Also works, but unnecessaryBoth work. The first is cleaner.
But use $ for clarity in complex expressions:
# This works but is hard to readresult=$((${array[0]} + 5))
# Clearer: expand the variable outsideval=${array[0]}result=$((val + 5))Bitwise Operators
Bash also supports bitwise operations:
$((5 & 3)) # AND: 1$((5 | 3)) # OR: 7$((5 ^ 3)) # XOR: 6$((~5)) # NOT: -6 (two's complement)$((5 << 1)) # Left shift: 10$((5 >> 1)) # Right shift: 2Rarely used in sysadmin scripts, but useful for low-level bit manipulation.
Performance Gains
Spawning bc for every calculation is expensive. Here’s a loop that sums 1000 numbers:
# Slow: spawning bctime { total=0 for i in {1..1000}; do total=$( echo "$total + $i" | bc ) done}
# Fast: builtin arithmetictime { total=0 for i in {1..1000}; do ((total += i)) done}The builtin version is 10-50x faster. Use it.
Bottom Line
Bash arithmetic with $(( )) handles 95% of shell script math. It’s built-in, fast, and readable. Save bc for the rare case when you need floating-point precision, and even then, consider whether you should be using Python instead.