Complete Guide to Bash Loops

Introduction to Bash Loops

Loops are fundamental control structures in Bash scripting that allow you to execute commands repeatedly. They are essential for automating repetitive tasks, processing files, iterating through data, and controlling program flow. Bash provides several types of loops, each suited for different scenarios.

Key Concepts

  • Iteration: Repeating a set of commands multiple times
  • Loop Control: Managing loop execution with conditions
  • Loop Types: for, while, until, and select loops
  • Loop Control Statements: break and continue
  • Loop Performance: Different loops have different performance characteristics

1. For Loops

Basic For Loop Syntax

#!/bin/bash
# Basic for loop with list
for fruit in apple banana orange; do
echo "I like $fruit"
done
# Output:
# I like apple
# I like banana
# I like orange
# For loop with variable list
items="one two three"
for item in $items; do
echo "Item: $item"
done
# For loop with wildcards
for file in *.txt; do
echo "Text file: $file"
done

C-style For Loop

#!/bin/bash
# C-style for loop
for (( i=1; i<=5; i++ )); do
echo "Iteration $i"
done
# Multiple variables
for (( i=0, j=10; i<=10; i++, j-- )); do
echo "i=$i, j=$j"
done
# Decrement loop
for (( i=10; i>=1; i-- )); do
echo "Countdown: $i"
done
# Step by 2
for (( i=0; i<=10; i+=2 )); do
echo "Even: $i"
done

For Loop with Ranges

#!/bin/bash
# Brace expansion ranges
for i in {1..5}; do
echo "Number $i"
done
# With step (Bash 4+)
for i in {1..10..2}; do
echo "Odd: $i"
done
# Reverse range
for i in {10..1}; do
echo "Reverse: $i"
done
# Alphabetic ranges
for letter in {a..e}; do
echo "Letter: $letter"
done
for letter in {A..Z}; do
echo -n "$letter "
done
echo

For Loop with Command Substitution

#!/bin/bash
# Loop through command output
for file in $(ls *.txt 2>/dev/null); do
echo "Processing $file"
wc -l "$file"
done
# Loop through lines from file
for line in $(cat file.txt); do
echo "Line: $line"
done
# Better way: read lines with IFS
IFS=$'\n'
for line in $(cat file.txt); do
echo "Line: $line"
done
# Loop through find results
for file in $(find . -name "*.sh" -type f); do
echo "Shell script: $file"
chmod +x "$file"
done

For Loop with Arrays

#!/bin/bash
# Indexed array
fruits=("apple" "banana" "orange" "grape")
# Loop through all elements
for fruit in "${fruits[@]}"; do
echo "Fruit: $fruit"
done
# Loop with index
for i in "${!fruits[@]}"; do
echo "Index $i: ${fruits[$i]}"
done
# Associative array (Bash 4+)
declare -A capitals
capitals=(
["USA"]="Washington"
["France"]="Paris"
["Japan"]="Tokyo"
)
for country in "${!capitals[@]}"; do
echo "$country: ${capitals[$country]}"
done
# Modify array elements
numbers=(1 2 3 4 5)
for i in "${!numbers[@]}"; do
numbers[$i]=$((numbers[$i] * 2))
done
echo "${numbers[@]}"  # 2 4 6 8 10

2. While Loops

Basic While Loop

#!/bin/bash
# Basic while loop
count=1
while [ $count -le 5 ]; do
echo "Count: $count"
((count++))
done
# Infinite loop (Ctrl+C to stop)
while true; do
echo "Running forever..."
sleep 1
done
# Loop with condition at end
while : ; do
echo "Infinite loop with :"
sleep 1
done

While Loop with File Reading

#!/bin/bash
# Read file line by line
while IFS= read -r line; do
echo "Line: $line"
done < file.txt
# Read file with line numbers
line_num=1
while IFS= read -r line; do
echo "$line_num: $line"
((line_num++))
done < file.txt
# Process CSV file
while IFS=',' read -r name age city; do
echo "Name: $name, Age: $age, City: $city"
done < data.csv
# Read from command output
while IFS= read -r line; do
echo "Process: $line"
done < <(ps aux)
# Read multiple files
while IFS= read -r line1 && IFS= read -r line2 <&3; do
echo "File1: $line1"
echo "File2: $line2"
echo "---"
done < file1.txt 3< file2.txt

While Loop with User Input

