Loops are the engine of automation. Bash offers for for iterating over lists and ranges, and while for condition-based repetition. Knowing which to choose and how to use break and continue correctly is what makes scripts robust rather than fragile.
1
for — list-based iteration
BASH
# ── Basic list ────────────────────────────────────────────
for fruit in apple banana cherry mango; do
echo " Fruit: ${fruit}"
done
# ── Loop over array ───────────────────────────────────────
servers=("web-01" "web-02" "db-01" "cache-01")
for srv in "${servers[@]}"; do
echo " Pinging ${srv}..."
ping -c1 -W1 "${srv}" >/dev/null 2>&1 \
&& echo " ✔ ${srv} reachable" \
|| echo " ✘ ${srv} unreachable"
done
# ── Loop over command output ──────────────────────────────
for user in $(awk -F: '$3 >= 1000 {print $1}' /etc/passwd); do
echo " System user: ${user}"
done
# ── Loop over files matching pattern ─────────────────────
for f in /var/log/*.log; do
[[ -f "${f}" ]] || continue # skip if no match (glob safety)
echo " Log: ${f} ($(wc -l < "${f}") lines)"
done
2
for — range and C-style
BASH
# ── Brace expansion range ─────────────────────────────────
for i in {1..5}; do
echo " Attempt ${i}"
done
# With step
for i in {0..100..10}; do # 0 10 20 ... 100
echo " ${i}%"
done
# Countdown
for i in {10..1}; do
printf "\r Restarting in %2d seconds..." "${i}"
sleep 1
done
# ── C-style for (most flexible) ───────────────────────────
for (( i=1; i<=5; i++ )); do
echo " i = ${i}"
done
# C-style with custom step
for (( i=0; i<=100; i+=25 )); do
echo " Progress: ${i}%"
done
# ── seq — useful when range is a variable ─────────────────
MAX=5
for i in $(seq 1 "${MAX}"); do
echo " Step ${i} of ${MAX}"
done
# NOTE: brace expansion {1..N} doesn't expand variables
# Use $(seq 1 $N) or C-style for when N is a variable
Terminal output
Key
Reachable
Unreachable
Info
vriddh@prod-01:~/scripts$bash ping_servers.sh
Pinging web-01...
✔ web-01 reachable
Pinging web-02...
✔ web-02 reachable
Pinging db-01...
✔ db-01 reachable
Pinging cache-01...
✘ cache-01 unreachable
█
3
while — condition-based loops
BASH
# ── Basic while ───────────────────────────────────────────
count=1
while (( count <= 5 )); do
echo " Count: ${count}"
(( count++ ))
done
# ── Retry loop with backoff ───────────────────────────────
MAX_RETRIES=5
attempt=1
while (( attempt <= MAX_RETRIES )); do
echo " Attempt ${attempt}/${MAX_RETRIES}..."
if curl -sf "http://api.example.com/health" >/dev/null; then
echo " ✔ Service healthy"
break
fi
echo " ✘ Not ready — waiting $((attempt * 2))s"
sleep $(( attempt * 2 ))
(( attempt++ ))
done
(( attempt > MAX_RETRIES )) && {
echo "ERROR: Service did not become healthy" >&2
exit 1
}
# ── Infinite loop with break ──────────────────────────────
while true; do
load=$(awk '{print $1}' /proc/loadavg)
echo " Load: ${load}"
(( $(echo "${load} > 2.0" | bc -l) )) && {
echo " HIGH LOAD — alerting"
break
}
sleep 5
done
4
Reading files line-by-line with while read
The while read pattern is the correct and safe way to process files line by line. It handles spaces in fields, empty lines, and binary-safe reading — unlike looping over command substitution.
BASH
# ── Basic file reading ────────────────────────────────────
while IFS= read -r line; do
echo " → ${line}"
done < /etc/hosts
# ── Parse CSV with IFS ────────────────────────────────────
while IFS=',' read -r host port service; do
[[ "${host}" == "#"* ]] && continue # skip comments
[[ -z "${host}" ]] && continue # skip blank lines
printf " Checking %-20s %-6s %s\n" "${host}" "${port}" "${service}"
nc -z "${host}" "${port}" 2>/dev/null \
&& echo " ✔ UP" || echo " ✘ DOWN"
done < services.csv
# ── Read from command output ──────────────────────────────
df -h | while IFS= read -r line; do
echo " ${line}"
done
# ── Process substitution (avoids subshell — vars persist) ─
total=0
while IFS=',' read -r name amount; do
(( total += amount ))
done < <(grep '^item' sales.csv)
echo "Total: ${total}" # Variable IS accessible here
vriddh@prod-01:~/scripts$bash check_services.sh
Checking prod-db-01 3306 MySQL
✔ UP
Checking prod-cache-01 6379 Redis
✔ UP
Checking prod-mq-01 5672 RabbitMQ
✘ DOWN
█
⚠ Pipe vs process substitution —
cmd | while read runs the while loop in a subshell — any variables set inside are lost after the loop. Use while read; done < <(cmd) instead to keep variables in the current shell.
5
break and continue — loop control
BASH
# ── break — exit the loop ─────────────────────────────────
for i in {1..10}; do
(( i == 5 )) && break
echo " i=${i}"
done
# Prints 1 2 3 4 then stops
# ── continue — skip to next iteration ─────────────────────
for i in {1..10}; do
(( i % 2 == 0 )) && continue # skip even numbers
echo " odd: ${i}"
done
# ── break N — break out of N nested loops ─────────────────
for host in host1 host2 host3; do
for port in 80 443 8080; do
if nc -z "${host}" "${port}" >/dev/null 2>&1; then
echo " First open port: ${host}:${port}"
break 2 # break out of BOTH loops
fi
done
done
# ── Real-world: find first available backup slot ──────────
for slot in {1..7}; do
backup_dir="/backups/slot_${slot}"
[[ -d "${backup_dir}" ]] && continue
mkdir -p "${backup_dir}"
echo " Using backup slot ${slot}"
break
done
6
Real-world — log rotation with for loop
BASH
#!/usr/bin/env bash
# rotate_logs.sh — Archive logs older than N days
set -euo pipefail
LOG_DIR="/var/log/myapp"
ARCHIVE_DIR="/var/log/myapp/archive"
DAYS_OLD=7
rotated=0
errors=0
mkdir -p "${ARCHIVE_DIR}"
echo "Rotating logs older than ${DAYS_OLD} days..."
while IFS= read -r -d '' logfile; do
filename=$(basename "${logfile}")
archive_name="${ARCHIVE_DIR}/${filename%.log}_$(date +%Y%m%d).log.gz"
echo " Archiving: ${filename}"
if gzip -c "${logfile}" > "${archive_name}"; then
rm -f "${logfile}"
echo " ✔ Archived to $(basename "${archive_name}")"
(( rotated++ ))
else
echo " ✘ Failed to archive ${filename}" >&2
(( errors++ ))
fi
done < <(find "${LOG_DIR}" -maxdepth 1 -name "*.log" \
-mtime +"${DAYS_OLD}" -print0)
echo "Done — rotated: ${rotated}, errors: ${errors}"
(( errors > 0 )) && exit 1 || exit 0
vriddh@prod-01:~/scripts$./rotate_logs.sh
Rotating logs older than 7 days...
Archiving: app_2026-04-20.log
✔ Archived to app_2026-04-20_20260501.log.gz
Archiving: app_2026-04-21.log
✔ Archived to app_2026-04-21_20260501.log.gz
Archiving: error_2026-04-19.log
✔ Archived to error_2026-04-19_20260501.log.gz
Done — rotated: 3, errors: 0
█
✔ Loop selection guide — Use
for item in list when you know the items upfront. Use for ((i=0; i<N; i++)) when you need an index or step control. Use while read for file/stream processing. Use while true; do ... break; done for retry and polling patterns. Always use "$@" not $* in argument loops.