funwithlinux guide

A Comprehensive Guide to Loop Structures in Bash

In the world of shell scripting, automation is king—and loops are the backbone of that automation. Whether you’re processing files, iterating over data, or repeating tasks until a condition is met, loops in Bash are indispensable. They allow you to execute a block of code repeatedly, saving time and reducing human error. This guide dives deep into Bash loop structures, covering their syntax, variations, practical use cases, and best practices. By the end, you’ll be equipped to write efficient, robust scripts that handle repetitive tasks with ease.

Table of Contents

  1. For Loops
  2. While Loops
  3. Until Loops
  4. Nested Loops
  5. Loop Control Statements
  6. Practical Examples & Use Cases
  7. Common Pitfalls & Best Practices
  8. Conclusion
  9. References

For Loops

For loops are ideal for iterating over a predefined list of items (e.g., filenames, numbers, or strings). Bash offers several variations to suit different needs.

Basic For Loop Syntax

The simplest for loop iterates over a list of items:

for item in list; do
    # Commands to execute for each item
done
  • item: A variable that takes the value of each element in list during iteration.
  • list: A space-separated sequence of values (e.g., 1 2 3, file1.txt file2.txt).

Iterating Over a List

Loop through a list of strings or filenames:

# Iterate over a list of fruits
for fruit in apple banana "cherry pie" date; do
    echo "I like $fruit"
done

Output:

I like apple
I like banana
I like cherry pie
I like date

Note: Enclose items with spaces (e.g., “cherry pie”) in quotes to treat them as a single item.

Using Ranges with {start..end}

For numerical or alphabetical ranges, use {start..end} to generate a sequence:

# Iterate from 1 to 5
for num in {1..5}; do
    echo "Number: $num"
done

# Iterate over letters A to D
for letter in {A..D}; do
    echo "Letter: $letter"
done

Output:

Number: 1
Number: 2
Number: 3
Number: 4
Number: 5
Letter: A
Letter: B
Letter: C
Letter: D

Command Substitution in For Loops

Use $(command) to dynamically generate the list of items (e.g., loop through files in a directory):

# List all .txt files in the current directory
for file in $(ls *.txt); do
    echo "Processing: $file"
done

Caution: Avoid ls in loops for filenames with spaces. Use for file in *.txt instead (Bash expands *.txt safely).

C-Style For Loops

For more control (e.g., custom increments/decrements), use C-style syntax with arithmetic expressions:

# Syntax: for ((initialization; condition; increment)); do ... done
for ((i=0; i<5; i++)); do
    echo "Count: $i"
done

# Decrement example (countdown from 3 to 0)
for ((i=3; i>=0; i--)); do
    echo "T-minus $i..."
done

Output:

Count: 0
Count: 1
Count: 2
Count: 3
Count: 4
T-minus 3...
T-minus 2...
T-minus 1...
T-minus 0...

While Loops

While loops run a block of code as long as a condition is true. They’re useful for tasks where the number of iterations isn’t known in advance (e.g., waiting for user input or a system event).

Basic While Loop Syntax

while [ condition ]; do
    # Commands to execute while condition is true
done
  • condition: A test expression (e.g., $count -lt 5, -f "file.txt"). Use [ ] (POSIX test) or [[ ]] (Bash-specific, supports patterns).

Reading Input with while read

A common use case is reading lines from a file or user input:

# Read lines from a file (e.g., "names.txt")
while IFS= read -r line; do
    echo "Hello, $line!"
done < "names.txt"
  • IFS=: Prevents trimming of leading/trailing whitespace.
  • -r: Disables backslash escapes (treats \ as a literal character).
  • < "names.txt": Redirects the file into the loop.

Infinite While Loops

Use while true for infinite loops (terminate with Ctrl+C or break):

# Infinite loop with a 2-second delay
while true; do
    echo "This runs forever (press Ctrl+C to stop)..."
    sleep 2
done

Arithmetic Conditions in While Loops

Use (( )) for arithmetic conditions (e.g., incrementing a counter):

count=0
while (( count < 3 )); do  # Equivalent to [ $count -lt 3 ]
    echo "Count: $count"
    ((count++))  # Increment count by 1
done

Output:

Count: 0
Count: 1
Count: 2

Until Loops

Until loops are the inverse of while loops: they run until a condition becomes true. Use them when you want to repeat a task until a target state is reached.

Basic Until Loop Syntax

until [ condition ]; do
    # Commands to execute until condition is true
done

