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, andselectloops - Loop Control Statements:
breakandcontinue - 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 Type | Use Case | Example |
|---|---|---|
for in | Iterate over list | for i in 1 2 3; do echo $i; done |
for (( )) | Numeric iteration | for ((i=0; i<10; i++)); do echo $i; done |
while | Condition-based | while [ $count -lt 10 ]; do ((count++)); done |
until | Wait for condition | until ping -c1 host; do sleep 1; done |
select | Menu creation | select 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
- Four loop types:
for,while,until,selectfor different scenarios - Loop control:
break,continue,exitfor flow management - Performance: Choose the right loop type for your use case
- File processing:
while readis best for line-by-line processing - Arrays: Use arrays with loops for complex data structures
- Error handling: Always validate and handle errors in loops
Best Practices Summary
| Practice | Why |
|---|---|
| Quote variables | Prevent word splitting |
Use while read for files | Preserve whitespace |
| Prefer built-in operations | Better performance |
| Limit loop scope | Avoid side effects |
| Use arrays | Handle complex data |
| Add progress indicators | User feedback for long loops |
| Handle signals | Graceful interruption |
| Document complex loops | Maintainability |
Use break and continue | Efficient loop control |
| Test edge cases | Reliability |
Common Pitfalls
| Pitfall | Solution |
|---|---|
| Word splitting | Quote variables, set IFS |
| File globbing | Use arrays, quote variables |
| Subshell issues | Use process substitution |
| Performance | Use appropriate loop type |
| Memory | Process in chunks |
| Error handling | Check 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.