Loops#

Concepts#

for Loop#

Iterating Over a List#

for item in value1 value2 value3; do
    echo "$item"
done
# Loop over words
for color in red green blue; do
    echo "Color: $color"
done

# Loop over files
for file in /etc/*.conf; do
    echo "Config file: $file"
done

# Loop over command output
for user in $(cut -d: -f1 /etc/passwd | head -5); do
    echo "User: $user"
done

# Loop over array elements
fruits=("apple" "banana" "cherry")
for fruit in "${fruits[@]}"; do
    echo "Fruit: $fruit"
done

# Loop over arguments
for arg in "$@"; do
    echo "Argument: $arg"
done

C-Style for Loop#

for ((i = 0; i < 10; i++)); do
    echo "i = $i"
done

# Count by 2
for ((i = 0; i <= 20; i += 2)); do
    echo "$i"
done

Brace Expansion#

# Range of numbers
for i in {1..5}; do
    echo "Number: $i"
done

# Range with step
for i in {0..100..10}; do
    echo "$i"
done

# Letters
for letter in {a..z}; do
    echo "$letter"
done

Note: Brace expansion {1..5} is a Bash feature. It does not work in sh/dash.

while Loop#

Repeats as long as the condition is true:

while condition; do
    commands
done
# Count down
count=5
while [[ $count -gt 0 ]]; do
    echo "Count: $count"
    ((count--))
done
echo "Done!"

# Wait for a file to appear
while [[ ! -f /tmp/ready.flag ]]; do
    echo "Waiting..."
    sleep 2
done
echo "File appeared!"

# Infinite loop (common for service scripts)
while true; do
    echo "Running... (Ctrl+C to stop)"
    sleep 1
done

until Loop#

Repeats as long as the condition is false (opposite of while):

until condition; do
    commands
done
count=0
until [[ $count -ge 5 ]]; do
    echo "Count: $count"
    ((count++))
done

until is less common than while — they are interchangeable by negating the condition.

Reading Files Line by Line#

The most reliable way to process a file line by line:

while IFS= read -r line; do
    echo "Line: $line"
done < filename.txt
  • IFS= — prevents leading/trailing whitespace from being stripped
  • -r — prevents backslashes from being interpreted as escape characters
  • < filename.txt — redirects the file into the loop’s stdin
# Process /etc/passwd
while IFS=: read -r username _ uid _ _ homedir shell; do
    if [[ $uid -ge 1000 && "$shell" != "/usr/sbin/nologin" ]]; then
        echo "User: $username (UID: $uid, Home: $homedir, Shell: $shell)"
    fi
done < /etc/passwd

Why not for line in $(cat file)? Because:

  1. Word splitting breaks lines with spaces
  2. Globbing expands * in the content
  3. Entire file is read into memory at once

break and continue#

# break — exit the loop entirely
for i in {1..10}; do
    if [[ $i -eq 5 ]]; then
        break
    fi
    echo "$i"
done
# Prints: 1 2 3 4

# continue — skip the rest of this iteration
for i in {1..10}; do
    if [[ $((i % 2)) -eq 0 ]]; then
        continue
    fi
    echo "$i"
done
# Prints: 1 3 5 7 9 (odd numbers only)

Loop Output Redirection#

You can redirect the output of an entire loop:

for file in /etc/*.conf; do
    echo "$file"
done > conffiles.txt
# All output goes to the file, not the screen

while read -r line; do
    echo "Processed: $line"
done < input.txt | sort > output.txt
# Pipe the loop's output to sort, then to a file

Nested Loops#

for i in {1..3}; do
    for j in {1..3}; do
        echo "($i, $j)"
    done
done

Processing Command Output#

# Pipe into while loop
ps aux | while read -r user pid cpu mem vsz rss tty stat start time cmd; do
    if (( $(echo "$mem > 5.0" | bc -l 2>/dev/null) )); then
        echo "High memory: $user $pid $mem% $cmd"
    fi
done

# Using process substitution
while read -r line; do
    echo "Log: $line"
done < <(journalctl -n 5 --no-pager)

Lab#

Exercise 1: for Loop Basics#

cd ~/lab/scripts

cat > forloop.sh << 'SCRIPT'
#!/bin/bash

# Loop over a list
echo "=== Colors ==="
for color in red green blue yellow; do
    echo "  $color"
done

# Loop with brace expansion
echo ""
echo "=== Numbers 1-5 ==="
for i in {1..5}; do
    echo "  Number: $i"
done

# C-style loop
echo ""
echo "=== Countdown ==="
for ((i = 5; i >= 1; i--)); do
    echo "  $i..."
done
echo "  Go!"
SCRIPT

chmod +x forloop.sh
./forloop.sh

Exercise 2: Loop Over Files#

cd ~/lab/scripts

cat > fileloop.sh << 'SCRIPT'
#!/bin/bash

echo "=== Config files in /etc/ (first 10) ==="
count=0
for file in /etc/*.conf; do
    if [[ -f "$file" ]]; then
        size=$(wc -c < "$file")
        echo "  $(basename "$file") — $size bytes"
        ((count++))
        [[ $count -ge 10 ]] && break
    fi
done
echo "Showed $count files."
SCRIPT

chmod +x fileloop.sh
./fileloop.sh

Exercise 3: while Loop and Input#

cd ~/lab/scripts

cat > countdown.sh << 'SCRIPT'
#!/bin/bash
# Countdown timer

read -p "Countdown from? " start

if [[ ! "$start" =~ ^[0-9]+$ ]]; then
    echo "Please enter a number."
    exit 1
fi

while [[ $start -gt 0 ]]; do
    echo "$start..."
    ((start--))
    sleep 1
done
echo "Time's up!"
SCRIPT

chmod +x countdown.sh
./countdown.sh

Exercise 4: Read a File Line by Line#

cd ~/lab/scripts

# Create a sample data file
cat > users.csv << 'EOF'
Alice,Engineering,85000
Bob,Marketing,72000
Carol,Engineering,92000
Dave,Sales,68000
Eve,Marketing,78000
EOF

cat > readcsv.sh << 'SCRIPT'
#!/bin/bash
# Read and process a CSV file

echo "=== Employee Report ==="
printf "%-10s %-15s %10s\n" "Name" "Department" "Salary"
echo "--------------------------------------"

while IFS=, read -r name dept salary; do
    printf "%-10s %-15s %10s\n" "$name" "$dept" "$salary"
done < users.csv

echo ""

# Calculate total salary
total=0
while IFS=, read -r _ _ salary; do
    total=$((total + salary))
done < users.csv
echo "Total payroll: \$$total"
SCRIPT

chmod +x readcsv.sh
./readcsv.sh

Exercise 5: break and continue#

cd ~/lab/scripts

cat > breakcontinue.sh << 'SCRIPT'
#!/bin/bash

echo "=== Find first .sh file in current directory ==="
for file in *; do
    if [[ "$file" == *.sh ]]; then
        echo "Found: $file"
        break
    fi
done

echo ""
echo "=== Skip .csv files, list everything else ==="
for file in *; do
    if [[ "$file" == *.csv ]]; then
        continue
    fi
    echo "  $file"
done
SCRIPT

chmod +x breakcontinue.sh
./breakcontinue.sh

Exercise 6: Multiplication Table#

cd ~/lab/scripts

cat > multitable.sh << 'SCRIPT'
#!/bin/bash
# Generate a multiplication table

n=${1:-5}

for ((i = 1; i <= n; i++)); do
    for ((j = 1; j <= n; j++)); do
        printf "%4d" $((i * j))
    done
    echo ""
done
SCRIPT

chmod +x multitable.sh
./multitable.sh
./multitable.sh 10

Review#

1. What is the difference between `while` and `until`?

while loops as long as the condition is true. until loops as long as the condition is false. They are functionally interchangeable by negating the condition.

2. What is the correct way to read a file line by line?

while IFS= read -r line; do ... done < file. IFS= preserves whitespace, -r prevents backslash interpretation. Do NOT use for line in $(cat file).

3. What does `break` do in a loop?

It immediately exits the loop and continues with the code after the loop. break 2 exits two nested loops.

4. What does `continue` do in a loop?

It skips the rest of the current iteration and goes to the next iteration of the loop.

5. How do you loop over numbers 1 through 100 in Bash?

for i in {1..100}; do ... done (brace expansion) or for ((i = 1; i <= 100; i++)); do ... done (C-style).

6. Why should you avoid `for line in $(cat file)`?

Word splitting breaks lines with spaces into multiple iterations. Globbing expands * characters in the content. The entire file is read into memory at once. Use while read instead.


Previous: Conditionals | Next: Functions and Error Handling