#!/bin/bash
# Read user input until quit
while true; do
read -p "Enter command (quit to exit): " command
if [ "$command" = "quit" ]; then
break
fi
echo "You entered: $command"
done
# Validate input
while [ -z "$name" ]; do
read -p "Please enter your name: " name
done
echo "Hello, $name!"
# Menu system
while true; do
echo "1. Option 1"
echo "2. Option 2"
echo "3. Quit"
read -p "Choose: " choice
case $choice in
1) echo "Option 1 selected" ;;
2) echo "Option 2 selected" ;;
3) break ;;
*) echo "Invalid option" ;;
esac
done

While Loop with Process Control

#!/bin/bash
# Monitor process
process="myapp"
while pgrep -x "$process" >/dev/null; do
echo "$process is still running..."
sleep 5
done
echo "$process has terminated"
# Wait for file to appear
while [ ! -f "/tmp/ready.txt" ]; do
echo "Waiting for ready file..."
sleep 2
done
echo "Ready file detected!"
# Countdown timer
seconds=10
while [ $seconds -gt 0 ]; do
echo -ne "Time remaining: $seconds seconds\r"
sleep 1
((seconds--))
done
echo -e "\nTime's up!"

3. Until Loops

Basic Until Loop

#!/bin/bash
# Until loop (opposite of while)
count=1
until [ $count -gt 5 ]; do
echo "Count: $count"
((count++))
done
# Wait for condition
until ping -c1 google.com &>/dev/null; do
echo "Waiting for network..."
sleep 2
done
echo "Network is up!"
# Until file exists
until [ -f "/tmp/stop.txt" ]; do
echo "Processing... (create /tmp/stop.txt to stop)"
# Do work
sleep 1
done
echo "Stop file detected, exiting"

Until Loop Examples

#!/bin/bash
# Retry until success
until ./my_script.sh; do
echo "Script failed, retrying in 5 seconds..."
sleep 5
done
# Wait for service
until curl -s http://localhost:8080/health >/dev/null; do
echo "Waiting for service to start..."
sleep 2
done
echo "Service is ready!"
# Until user enters correct password
password="secret123"
input=""
until [ "$input" = "$password" ]; do
read -s -p "Enter password: " input
echo
if [ "$input" != "$password" ]; then
echo "Incorrect password, try again"
fi
done
echo "Access granted!"
# Until condition met
attempt=1
max_attempts=5
until [ $attempt -gt $max_attempts ]; do
echo "Attempt $attempt of $max_attempts"
if some_command; then
echo "Success!"
break
fi
((attempt++))
done
if [ $attempt -gt $max_attempts ]; then
echo "All attempts failed"
fi

4. Select Loops

Basic Select Loop

#!/bin/bash
# Simple menu with select
echo "Choose your favorite color:"
select color in Red Green Blue Yellow Quit; do
case $color in
Red|Green|Blue|Yellow)
echo "You chose $color"
;;
Quit)
echo "Goodbye!"
break
;;
*)
echo "Invalid choice $REPLY"
;;
esac
done
# Select with custom prompt
PS3="Please select an option: "
options=("Option 1" "Option 2" "Option 3" "Exit")
select opt in "${options[@]}"; do
case $opt in
"Option 1")
echo "You selected option 1"
;;
"Option 2")
echo "You selected option 2"
;;
"Option 3")
echo "You selected option 3"
;;
"Exit")
echo "Exiting..."
break
;;
*)
echo "Invalid option $REPLY"
;;
esac
done

Advanced Select Examples

#!/bin/bash
# Dynamic menu from array
servers=("Web Server" "Database Server" "File Server" "Backup Server" "Exit")
PS3="Select server to manage: "
select server in "${servers[@]}"; do
case $server in
"Web Server")
echo "Managing Web Server..."
# Add web server management commands
;;
"Database Server")
echo "Managing Database Server..."
# Add database server management commands
;;
"File Server")
echo "Managing File Server..."
# Add file server management commands
;;
"Backup Server")
echo "Managing Backup Server..."
# Add backup server management commands
;;
"Exit")
echo "Exiting server management"
break
;;
*)
echo "Invalid selection: $REPLY"
;;
esac
done
# Menu with submenus
main_menu() {
PS3="Main menu: "
select main in "Submenu 1" "Submenu 2" "Exit"; do
case $main in
"Submenu 1")
submenu1
;;
"Submenu 2")
submenu2
;;
"Exit")
break 2
;;
esac
done
}
submenu1() {
PS3="Submenu 1: "
select sub in "Action A" "Action B" "Back"; do
case $sub in
"Action A")
echo "Performing Action A"
;;
"Action B")
echo "Performing Action B"
;;
"Back")
break
;;
esac
done
}
submenu2() {
PS3="Submenu 2: "
select sub in "Action X" "Action Y" "Back"; do
case $sub in
"Action X")
echo "Performing Action X"
;;
"Action Y")
echo "Performing Action Y"
;;
"Back")
break
;;
esac
done
}
# Call main menu
main_menu