Practical Use Case: Waiting for a Condition

Example: Wait until a file (data.txt) exists before proceeding:

until [ -f "data.txt" ]; do
    echo "Waiting for data.txt..."
    sleep 1  # Check every second
done
echo "data.txt found!"  

Output (if data.txt is created after 3 seconds):

Waiting for data.txt...
Waiting for data.txt...
Waiting for data.txt...
data.txt found!

Nested Loops

Nested loops are loops inside other loops. They’re useful for multi-dimensional tasks (e.g., rows and columns, or files in subdirectories).

Example: Multi-Dimensional Iteration

Print a 3x3 grid of numbers:

# Outer loop (rows)
for ((row=1; row<=3; row++)); do
    # Inner loop (columns)
    for ((col=1; col<=3; col++)); do
        echo -n "$row,$col  "  # -n: No newline
    done
    echo  # Newline after each row
done

Output:

1,1  1,2  1,3  
2,1  2,2  2,3  
3,1  3,2  3,3  

Loop Control Statements

Bash provides break and continue to modify loop behavior.

Break: Exit a Loop Early

break terminates the nearest enclosing loop:

# Break when count reaches 3
for ((i=1; i<=5; i++)); do
    if ((i == 3)); then
        break  # Exit loop
    fi
    echo "i = $i"
done

Output:

i = 1
i = 2

Continue: Skip to the Next Iteration

continue skips the rest of the current iteration and moves to the next:

# Skip even numbers
for ((i=1; i<=5; i++)); do
    if ((i % 2 == 0)); then
        continue  
    fi
    echo "Odd number: $i"
done

Output:

Odd number: 1
Odd number: 3
Odd number: 5

Practical Examples & Use Cases

Example 1: Backup Log Files

Use a for loop to back up .log files with timestamps:

LOG_DIR="/var/log"
BACKUP_DIR="$HOME/log_backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

# Create backup directory if it doesn't exist
mkdir -p "$BACKUP_DIR"

# Loop through log files
for logfile in "$LOG_DIR"/*.log; do
    # Skip if not a regular file
    [ -f "$logfile" ] || continue
    
    # Backup the file with timestamp
    cp "$logfile" "$BACKUP_DIR/$(basename "$logfile").$TIMESTAMP.bak"
    echo "Backed up: $(basename "$logfile")"
done

Example 2: Monitor Disk Space

Use a while loop to check disk space every 5 minutes:

THRESHOLD=90  # Alert if usage > 90%
MOUNT_POINT="/"

while true; do
    # Get disk usage percentage (e.g., "85" for 85%)
    USAGE=$(df -P "$MOUNT_POINT" | awk 'NR==2 {print $5}' | sed 's/%//')
    
    if (( USAGE > THRESHOLD )); then
        echo "ALERT: Disk usage on $MOUNT_POINT is $USAGE% (over $THRESHOLD%)"
        # Add notification logic here (e.g., email, Slack)
    else
        echo "Disk usage on $MOUNT_POINT: $USAGE%"
    fi
    
    sleep 300  # Check every 5 minutes (300 seconds)
done

Common Pitfalls & Best Practices

  1. Unquoted Variables: Always quote variables (e.g., "$file") to handle filenames with spaces:

    # Bad: Splits "cherry pie.txt" into "cherry" and "pie.txt"
    for file in $files; do ... done
    
    # Good: Treats "cherry pie.txt" as one item
    for file in "$files"; do ... done
  2. Infinite Loops: Ensure loop conditions eventually change. For example, in while ((i < 5)), increment i with ((i++)).

  3. Overusing ls in For Loops: Use glob patterns (*.txt) instead of $(ls *.txt) to avoid issues with special characters.

  4. Arithmetic in [ ]: Use (( )) for arithmetic (e.g., ((i++))) instead of [ $i -eq 5 ] for better readability.

  5. Test with echo First: Before running destructive commands (e.g., rm, cp), test loops with echo to verify behavior:

    # Test first
    for file in *.txt; do echo "Would delete: $file"; done
    
    # Then run
    for file in *.txt; do rm "$file"; done

Conclusion

Loops are the workhorses of Bash scripting, enabling automation of repetitive tasks with precision and flexibility. By mastering for, while, and until loops—along with control statements like break and continue—you’ll be able to write scripts that handle everything from file processing to system monitoring.

Practice with real-world scenarios (e.g., backups, log analysis) to solidify your understanding. Remember to follow best practices like quoting variables and testing conditions to avoid common pitfalls.

References