Shell Script Loops: Complete Guide and Reference
Table of Contents
1. [Introduction to Shell Loops](#introduction) 2. [Types of Loops](#types-of-loops) 3. [For Loops](#for-loops) 4. [While Loops](#while-loops) 5. [Until Loops](#until-loops) 6. [Loop Control Statements](#loop-control-statements) 7. [Nested Loops](#nested-loops) 8. [Practical Examples](#practical-examples) 9. [Best Practices](#best-practices) 10. [Common Pitfalls](#common-pitfalls)Introduction to Shell Loops {#introduction}
Shell loops are fundamental control structures that allow you to execute a block of code repeatedly based on specific conditions or iterate through collections of data. They are essential for automating repetitive tasks, processing files, handling arrays, and creating efficient shell scripts.
Loops in shell scripting provide the ability to: - Process multiple files or directories - Repeat operations until a condition is met - Iterate through command-line arguments - Handle data from files or command output - Create menu-driven interfaces - Perform batch operations
Types of Loops {#types-of-loops}
Shell scripting supports three primary types of loops, each designed for specific use cases:
| Loop Type | Purpose | When to Use |
|-----------|---------|-------------|
| for | Iterate through a known list or range | When you know the number of iterations or have a specific set of items |
| while | Continue while condition is true | When you need to loop based on a condition that may change during execution |
| until | Continue until condition becomes true | When you want to loop until a specific condition is achieved |
Loop Syntax Comparison Table
| Loop Type | Basic Syntax | Condition Check | Termination |
|-----------|--------------|-----------------|-------------|
| for | for var in list; do ... done | Implicit (list exhaustion) | When list is exhausted |
| while | while condition; do ... done | At beginning of each iteration | When condition becomes false |
| until | until condition; do ... done | At beginning of each iteration | When condition becomes true |
For Loops {#for-loops}
The for loop is the most commonly used loop in shell scripting. It iterates through a list of items, executing the loop body for each item.
Basic For Loop Syntax
`bash
for variable in list
do
# commands to execute
done
`
Alternative Compact Syntax
`bash
for variable in list; do commands; done
`
For Loop Variations
#### 1. Iterating Through a Simple List
`bash
#!/bin/bash
Example 1: Basic list iteration
for fruit in apple banana orange grape
do
echo "Processing: $fruit"
done
`
Output:
`
Processing: apple
Processing: banana
Processing: orange
Processing: grape
`
#### 2. Iterating Through Files
`bash
#!/bin/bash
Example 2: Processing files in current directory
for file in *.txt
do
if [ -f "$file" ]; then
echo "Found text file: $file"
wc -l "$file"
fi
done
`
#### 3. C-Style For Loop
`bash
#!/bin/bash
Example 3: C-style for loop with numeric range
for ((i=1; i<=10; i++))
do
echo "Number: $i"
done
`
#### 4. Using Brace Expansion
`bash
#!/bin/bash
Example 4: Brace expansion for ranges
Numeric range
for num in {1..5} do echo "Count: $num" doneCharacter range
for letter in {a..e} do echo "Letter: $letter" doneStep increment
for num in {0..20..5} do echo "Step by 5: $num" done`#### 5. Command Substitution in For Loops
`bash
#!/bin/bash
Example 5: Using command output as list
Process all users from /etc/passwd
for user in $(cut -d: -f1 /etc/passwd | head -5) do echo "User: $user" doneProcess files from find command
for file in $(find /tmp -name "*.log" -type f) do echo "Log file: $file" tail -5 "$file" done`#### 6. Array Iteration
`bash
#!/bin/bash
Example 6: Iterating through arrays
Declare array
colors=("red" "green" "blue" "yellow")Method 1: Direct array expansion
for color in "${colors[@]}" do echo "Color: $color" doneMethod 2: Index-based iteration
for i in "${!colors[@]}" do echo "Index $i: ${colors[$i]}" done`For Loop with Command Line Arguments
`bash
#!/bin/bash
Example 7: Processing command line arguments
echo "Processing all arguments:" for arg in "$@" do echo "Argument: $arg" done
Alternative using $*
echo "Using \$*:" for arg in $* do echo "Argument: $arg" done`While Loops {#while-loops}
The while loop continues executing as long as the specified condition remains true. It's ideal for situations where you don't know the exact number of iterations needed.
Basic While Loop Syntax
`bash
while condition
do
# commands to execute
done
`
While Loop Examples
#### 1. Basic Counter Loop
`bash
#!/bin/bash
Example 1: Simple counter
counter=1
while [ $counter -le 5 ]
do
echo "Count: $counter"
counter=$((counter + 1))
done
`
#### 2. Reading File Line by Line
`bash
#!/bin/bash
Example 2: Reading file contents
filename="data.txt" line_number=1
while IFS= read -r line
do
echo "Line $line_number: $line"
line_number=$((line_number + 1))
done < "$filename"
`
#### 3. Menu-Driven Interface
`bash
#!/bin/bash
Example 3: Interactive menu
choice=""
while [ "$choice" != "quit" ]
do
echo "Menu Options:"
echo "1. List files"
echo "2. Show date"
echo "3. Show users"
echo "Type 'quit' to exit"
read -p "Enter your choice: " choice
case $choice in
1) ls -la ;;
2) date ;;
3) who ;;
quit) echo "Goodbye!" ;;
*) echo "Invalid option" ;;
esac
done
`
#### 4. Monitoring System Resources
`bash
#!/bin/bash
Example 4: System monitoring
while true
do
clear
echo "System Monitor - $(date)"
echo "========================"
# CPU usage
echo "CPU Usage:"
top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1
# Memory usage
echo "Memory Usage:"
free -h | grep Mem
# Disk usage
echo "Disk Usage:"
df -h | grep -E '^/dev/'
echo "Press Ctrl+C to exit"
sleep 5
done
`
#### 5. Waiting for File Creation
`bash
#!/bin/bash
Example 5: Wait for file to be created
target_file="/tmp/important_file.txt"
echo "Waiting for $target_file to be created..." while [ ! -f "$target_file" ] do echo "Still waiting... $(date)" sleep 2 done
echo "File $target_file has been created!"
`
#### 6. Reading User Input Until Valid
`bash
#!/bin/bash
Example 6: Input validation
valid_input=false
while [ "$valid_input" = false ]
do
read -p "Enter a number between 1 and 10: " number
if [[ "$number" =~ ^[0-9]+$ ]] && [ "$number" -ge 1 ] && [ "$number" -le 10 ]; then
echo "Valid input: $number"
valid_input=true
else
echo "Invalid input. Please try again."
fi
done
`
Until Loops {#until-loops}
The until loop is the opposite of the while loop. It continues executing until the specified condition becomes true.
Basic Until Loop Syntax
`bash
until condition
do
# commands to execute
done
`
Until Loop Examples
#### 1. Basic Counter with Until
`bash
#!/bin/bash
Example 1: Counter using until
counter=1
until [ $counter -gt 5 ]
do
echo "Count: $counter"
counter=$((counter + 1))
done
`
#### 2. Wait Until Service is Running
`bash
#!/bin/bash
Example 2: Wait for service to start
service_name="apache2"
echo "Waiting for $service_name to start..." until systemctl is-active --quiet $service_name do echo "Service not running, waiting..." sleep 3 done
echo "$service_name is now running!"
`
#### 3. Wait Until File Size Reaches Threshold
`bash
#!/bin/bash
Example 3: Monitor file size
target_file="/var/log/application.log" target_size=1048576 # 1MB in bytes
until [ -f "$target_file" ] && [ $(stat -f%z "$target_file" 2>/dev/null || stat -c%s "$target_file" 2>/dev/null || echo 0) -ge $target_size ] do current_size=$(stat -f%z "$target_file" 2>/dev/null || stat -c%s "$target_file" 2>/dev/null || echo 0) echo "Current file size: $current_size bytes, waiting for $target_size bytes..." sleep 5 done
echo "File has reached the target size!"
`
#### 4. Network Connectivity Check
`bash
#!/bin/bash
Example 4: Wait for network connectivity
host="google.com"
echo "Checking network connectivity to $host..." until ping -c 1 "$host" &> /dev/null do echo "No connectivity to $host, retrying..." sleep 5 done
echo "Network connectivity to $host established!"
`
Loop Control Statements {#loop-control-statements}
Loop control statements allow you to alter the normal flow of loop execution.
Control Statement Types
| Statement | Purpose | Effect |
|-----------|---------|---------|
| break | Exit the loop immediately | Terminates the current loop and continues with the next statement after the loop |
| continue | Skip current iteration | Skips the remaining commands in the current iteration and moves to the next iteration |
Break Statement Examples
#### 1. Breaking Out of For Loop
`bash
#!/bin/bash
Example 1: Break in for loop
for num in {1..10}
do
if [ $num -eq 6 ]; then
echo "Breaking at $num"
break
fi
echo "Number: $num"
done
echo "Loop ended"
`
#### 2. Breaking Out of While Loop
`bash
#!/bin/bash
Example 2: Break in while loop
counter=1
while true
do
echo "Counter: $counter"
if [ $counter -eq 5 ]; then
echo "Breaking out of infinite loop"
break
fi
counter=$((counter + 1))
done
`
#### 3. Breaking Out of Nested Loops
`bash
#!/bin/bash
Example 3: Breaking from nested loops
for i in {1..3}
do
echo "Outer loop: $i"
for j in {1..5}
do
if [ $j -eq 3 ]; then
echo " Breaking inner loop at j=$j"
break
fi
echo " Inner loop: $j"
done
done
`
Continue Statement Examples
#### 1. Skipping Even Numbers
`bash
#!/bin/bash
Example 1: Skip even numbers
for num in {1..10}
do
if [ $((num % 2)) -eq 0 ]; then
continue
fi
echo "Odd number: $num"
done
`
#### 2. Processing Valid Files Only
`bash
#!/bin/bash
Example 2: Process only readable files
for file in /etc/*
do
if [ ! -r "$file" ]; then
echo "Skipping unreadable file: $file"
continue
fi
if [ -d "$file" ]; then
echo "Skipping directory: $file"
continue
fi
echo "Processing file: $file"
# Process the file here
done
`
Nested Loops {#nested-loops}
Nested loops are loops inside other loops. They're useful for processing multi-dimensional data or performing complex iterations.
Nested Loop Examples
#### 1. Multiplication Table
`bash
#!/bin/bash
Example 1: Multiplication table
echo "Multiplication Table (1-5):" echo " | 1 2 3 4 5" echo "---|---------------"
for i in {1..5}
do
printf "%2d | " $i
for j in {1..5}
do
result=$((i * j))
printf "%2d " $result
done
echo
done
`
#### 2. File Processing in Multiple Directories
`bash
#!/bin/bash
Example 2: Process files in multiple directories
directories=("/home/user/docs" "/home/user/projects" "/home/user/scripts") extensions=("txt" "log" "conf")
for dir in "${directories[@]}"
do
if [ -d "$dir" ]; then
echo "Processing directory: $dir"
for ext in "${extensions[@]}"
do
echo " Looking for .$ext files:"
for file in "$dir"/*.$ext
do
if [ -f "$file" ]; then
echo " Found: $(basename "$file")"
fi
done
done
fi
done
`
#### 3. Matrix Operations
`bash
#!/bin/bash
Example 3: Simple matrix display
rows=3 cols=4
echo "Matrix ${rows}x${cols}:"
for ((i=1; i<=rows; i++))
do
for ((j=1; j<=cols; j++))
do
value=$((i * cols + j - cols))
printf "%3d " $value
done
echo
done
`
Practical Examples {#practical-examples}
Example 1: Log File Analyzer
`bash
#!/bin/bash
Log file analyzer script
log_file="/var/log/access.log" temp_dir="/tmp/log_analysis"
Create temporary directory
mkdir -p "$temp_dir"echo "Analyzing log file: $log_file" echo "================================"
Count different HTTP status codes
declare -A status_codes while IFS= read -r line do # Extract status code (assuming Apache common log format) status=$(echo "$line" | awk '{print $9}') if [[ "$status" =~ ^[0-9]{3}$ ]]; then ((status_codes[$status]++)) fi done < "$log_file"echo "HTTP Status Code Summary:" for status in "${!status_codes[@]}" do echo "Status $status: ${status_codes[$status]} occurrences" done
Find top 10 IP addresses
echo -e "\nTop 10 IP Addresses:" awk '{print $1}' "$log_file" | sort | uniq -c | sort -nr | head -10 | while read count ip do echo "IP: $ip - $count requests" done`Example 2: System Backup Script
`bash
#!/bin/bash
System backup script with progress indication
backup_source="/home/user/important_data" backup_dest="/backup/$(date +%Y%m%d_%H%M%S)" exclude_patterns=(".tmp" ".log" "cache/*")
echo "Starting backup process..." echo "Source: $backup_source" echo "Destination: $backup_dest"
Create backup directory
mkdir -p "$backup_dest"Build exclude options
exclude_opts="" for pattern in "${exclude_patterns[@]}" do exclude_opts="$exclude_opts --exclude=$pattern" doneGet total number of files to backup
total_files=$(find "$backup_source" -type f | wc -l) current_file=0echo "Total files to backup: $total_files" echo "Progress:"
Backup with progress indication
find "$backup_source" -type f | while read -r file do ((current_file++)) # Calculate progress percentage progress=$((current_file * 100 / total_files)) # Copy file maintaining directory structure relative_path="${file#$backup_source/}" dest_file="$backup_dest/$relative_path" dest_dir=$(dirname "$dest_file") mkdir -p "$dest_dir" cp "$file" "$dest_file" # Display progress every 10 files if [ $((current_file % 10)) -eq 0 ] || [ $current_file -eq $total_files ]; then printf "\rProgress: [%-50s] %d%% (%d/%d files)" \ "$(printf '#%.0s' $(seq 1 $((progress/2))))" \ "$progress" "$current_file" "$total_files" fi doneecho -e "\nBackup completed successfully!"
echo "Backup location: $backup_dest"
`
Example 3: Network Port Scanner
`bash
#!/bin/bash
Simple network port scanner
target_host="$1" start_port="${2:-1}" end_port="${3:-1000}" timeout=1
if [ -z "$target_host" ]; then
echo "Usage: $0
echo "Scanning $target_host for open ports ($start_port-$end_port)..." echo "================================================"
open_ports=() closed_count=0
for ((port=start_port; port<=end_port; port++)) do # Use timeout and netcat to check port if timeout $timeout bash -c "echo >/dev/tcp/$target_host/$port" 2>/dev/null; then echo "Port $port: OPEN" open_ports+=($port) else ((closed_count++)) fi # Progress indicator if [ $((port % 100)) -eq 0 ]; then echo "Scanned up to port $port..." fi done
echo "================================================"
echo "Scan completed!"
echo "Open ports: ${open_ports[@]}"
echo "Closed/filtered ports: $closed_count"
`
Best Practices {#best-practices}
Performance Optimization
| Practice | Description | Example |
|----------|-------------|---------|
| Use appropriate loop type | Choose the right loop for your use case | Use for for known lists, while for conditions |
| Minimize command substitution | Avoid expensive operations inside loops | Store command output in variables before the loop |
| Use built-in operations | Prefer shell built-ins over external commands | Use $((i++)) instead of i=$(expr $i + 1) |
| Quote variables properly | Always quote variables to handle spaces | "$variable" instead of $variable |
Code Quality Guidelines
#### 1. Proper Variable Quoting
`bash
Good practice
for file in "$@" do if [ -f "$file" ]; then echo "Processing: $file" fi doneBad practice - unquoted variables
for file in $@ do if [ -f $file ]; then echo "Processing: $file" fi done`#### 2. Error Handling in Loops
`bash
#!/bin/bash
Good practice with error handling
files_processed=0 errors=0
for file in *.txt do if [ ! -f "$file" ]; then echo "No .txt files found" break fi if cp "$file" "/backup/" 2>/dev/null; then echo "Successfully copied: $file" ((files_processed++)) else echo "Error copying: $file" >&2 ((errors++)) fi done
echo "Summary: $files_processed files processed, $errors errors"
`
#### 3. Using Arrays Effectively
`bash
#!/bin/bash
Efficient array processing
Declare array
declare -a servers=("web1.example.com" "web2.example.com" "db1.example.com")Process array efficiently
for server in "${servers[@]}" do echo "Checking server: $server" if ping -c 1 "$server" &>/dev/null; then echo " $server is reachable" else echo " $server is unreachable" fi done`Memory and Resource Management
#### 1. Handling Large Files
`bash
#!/bin/bash
Memory-efficient file processing
large_file="/var/log/huge_logfile.log" chunk_size=1000
line_count=0
while IFS= read -r line
do
# Process line
echo "Processing line $((++line_count)): ${line:0:50}..."
# Process in chunks to avoid memory issues
if [ $((line_count % chunk_size)) -eq 0 ]; then
echo "Processed $line_count lines, pausing..."
sleep 1
fi
done < "$large_file"
`
#### 2. Resource Cleanup
`bash
#!/bin/bash
Proper resource cleanup
temp_files=() trap 'cleanup' EXIT
cleanup() { echo "Cleaning up temporary files..." for temp_file in "${temp_files[@]}" do if [ -f "$temp_file" ]; then rm "$temp_file" echo "Removed: $temp_file" fi done }
Main processing loop
for i in {1..5} do temp_file="/tmp/process_$i.tmp" temp_files+=("$temp_file") echo "Creating temporary file: $temp_file" echo "Data for process $i" > "$temp_file" # Process the temporary file cat "$temp_file" done`Common Pitfalls and Solutions {#common-pitfalls}
Pitfall Analysis Table
| Pitfall | Problem | Solution | Example |
|---------|---------|----------|---------|
| Infinite loops | Loop condition never becomes false | Always ensure loop variable is modified | Use counters or break conditions |
| Unquoted variables | Variables with spaces break | Always quote variables | Use "$var" not $var |
| Command substitution in loops | Performance degradation | Move expensive operations outside | Pre-calculate values |
| File globbing issues | Patterns don't match files | Check for file existence | Use conditional checks |
Common Mistakes and Fixes
#### 1. Infinite Loop Prevention
`bash
Problem: Infinite loop
counter=1 while [ $counter -le 10 ] do echo "Count: $counter" # Missing: counter increment doneSolution: Always modify loop variable
counter=1 while [ $counter -le 10 ] do echo "Count: $counter" counter=$((counter + 1)) # Essential increment done`#### 2. Proper File Handling
`bash
Problem: Doesn't handle files with spaces
for file in $(ls *.txt) do echo "Processing: $file" doneSolution: Use glob patterns directly
for file in *.txt do if [ -f "$file" ]; then # Check file exists echo "Processing: $file" fi done`#### 3. Array Iteration Issues
`bash
Problem: Incorrect array iteration
array=("item one" "item two" "item three") for item in ${array[@]} # Unquoted - splits on spaces do echo "Item: $item" doneSolution: Proper quoting
array=("item one" "item two" "item three") for item in "${array[@]}" # Quoted - preserves spaces do echo "Item: $item" done`#### 4. Loop Variable Scope
`bash
Problem: Variable scope in subshells
total=0 echo "1 2 3 4 5" | while read -r num do total=$((total + num)) # This runs in a subshell done echo "Total: $total" # Still 0 - subshell variable not accessibleSolution: Use process substitution or here-string
total=0 while read -r num do total=$((total + num)) done <<< "1 2 3 4 5" echo "Total: $total" # Correct total`This comprehensive guide covers all aspects of using loops in shell scripts, from basic syntax to advanced techniques and best practices. The examples provided demonstrate real-world applications and common scenarios you'll encounter when writing shell scripts with loops.