5. Loop Control Statements

Break Statement

#!/bin/bash
# Break out of loop
for i in {1..10}; do
if [ $i -eq 5 ]; then
echo "Breaking at $i"
break
fi
echo "Number: $i"
done
# Break with labels
outer_loop: for i in {1..3}; do
echo "Outer loop: $i"
for j in {1..3}; do
if [ $i -eq 2 ] && [ $j -eq 2 ]; then
echo "Breaking outer loop"
break outer_loop
fi
echo "  Inner loop: $j"
done
done
# Break from infinite loop
count=0
while true; do
((count++))
if [ $count -gt 10 ]; then
break
fi
echo "Count: $count"
done

Continue Statement

#!/bin/bash
# Skip odd numbers
for i in {1..10}; do
if [ $((i % 2)) -eq 1 ]; then
continue
fi
echo "Even: $i"
done
# Skip specific files
for file in *; do
if [ "$file" = "skip.txt" ] || [ "$file" = "temp.txt" ]; then
continue
fi
echo "Processing: $file"
done
# Continue with labels
for i in {1..3}; do
echo "Outer: $i"
for j in {1..3}; do
if [ $j -eq 2 ]; then
continue 2  # Continue outer loop
fi
echo "  Inner: $j"
done
done

Exit Statement

#!/bin/bash
# Exit script from loop
for file in *; do
if [ ! -r "$file" ]; then
echo "Error: Cannot read $file"
exit 1
fi
echo "Processing $file"
done
# Exit with specific code
check_files() {
local dir="$1"
cd "$dir" || exit 2
for file in *; do
if [ ! -f "$file" ]; then
echo "Not a regular file: $file"
exit 3
fi
done
}

6. Nested Loops

Basic Nested Loops

#!/bin/bash
# Multiplication table
for i in {1..5}; do
for j in {1..5}; do
printf "%4d" $((i * j))
done
echo
done
# Triangle pattern
rows=5
for ((i=1; i<=rows; i++)); do
for ((j=1; j<=i; j++)); do
echo -n "* "
done
echo
done
# 2D array traversal
matrix=(
"1 2 3"
"4 5 6"
"7 8 9"
)
for row in "${matrix[@]}"; do
for value in $row; do
echo -n "$value "
done
echo
done

Complex Nested Loops

#!/bin/bash
# Find pairs that sum to target
target=10
for i in {1..9}; do
for j in {1..9}; do
if [ $((i + j)) -eq $target ]; then
echo "$i + $j = $target"
fi
done
done
# Process files by directory
for dir in */; do
echo "Directory: ${dir%/}"
for file in "$dir"*.txt; do
if [ -f "$file" ]; then
lines=$(wc -l < "$file")
echo "  $file: $lines lines"
fi
done
done
# Time-based scheduling
for hour in {0..23}; do
for minute in {0..59..15}; do
printf "Schedule: %02d:%02d\n" $hour $minute
done
done

7. Loop Control with IFS

IFS Examples

#!/bin/bash
# Default IFS (space, tab, newline)
data="apple banana cherry"
for fruit in $data; do
echo "$fruit"
done
# Custom IFS for CSV
data="John,25,NYC;Jane,30,LA;Bob,35,Chicago"
IFS=';' read -ra records <<< "$data"
for record in "${records[@]}"; do
IFS=',' read -r name age city <<< "$record"
echo "Name: $name, Age: $age, City: $city"
done
# IFS for path splitting
PATH="/usr/local/bin:/usr/bin:/bin"
IFS=':' read -ra paths <<< "$PATH"
for dir in "${paths[@]}"; do
echo "Path directory: $dir"
done
# Preserve leading/trailing spaces
data="  leading  trailing  "
IFS= read -r preserved <<< "$data"
echo "Preserved: '$preserved'"
# Multi-line with IFS
data="line1
line2
line3"
IFS=$'\n' read -ra lines <<< "$data"
for line in "${lines[@]}"; do
echo "Line: $line"
done

