Bash is not a mathematical language, but it handles integer arithmetic natively and delegates floating-point work to bc or awk. Understanding which tool to use for which job prevents subtle precision bugs in scripts that calculate thresholds, percentages, and file sizes.
The $(( )) syntax is the modern, preferred way to do integer arithmetic in bash. It supports all standard operators and returns the result as a string you can use anywhere.
# Basic operators
echo $(( 10 + 3 )) # 13 — addition
echo $(( 10 - 3 )) # 7 — subtraction
echo $(( 10 * 3 )) # 30 — multiplication
echo $(( 10 / 3 )) # 3 — integer division (truncates)
echo $(( 10 % 3 )) # 1 — modulo (remainder)
echo $(( 2 ** 10 )) # 1024 — exponentiation
# Using variables (no $ needed inside (( )))
a=15; b=4
echo $(( a + b )) # 19
echo $(( a * b )) # 60
echo $(( a / b )) # 3 (truncated, not 3.75)
# Assign arithmetic result to variable
result=$(( a * b + 10 ))
echo "Result: ${result}" # 70
# Compound assignment operators
x=10
(( x += 5 )) # x = 15
(( x -= 3 )) # x = 12
(( x *= 2 )) # x = 24
(( x /= 4 )) # x = 6
(( x %= 4 )) # x = 2
(( x++ )) # x = 3 (post-increment)
(( ++x )) # x = 4 (pre-increment)
# Bitwise operators
echo $(( 5 & 3 )) # 1 — AND
echo $(( 5 | 3 )) # 7 — OR
echo $(( 5 ^ 3 )) # 6 — XOR
echo $(( 5 << 1 )) # 10 — left shift
echo $(( 5 >> 1 )) # 2 — right shift
Bash has no native float support. Pass expressions to bc (basic calculator) for decimal arithmetic. Always use scale=N to set decimal places.
# Basic float operations with bc
echo "10 / 3" | bc # 3 — no decimal (default scale=0)
echo "scale=4; 10 / 3" | bc # 3.3333
echo "scale=2; 22 / 7" | bc # 3.14
# Assign bc result to variable
pi=$(echo "scale=6; 22 / 7" | bc)
echo "Pi ≈ ${pi}" # Pi ≈ 3.142857
# Percentage calculation (common in monitoring scripts)
used=75
total=128
pct=$(echo "scale=1; ${used} * 100 / ${total}" | bc)
echo "Memory used: ${pct}%" # Memory used: 58.5%
# Math functions with bc -l (math library)
sqrt_2=$(echo "scale=4; sqrt(2)" | bc -l)
log_10=$(echo "scale=4; l(10)/l(10)" | bc -l) # log base 10
echo "√2 = ${sqrt_2}, log10(10) = ${log_10}"
# Comparison with bc (returns 1=true, 0=false)
threshold=90
usage=92.5
exceeded=$(echo "${usage} > ${threshold}" | bc)
[[ "${exceeded}" -eq 1 ]] && echo "ALERT: threshold exceeded"
# Multi-line bc script
result=$(bc <<EOF
scale=2
x = 100
y = 3.14159
z = x * y
z
EOF
)
echo "Result: ${result}"
For float arithmetic in scripts that already use awk (most monitoring scripts do), awk's built-in math is faster and cleaner than piping to bc.
# awk for float arithmetic
awk 'BEGIN { printf "%.2f\n", 10/3 }' # 3.33
awk 'BEGIN { printf "%.4f\n", sqrt(2) }' # 1.4142
awk 'BEGIN { printf "%.6f\n", atan2(0,-1) }' # 3.141593 (pi)
# Percentage with awk — common in monitoring
used_mem=6554
total_mem=16384
pct=$(awk "BEGIN { printf \"%.1f\", ${used_mem}/${total_mem}*100 }")
echo "Memory: ${pct}%" # Memory: 40.0%
# Disk usage percentage
df -BM /var | awk 'NR==2 { sub(/%/,"",$5); print $5 }'
# Check if float exceeds threshold
load=$(uptime | awk '{print $NF}') # 15min load average
cores=$(nproc)
if awk "BEGIN { exit !(${load} > ${cores}) }"; then
echo "WARNING: Load ${load} exceeds ${cores} cores"
fi
# ── Base conversion ───────────────────────────────────────
# Decimal to binary/octal/hex
printf "%b\n" 42 # Nope — %b is for escape sequences
echo "obase=2;42" | bc # 101010 (binary)
echo "obase=8;42" | bc # 52 (octal)
echo "obase=16;255" | bc # FF (hex)
# printf for base conversion
printf "%d\n" 0xFF # 255 (hex to decimal)
printf "%o\n" 255 # 377 (decimal to octal)
printf "%x\n" 255 # ff (decimal to hex lower)
printf "%X\n" 255 # FF (decimal to hex upper)
# Bash literal — prefix notation
echo $(( 16#FF )) # 255 (hex literal)
echo $(( 8#77 )) # 63 (octal literal)
echo $(( 2#1010 )) # 10 (binary literal)
# ── Random numbers ────────────────────────────────────────
echo "$RANDOM" # 0 to 32767
echo $(( RANDOM % 100 )) # 0 to 99
echo $(( RANDOM % 100 + 1 )) # 1 to 100
echo $(( RANDOM % 6 + 1 )) # Dice roll 1-6
# Better random with /dev/urandom (for IDs/tokens)
cat /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c 16 # 16-char random string
openssl rand -hex 16 # Cryptographic random hex
#!/usr/bin/env bash
# disk_report.sh — Calculate and report disk usage with percentages
set -euo pipefail
readonly WARN_PCT=70
readonly CRIT_PCT=90
check_disk() {
local mount="${1:-/}"
local total_kb used_kb avail_kb pct
read -r total_kb used_kb avail_kb <<< $(df -k "${mount}" | awk 'NR==2{print $2,$3,$4}')
# Convert to human-readable GB
local total_gb=$(awk "BEGIN{printf \"%.1f\", ${total_kb}/1048576}")
local used_gb=$(awk "BEGIN{printf \"%.1f\", ${used_kb}/1048576}")
local avail_gb=$(awk "BEGIN{printf \"%.1f\", ${avail_kb}/1048576}")
# Percentage used (integer for comparison)
pct=$(( used_kb * 100 / total_kb ))
printf "%-15s %6s GB total %6s GB used %6s GB free %3d%%" \
"${mount}" "${total_gb}" "${used_gb}" "${avail_gb}" "${pct}"
if (( pct >= CRIT_PCT )); then
echo " ← CRITICAL"
elif (( pct >= WARN_PCT )); then
echo " ← WARNING"
else
echo " ✔"
fi
}
echo "Disk Usage Report — $(date '+%Y-%m-%d %H:%M:%S')"
echo "────────────────────────────────────────────────────"
for mount in / /var /home /tmp; do
[[ -d "${mount}" ]] && check_disk "${mount}"
done
$(( )) for all integer math (fast, no subshell). Use awk BEGIN{} for float math in scripts that already use awk (fastest). Use bc when you need arbitrary precision or a math library. Never use expr — it is obsolete, slow (spawns a process), and error-prone.$(( 7 / 2 )) gives 3 not 3.5. When you need percentages, multiply first to preserve precision: $(( used * 100 / total )) not $(( used / total * 100 )) — the latter always gives 0 when used < total.