Sagar.BlogArticle
All posts
All posts
Bash

for Loops — Lists, Ranges, and C-Style

Loop over items, file globs, command output, and number ranges. The for loop is the most versatile iteration tool in Bash.

January 13, 20267 min read
BashLoopsControl FlowScripting

The for loop lets you repeat a block of commands for each item in a collection. In Bash, that collection can be a list of words, a glob pattern, a number range, or the output of a command. It's usually the first loop you reach for.

List Form — for ... in

# Simple word list
for fruit in apple banana cherry; do
    echo "I like $fruit"
done

# Variable containing a list (WARNING: word-splitting is intentional here)
colors="red green blue"
for color in $colors; do    # deliberate word-splitting
    echo "$color"
done

# Array (correct — preserves elements with spaces)
files=("file one.txt" "file two.txt" "notes.md")
for f in "${files[@]}"; do
    echo "Processing: $f"
done

Looping Over Files (Glob)

# All text files in current directory
for file in *.txt; do
    echo "File: $file"
done

# Recursive (Bash 4+ with globstar)
shopt -s globstar
for file in **/*.log; do
    echo "Log: $file"
done

# Safe: handle the case where no files match
shopt -s nullglob    # glob expands to empty if no match
for file in *.csv; do
    echo "CSV: $file"
done
shopt -u nullglob    # restore default

The for-in filename pitfall

Never do this:

for f in $(ls *.txt); do   # ❌ breaks on filenames with spaces

Do this instead:

for f in *.txt; do          # ✅ glob expansion handles spaces correctly

The glob expands safely; ls output is word-split on spaces and newlines.

Brace Expansion Ranges

# Number range
for i in {1..10}; do
    echo "Count: $i"
done

# With step (Bash 4+)
for i in {0..20..5}; do    # 0, 5, 10, 15, 20
    echo "$i"
done

# Letter range
for letter in {a..f}; do
    echo "$letter"
done
# a b c d e f

# Zero-padded numbers
for i in {01..09}; do
    echo "zero-padded: $i"   # 01, 02, ..., 09
done

Variables don't work in {..}

Brace expansion happens before variable substitution:

n=5
for i in {1..$n}; do   # ❌ prints literal "{1..5}" — doesn't expand

Use C-style or seq instead when the limit is a variable:

for (( i=1; i<=n; i++ )); do echo "$i"; done
for i in $(seq 1 "$n"); do echo "$i"; done

C-Style for Loop

# C-style: for ((init; condition; increment))
for (( i=0; i<10; i++ )); do
    echo "i=$i"
done

# Countdown
for (( i=10; i>=1; i-- )); do
    echo "$i..."
done
echo "Blast off!"

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

# Variable limit
limit=5
for (( i=1; i<=limit; i++ )); do   # ✅ works! (unlike brace expansion)
    echo "$i"
done

Looping Over Command Output

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

# Better: read lines safely with while + process substitution
while IFS= read -r line; do
    echo "Line: $line"
done < <(cat /etc/hosts)
Quick Check

Why should you avoid `for f in $(ls *.txt)` for processing files?

Exercise

Write a script that:

  1. Loops through all .sh files in the current directory
  2. For each, runs bash -n to syntax-check it
  3. Prints "OK: filename" if it passes, "FAIL: filename" if it fails
  4. At the end, prints how many passed and how many failed