8. Loop Optimization

Performance Considerations

#!/bin/bash
# Bad: Command substitution in loop condition
for i in $(seq 1 1000); do
# ... do work
done
# Better: Brace expansion
for i in {1..1000}; do
# ... do work
done
# Best: C-style for loop
for ((i=1; i<=1000; i++)); do
# ... do work
done
# Bad: Repeated external commands
for file in *.txt; do
lines=$(wc -l < "$file")
echo "$file: $lines"
done
# Better: Use built-in operations
for file in *.txt; do
lines=$(grep -c '^' "$file")  # Still external
echo "$file: $lines"
done
# Bad: Reading file line by line
while IFS= read -r line; do
echo "$line"
done < hugefile.txt
# Better for simple operations
cat hugefile.txt
# Use arrays for speed
files=()
for file in *.txt; do
files+=("$file")
done
# Process in chunks
process_chunk() {
local chunk=("${@}")
for item in "${chunk[@]}"; do
# Process item
done
}
chunk_size=10
for ((i=0; i<${#files[@]}; i+=chunk_size)); do
chunk=("${files[@]:i:chunk_size}")
process_chunk "${chunk[@]}" &
done
wait

Loop Optimizations

#!/bin/bash
# Cache expensive operations
files=(*.txt)  # Expand once
for file in "${files[@]}"; do
# Process file
done
# Avoid repeated calculations
max=${#files[@]}
for ((i=0; i<max; i++)); do
file="${files[$i]}"
# Process file
done
# Use built-in commands when possible
# Instead of: lines=$(cat file | wc -l)
lines=$(wc -l < file)
# Parallel processing with background jobs
process_file() {
file="$1"
# Process file
}
for file in *.txt; do
process_file "$file" &
# Limit concurrent jobs
if [ $(jobs -r | wc -l) -ge 4 ]; then
wait
fi
done
wait

9. Practical Examples

File Processing

#!/bin/bash
# Batch rename files
counter=1
for file in *.jpg; do
mv "$file" "image_$(printf "%03d" $counter).jpg"
((counter++))
done
# Find and process files
find . -type f -name "*.log" -print0 | while IFS= read -r -d '' file; do
echo "Processing: $file"
gzip "$file"
done
# Create numbered backups
for file in *.conf; do
cp "$file" "$file.$(date +%Y%m%d).bak"
done
# Check file permissions
for file in *.sh; do
if [ ! -x "$file" ]; then
echo "Making $file executable"
chmod +x "$file"
fi
done
# Process CSV with header
read -r header < data.csv
IFS=',' read -ra columns <<< "$header"
echo "Columns: ${columns[*]}"
line_num=1
while IFS=',' read -ra values; do
((line_num++))
echo "Record $line_num:"
for i in "${!columns[@]}"; do
echo "  ${columns[$i]}: ${values[$i]}"
done
done < data.csv

System Administration

#!/bin/bash
# Monitor processes
processes=("httpd" "mysqld" "sshd")
for proc in "${processes[@]}"; do
if pgrep -x "$proc" >/dev/null; then
echo "$proc is running"
else
echo "$proc is NOT running"
fi
done
# Check disk usage
for dir in /home/*; do
if [ -d "$dir" ]; then
usage=$(du -sh "$dir" | cut -f1)
echo "$(basename "$dir"): $usage"
fi
done
# Test network connectivity
servers=("google.com" "github.com" "stackoverflow.com")
for server in "${servers[@]}"; do
if ping -c1 -W2 "$server" >/dev/null 2>&1; then
echo "✓ $server is reachable"
else
echo "✗ $server is unreachable"
fi
done
# Check service status
services=("nginx" "postgresql" "redis")
for service in "${services[@]}"; do
if systemctl is-active --quiet "$service"; then
echo "✓ $service is active"
else
echo "✗ $service is inactive"
fi
done

Data Processing

#!/bin/bash
# Calculate average
numbers=(10 20 30 40 50)
sum=0
count=0
for num in "${numbers[@]}"; do
sum=$((sum + num))
((count++))
done
average=$((sum / count))
echo "Average: $average"
# Find min and max
min=${numbers[0]}
max=${numbers[0]}
for num in "${numbers[@]}"; do
if [ "$num" -lt "$min" ]; then
min=$num
fi
if [ "$num" -gt "$max" ]; then
max=$num
fi
done
echo "Min: $min, Max: $max"
# Filter data
for num in {1..20}; do
if [ $((num % 3)) -eq 0 ] && [ $((num % 5)) -eq 0 ]; then
echo "FizzBuzz: $num"
elif [ $((num % 3)) -eq 0 ]; then
echo "Fizz: $num"
elif [ $((num % 5)) -eq 0 ]; then
echo "Buzz: $num"
fi
done
# Group by pattern
declare -A groups
for file in *; do
ext="${file##*.}"
groups["$ext"]=$((groups["$ext"] + 1))
done
for ext in "${!groups[@]}"; do
echo "*.${ext}: ${groups[$ext]} files"
done

Utility Scripts

#!/bin/bash
# Progress bar
progress_bar() {
local current=$1
local total=$2
local width=50
local percent=$((current * 100 / total))
local completed=$((current * width / total))
local remaining=$((width - completed))
printf "\r["
printf "%${completed}s" | tr ' ' '#'
printf "%${remaining}s" | tr ' ' '-'
printf "] %d%%" $percent
}
total=100
for i in $(seq 1 $total); do
progress_bar $i $total
sleep 0.1
done
echo
# Countdown timer
countdown() {
local seconds=$1
while [ $seconds -gt 0 ]; do
printf "\rTime remaining: %02d:%02d" $((seconds/60)) $((seconds%60))
sleep 1
((seconds--))
done
echo -e "\nTime's up!"
}
countdown 10
# Retry function
retry() {
local max_attempts=$1
local delay=$2
shift 2
local attempt=1
until "$@" || [ $attempt -ge $max_attempts ]; do
echo "Attempt $attempt failed, retrying in ${delay}s..."
sleep $delay
((attempt++))
done
if [ $attempt -ge $max_attempts ]; then
echo "All attempts failed"
return 1
fi
}
retry 3 5 curl -f http://localhost:8080

10. Loop Patterns and Idioms

Common Loop Patterns

#!/bin/bash
# Read until EOF
while IFS= read -r line; do
echo "$line"
done
# Process arguments
for arg in "$@"; do
echo "Argument: $arg"
done
# Process options
while getopts "ab:c:" opt; do
case $opt in
a) echo "Option a" ;;
b) echo "Option b with value: $OPTARG" ;;
c) echo "Option c with value: $OPTARG" ;;
*) echo "Invalid option" ;;
esac
done
# Process here document
while read -r line; do
echo "Processing: $line"
done << EOF
Line 1
Line 2
Line 3
EOF
# Process command output
ps aux | while read -r line; do
if [[ $line =~ ^USER ]]; then
echo "Header: $line"
else
echo "Process: $line"
fi
done
# Generate sequences
for i in {1..5}; do
for j in {1..5}; do
echo -n "$((i * j)) "
done
echo
done

Loop with Case Statements

#!/bin/bash
# Menu-driven script
while true; do
clear
echo "=== System Manager ==="
echo "1. Show disk usage"
echo "2. Show memory info"
echo "3. Show process info"
echo "4. Exit"
echo -n "Choice: "
read choice
case $choice in
1)
df -h
echo -n "Press Enter to continue..."
read
;;
2)
free -h
echo -n "Press Enter to continue..."
read
;;
3)
ps aux | head -20
echo -n "Press Enter to continue..."
read
;;
4)
echo "Goodbye!"
break
;;
*)
echo "Invalid choice"
sleep 2
;;
esac
done
# Process files based on type
for file in *; do
case $file in
*.sh)
echo "Shell script: $file"
;;
*.txt)
echo "Text file: $file"
;;
*.jpg|*.png|*.gif)
echo "Image: $file"
;;
*)
echo "Other: $file"
;;
esac
done

Loop with Functions

#!/bin/bash
# Function that uses loops
process_files() {
local pattern="$1"
local action="$2"
for file in $pattern; do
if [ -f "$file" ]; then
$action "$file"
fi
done
}
# Function that returns via loop
find_files_by_size() {
local min_size="$1"
local max_size="$2"
for file in *; do
if [ -f "$file" ]; then
size=$(stat -c%s "$file")
if [ "$size" -ge "$min_size" ] && [ "$size" -le "$max_size" ]; then
echo "$file"
fi
fi
done
}
# Recursive function with loop
scan_directory() {
local dir="$1"
local indent="$2"
for item in "$dir"/*; do
if [ -d "$item" ]; then
echo "${indent}📁 $(basename "$item")"
scan_directory "$item" "  $indent"
else
echo "${indent}📄 $(basename "$item")"
fi
done
}
# Usage
process_files "*.sh" "chmod +x"
find_files_by_size 1000 10000
scan_directory "/home/user" ""

11. Error Handling in Loops

Loop Error Handling

#!/bin/bash
# Set error handling
set -e  # Exit on error
set -u  # Exit on undefined variable
# Error handling with trap
trap 'echo "Error on line $LINENO"' ERR
# Check each command
for file in *.txt; do
if ! cp "$file" "/backup/"; then
echo "Failed to copy $file" >&2
continue
fi
if ! gzip "/backup/$file"; then
echo "Failed to compress /backup/$file" >&2
continue
fi
done
# Validate before processing
for file in "$@"; do
if [ ! -f "$file" ]; then
echo "Error: $file is not a regular file" >&2
continue
fi
if [ ! -r "$file" ]; then
echo "Error: $file is not readable" >&2
continue
fi
# Process file
echo "Processing $file"
done
# Retry on failure
max_retries=3
for i in {1..5}; do
retry=0
until curl -f "http://example.com/api/data/$i" -o "data_$i.json"; do
((retry++))
if [ $retry -ge $max_retries ]; then
echo "Failed to fetch data for $i" >&2
break 2
fi
echo "Retry $retry for $i..."
sleep 2
done
done

Logging in Loops

#!/bin/bash
# Simple logging
log_file="/tmp/script.log"
exec > >(tee -a "$log_file") 2>&1
echo "Starting processing at $(date)"
for i in {1..10}; do
echo "Processing iteration $i"
# Do work
if [ $i -eq 5 ]; then
echo "Warning: Something unusual at iteration 5"
fi
done
echo "Finished at $(date)"
# Detailed logging with levels
log() {
local level="$1"
local message="$2"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $message"
}
for i in {1..10}; do
log "INFO" "Starting iteration $i"
if ! some_command; then
log "ERROR" "Command failed at iteration $i"
continue
fi
log "INFO" "Completed iteration $i"
done

12. Loop Performance Comparison

Performance Testing

#!/bin/bash
# Test different loop types
test_performance() {
local iterations=10000
echo "Testing $iterations iterations:"
echo "================================"
# C-style for loop
echo -n "C-style for: "
time {
for ((i=0; i<iterations; i++)); do
:  # null operation
done
}
# Brace expansion for
echo -n "Brace expansion for: "
time {
for i in {1..10000}; do
:
done
}
# While loop
echo -n "While loop: "
time {
i=0
while [ $i -lt $iterations ]; do
((i++))
done
}
# Until loop
echo -n "Until loop: "
time {
i=0
until [ $i -ge $iterations ]; do
((i++))
done
}
}
# Test with arrays
array_size=1000
echo "Testing with array of $array_size elements:"
echo "=========================================="
# Create test array
test_array=($(seq 1 $array_size))
# For loop with elements
echo -n "For loop with elements: "
time {
for elem in "${test_array[@]}"; do
: 
done
}
# For loop with indices
echo -n "For loop with indices: "
time {
for ((i=0; i<${#test_array[@]}; i++)); do
:
done
}
# While loop with counter
echo -n "While loop with counter: "
time {
i=0
while [ $i -lt ${#test_array[@]} ]; do
((i++))
done
}
# Test file reading
test_file="/tmp/test_file.txt"
seq 1 1000 > "$test_file"
echo "Testing file reading methods:"
echo "============================"
# While read
echo -n "While read: "
time {
while read -r line; do
:
done < "$test_file"
}
# For loop with cat
echo -n "For loop with cat: "
time {
IFS=$'\n'
for line in $(cat "$test_file"); do
:
done
}
# mapfile
echo -n "Mapfile: "
time {
mapfile -t lines < "$test_file"
for line in "${lines[@]}"; do
:
done
}
rm "$test_file"

13. Loop Design Patterns

Common Design Patterns

#!/bin/bash
# Producer-Consumer pattern
producer() {
for i in {1..10}; do
echo "Producing item $i"
echo "$i" > /tmp/item
sleep 1
done
}
consumer() {
while true; do
if [ -f /tmp/item ]; then
item=$(cat /tmp/item)
rm /tmp/item
echo "Consuming item $item"
fi
sleep 1
done
}
# Pipeline pattern
pipeline() {
generate_data() {
for i in {1..100}; do
echo $((RANDOM % 100))
done
}
filter() {
while read -r num; do
if [ $((num % 2)) -eq 0 ]; then
echo "$num"
fi
done
}
transform() {
while read -r num; do
echo $((num * 2))
done
}
aggregate() {
local sum=0
local count=0
while read -r num; do
sum=$((sum + num))
((count++))
done
echo "Average: $((sum / count))"
}
generate_data | filter | transform | aggregate
}
# Batch processing pattern
batch_process() {
local batch_size=10
local batch=()
for item in {1..100}; do
batch+=("$item")
if [ ${#batch[@]} -ge $batch_size ]; then
echo "Processing batch: ${batch[*]}"
# Process batch
batch=()
fi
done
# Process remaining
if [ ${#batch[@]} -gt 0 ]; then
echo "Processing final batch: ${batch[*]}"
fi
}
# Rate limiting pattern
rate_limit() {
local delay=1
local max_per_second=5
local count=0
for i in {1..50}; do
# Process item
echo "Processing item $i"
((count++))
if [ $count -ge $max_per_second ]; then
sleep $delay
count=0
fi
done
}

14. Best Practices and Tips

Loop Best Practices

#!/bin/bash
# 1. Always quote variables in loop conditions
# Bad
for file in $files; do
echo "$file"
done
# Good
for file in "${files[@]}"; do
echo "$file"
done
# 2. Use while read for file processing
# Bad
for line in $(cat file.txt); do
echo "$line"
done
# Good
while IFS= read -r line; do
echo "$line"
done < file.txt
# 3. Use proper IFS handling
# Bad
data="a:b:c"
for item in $data; do
echo "$item"
done
# Good
IFS=':' read -ra items <<< "$data"
for item in "${items[@]}"; do
echo "$item"
done
# 4. Avoid unnecessary command substitution
# Bad
for i in $(seq 1 10); do
echo "$i"
done
# Good
for i in {1..10}; do
echo "$i"
done
# 5. Use break and continue appropriately
for file in *; do
[ -f "$file" ] || continue  # Skip non-files
[ "$file" = "stop.txt" ] && break  # Stop if special file
process_file "$file"
done
# 6. Limit loop scope with subshells
(
cd /some/dir || exit
for file in *; do
echo "In subdir: $file"
done
)  # Returns to original directory
# 7. Use arrays for complex data
# Bad
fruits="apple banana orange"
for fruit in $fruits; do
echo "$fruit"
done
# Good
fruits=("apple" "banana" "orange")
for fruit in "${fruits[@]}"; do
echo "$fruit"
done
# 8. Handle signals properly
trap 'echo "Interrupted"; exit 1' INT
for i in {1..10}; do
echo "Working... ($i)"
sleep 1
done
# 9. Use progress indicators for long loops
total=100
for ((i=1; i<=total; i++)); do
# Do work
printf "Progress: %d%%\r" $((i * 100 / total))
done
echo
# 10. Document complex loops
# Process files in chunks to avoid memory issues
# Chunk size: 100 files per batch
chunk_size=100
files=("$@")
for ((i=0; i<${#files[@]}; i+=chunk_size)); do
end=$((i + chunk_size))
[ $end -gt ${#files[@]} ] && end=${#files[@]}
echo "Processing files ${i} to $((end-1))"
for ((j=i; j<end; j++)); do
process "${files[$j]}"
done
done

Loop Tips and Tricks

#!/bin/bash
# 1. Loop over lines with line numbers
line_num=1
while IFS= read -r line; do
printf "%4d: %s\n" $line_num "$line"
((line_num++))
done < file.txt
# 2. Loop with command grouping
for i in {1..3}; do
{
echo "Starting iteration $i"
some_long_running_task
echo "Finished iteration $i"
} &
done
wait
# 3. Loop with timeout
timeout=10
while [ $timeout -gt 0 ] && ! ping -c1 host; do
echo "Waiting for host... ${timeout}s remaining"
((timeout--))
sleep 1
done
# 4. Loop with conditional break
found=false
for file in *; do
if grep -q "pattern" "$file"; then
found=true
break
fi
done
$found && echo "Pattern found" || echo "Pattern not found"
# 5. Loop with error accumulation
errors=()
for file in *; do
if ! process "$file"; then
errors+=("$file")
fi
done
if [ ${#errors[@]} -gt 0 ]; then
echo "Errors in: ${errors[*]}"
fi
# 6. Loop with parallel processing
max_jobs=4
current_jobs=0
for file in *.txt; do
process_file "$file" &
((current_jobs++))
if [ $current_jobs -ge $max_jobs ]; then
wait
current_jobs=0
fi
done
wait
# 7. Loop with dynamic variable names
for i in {1..5}; do
declare "var_$i=Value$i"
done
for i in {1..5}; do
var="var_$i"
echo "${!var}"
done
# 8. Loop with associative arrays
declare -A config
config=(
[host]="localhost"
[port]="8080"
[user]="admin"
)
for key in "${!config[@]}"; do
echo "$key = ${config[$key]}"
done
# 9. Loop with heredoc
while read -r line; do
echo "Line: $line"
done << EOF
This is line 1
This is line 2
This is line 3
EOF
# 10. Loop with process substitution
while read -r file; do
echo "Found: $file"
done < <(find . -name "*.txt")

15. Command Summary and Cheat Sheet

Loop Quick Reference

# For loops
for var in list; do commands; done
for ((init; condition; increment)); do commands; done
# While loops
while condition; do commands; done
# Until loops
until condition; do commands; done
# Select loops
select var in list; do commands; done
# Loop control
break [n]      # Break out of loop (n levels)
continue [n]   # Skip to next iteration (n levels)
exit [n]       # Exit script with status n
return [n]     # Return from function with status n
# Common patterns
for i in {1..10}; do echo $i; done
for ((i=1; i<=10; i++)); do echo $i; done
while IFS= read -r line; do echo "$line"; done < file
until ping -c1 host; do sleep 1; done
select opt in choices; do case $opt in ...) break;; esac; done
# Special variables
$@  # All arguments
$*  # All arguments as string
$#  # Number of arguments
$?  # Exit status
$$  # Process ID
$!  # Last background PID

Loop Examples Summary

Loop TypeUse CaseExample
for inIterate over listfor i in 1 2 3; do echo $i; done
for (( ))Numeric iterationfor ((i=0; i<10; i++)); do echo $i; done
whileCondition-basedwhile [ $count -lt 10 ]; do ((count++)); done
untilWait for conditionuntil ping -c1 host; do sleep 1; done
selectMenu creationselect opt in Start Stop; do echo $opt; done

Common Loop Operations

# Iterate over files
for file in *.txt; do
echo "$file"
done
# Iterate over array
for elem in "${array[@]}"; do
echo "$elem"
done
# Iterate over array indices
for i in "${!array[@]}"; do
echo "$i: ${array[$i]}"
done
# Read file line by line
while IFS= read -r line; do
echo "$line"
done < file.txt
# Process command output
while IFS= read -r line; do
echo "$line"
done < <(command)
# Infinite loop
while true; do
# Do something
sleep 1
done
# Loop with counter
for ((i=1; i<=10; i++)); do
echo "Iteration $i"
done
# Loop with step
for i in {1..10..2}; do
echo "Step: $i"
done

Conclusion

Bash loops are powerful constructs for automation and repetitive tasks:

Key Takeaways

  1. Four loop types: for, while, until, select for different scenarios
  2. Loop control: break, continue, exit for flow management
  3. Performance: Choose the right loop type for your use case
  4. File processing: while read is best for line-by-line processing
  5. Arrays: Use arrays with loops for complex data structures
  6. Error handling: Always validate and handle errors in loops

Best Practices Summary

PracticeWhy
Quote variablesPrevent word splitting
Use while read for filesPreserve whitespace
Prefer built-in operationsBetter performance
Limit loop scopeAvoid side effects
Use arraysHandle complex data
Add progress indicatorsUser feedback for long loops
Handle signalsGraceful interruption
Document complex loopsMaintainability
Use break and continueEfficient loop control
Test edge casesReliability

Common Pitfalls

PitfallSolution
Word splittingQuote variables, set IFS
File globbingUse arrays, quote variables
Subshell issuesUse process substitution
PerformanceUse appropriate loop type
MemoryProcess in chunks
Error handlingCheck exit status, use set -e

Mastering Bash loops is essential for effective shell scripting. They provide the foundation for automating repetitive tasks, processing data, and controlling program flow in the Unix/Linux environment.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper