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"
doneLooping 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 defaultThe 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
doneVariables 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"
doneLooping 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:
- Loops through all
.shfiles in the current directory - For each, runs
bash -nto syntax-check it - Prints "OK: filename" if it passes, "FAIL: filename" if it fails
- At the end, prints how many passed and how many failed