Shell Scripting Bash Arithmetic Math May 2026

Shell Scripting Arithmetic & Math Operations

Master all methods for integer and floating-point arithmetic in bash — $(( )), let, expr, and bc. Learn base conversion, random numbers, and build a real disk usage calculator script.

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.

BASH
# 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 — arithmetic demo
vriddh@prod-01:~$echo $(( 2 ** 10 ))
1024
vriddh@prod-01:~$echo $(( 10 / 3 ))
3 # integer division — fractional part dropped
vriddh@prod-01:~$x=10; (( x += 5 )); echo $x
15

Bash has no native float support. Pass expressions to bc (basic calculator) for decimal arithmetic. Always use scale=N to set decimal places.

BASH
# 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.

BASH
# 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
BASH
# ── 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
BASH
#!/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
Key OK (<70%) Warning (70-89%) Critical (≥90%)
bash — disk_report.sh
vriddh@prod-01:~/scripts$./disk_report.sh
Disk Usage Report — 2026-05-01 10:30:00
────────────────────────────────────────────────────
/ 200.0 GB total 45.2 GB used 154.8 GB free 22% ✔
/var 50.0 GB total 37.1 GB used 12.9 GB free 74% ← WARNING
/home 100.0 GB total 91.8 GB used 8.2 GB free 91% ← CRITICAL
/tmp 10.0 GB total 0.4 GB used 9.6 GB free 4% ✔
✔ Arithmetic method guide — Use $(( )) 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.
⚠ Integer division truncates$(( 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.