Lesson 4: Loops & Iteration
Goal: Learn to repeat actions efficiently with for, while, and until loops.
Table of Contents
- for-in Loops
- C-Style for Loops
- while Loops
- until Loops
- Reading Files Line by Line
- Loop Control — break & continue
- Infinite Loops & Retry Patterns
- Practical Examples
- Exercises
for-in Loops
Loop over a list
for color in red green blue; do
echo "Color: $color"
done
Loop over a range
# Brace expansion
for i in {1..5}; do
echo "Number: $i"
done
# With step
for i in {0..20..5}; do
echo "Value: $i" # 0, 5, 10, 15, 20
done
Loop over files
# All .txt files in current directory
for file in *.txt; do
echo "Processing: $file"
done
# All files in a directory
for file in /var/log/*.log; do
echo "Log: $file ($(du -h "$file" | cut -f1))"
done
# Safely handle "no match" case
shopt -s nullglob # empty loop if no matches
for file in *.xyz; do
echo "Found: $file"
done
Loop over command output
# Loop over lines of command output
for user in $(cut -d: -f1 /etc/passwd); do
echo "User: $user"
done
# Loop over arguments
for arg in "$@"; do
echo "Argument: $arg"
done
C-Style for Loops
Classic counter loops using C syntax:
# Count from 0 to 9
for ((i=0; i<10; i++)); do
echo "i = $i"
done
# Count down
for ((i=10; i>0; i--)); do
echo "Countdown: $i"
done
# Step by 2
for ((i=0; i<=20; i+=2)); do
echo "Even: $i"
done
while Loops
Run as long as a condition is true.
Basic while
COUNT=1
while [[ $COUNT -le 5 ]]; do
echo "Count: $COUNT"
((COUNT++))
done
While with command exit code
# Wait for a file to appear
while [[ ! -f /tmp/ready.flag ]]; do
echo "Waiting for ready flag..."
sleep 2
done
echo "Ready!"
While with arithmetic
N=1
while ((N <= 100)); do
echo "$N"
((N *= 2))
done
# Output: 1, 2, 4, 8, 16, 32, 64
until Loops
Run until a condition becomes true (opposite of while).
COUNT=1
until [[ $COUNT -gt 5 ]]; do
echo "Count: $COUNT"
((COUNT++))
done
Practical: Wait for a service
#!/bin/bash
# Wait until PostgreSQL is ready
until pg_isready -h localhost -p 5432 2>/dev/null; do
echo "Waiting for database..."
sleep 2
done
echo "Database is ready!"
Reading Files Line by Line
Standard pattern
while IFS= read -r line; do
echo "Line: $line"
done < /etc/hostname
Why IFS= read -r?
IFS=prevents trimming leading/trailing whitespace-rprevents backslash interpretation
Read from a file
# Process /etc/passwd
while IFS=: read -r user _ uid gid _ home shell; do
if ((uid >= 1000)); then
echo "User: $user (UID: $uid, Shell: $shell)"
fi
done < /etc/passwd
Read from command output (process substitution)
while IFS= read -r line; do
echo "Process: $line"
done < <(ps aux | grep nginx)
Read CSV data
cat > data.csv << 'EOF'
Name,Age,City
Alice,30,Berlin
Bob,25,Munich
Carol,28,Hamburg
EOF
# Skip header, read CSV
tail -n +2 data.csv | while IFS=, read -r name age city; do
echo "$name is $age years old and lives in $city"
done
rm data.csv
Loop Control — break & continue
break — Exit the loop
for i in {1..100}; do
if [[ $i -eq 5 ]]; then
echo "Found 5, stopping"
break
fi
echo "Number: $i"
done
# Output: 1, 2, 3, 4, Found 5, stopping
continue — Skip to next iteration
for i in {1..10}; do
if ((i % 3 == 0)); then
continue # skip multiples of 3
fi
echo "Number: $i"
done
# Output: 1, 2, 4, 5, 7, 8, 10
Practical: Skip dotfiles
for file in *; do
# Skip hidden files
[[ "$file" == .* ]] && continue
echo "Processing: $file"
done
Infinite Loops & Retry Patterns
Infinite loop
while true; do
echo "Running... (Ctrl+C to stop)"
sleep 5
done
Retry with max attempts
#!/bin/bash
set -euo pipefail
MAX_RETRIES=5
RETRY_DELAY=3
URL="https://api.example.com/health"
for ((attempt=1; attempt<=MAX_RETRIES; attempt++)); do
echo "Attempt $attempt/$MAX_RETRIES..."
if curl -sf "$URL" > /dev/null 2>&1; then
echo "Service is up!"
break
fi
if ((attempt == MAX_RETRIES)); then
echo "Failed after $MAX_RETRIES attempts" >&2
exit 1
fi
echo "Retrying in ${RETRY_DELAY}s..."
sleep "$RETRY_DELAY"
done
Docker wait-for pattern
#!/bin/bash
set -euo pipefail
HOST="${DB_HOST:-db}"
PORT="${DB_PORT:-5432}"
TIMEOUT="${WAIT_TIMEOUT:-30}"
echo "Waiting for $HOST:$PORT (timeout: ${TIMEOUT}s)..."
START=$(date +%s)
while ! nc -z "$HOST" "$PORT" 2>/dev/null; do
ELAPSED=$(( $(date +%s) - START ))
if ((ELAPSED >= TIMEOUT)); then
echo "Timeout waiting for $HOST:$PORT" >&2
exit 1
fi
sleep 1
done
echo "$HOST:$PORT is available (took ${ELAPSED}s)"
Practical Examples
Batch rename files
#!/bin/bash
# Rename all .jpeg to .jpg
for file in *.jpeg; do
[[ -f "$file" ]] || continue
mv "$file" "${file%.jpeg}.jpg"
echo "Renamed: $file → ${file%.jpeg}.jpg"
done
Backup multiple databases
#!/bin/bash
set -euo pipefail
BACKUP_DIR="/backup/$(date +%Y%m%d)"
mkdir -p "$BACKUP_DIR"
DATABASES=("users" "orders" "products" "analytics")
for db in "${DATABASES[@]}"; do
echo "Backing up: $db..."
pg_dump "$db" | gzip > "$BACKUP_DIR/${db}.sql.gz"
echo " Done: $BACKUP_DIR/${db}.sql.gz"
done
echo "All backups complete"
Monitor multiple services
#!/bin/bash
SERVICES=("nginx" "postgresql" "redis")
for service in "${SERVICES[@]}"; do
if systemctl is-active --quiet "$service" 2>/dev/null; then
echo "[OK] $service is running"
else
echo "[FAILED] $service is NOT running"
fi
done
Exercises
Exercise 1: Multiplication table
cat > table.sh << 'EOF'
#!/bin/bash
set -euo pipefail
NUM="${1:-5}"
echo "=== Multiplication Table for $NUM ==="
for ((i=1; i<=10; i++)); do
printf "%2d x %2d = %3d\n" "$NUM" "$i" "$((NUM * i))"
done
EOF
chmod +x table.sh
./table.sh 7
rm table.sh
Exercise 2: File counter by extension
cat > count-ext.sh << 'EOF'
#!/bin/bash
set -euo pipefail
DIR="${1:-.}"
echo "File types in $DIR:"
find "$DIR" -type f -name "*.*" | sed 's/.*\.//' | sort | uniq -c | sort -rn | while read -r count ext; do
printf " .%-10s %d files\n" "$ext" "$count"
done
EOF
chmod +x count-ext.sh
./count-ext.sh /etc
rm count-ext.sh
Exercise 3: Retry pattern
cat > retry.sh << 'EOF'
#!/bin/bash
set -euo pipefail
MAX=5
for ((i=1; i<=MAX; i++)); do
echo "Attempt $i/$MAX..."
# Simulate: succeeds on attempt 3
if ((i >= 3)); then
echo "Success on attempt $i!"
exit 0
fi
echo "Failed, retrying in 1s..."
sleep 1
done
echo "All attempts failed" >&2
exit 1
EOF
chmod +x retry.sh && ./retry.sh
rm retry.sh
Key Takeaways
for x in list— iterate over items, files, rangesfor ((i=0; i<n; i++))— classic counter loopswhile [[ condition ]]— repeat while trueuntil [[ condition ]]— repeat until truewhile IFS= read -r line— read files line by linebreakexits,continueskips- Retry loops are essential for Docker/CI reliability
- Always quote variables in loops:
"$file","$line"
Next Lesson: Lesson 5: Functions & Modularity →