Introduction to Zsh Loops
Zsh (Z shell) is a powerful shell that extends the Bourne shell syntax with modern features, better interactive usability, and richer scripting capabilities. Among its most useful scripting constructs are loops, which allow you to repeat blocks of code, iterate over collections, and automate repetitive tasks efficiently.
Loops matter because they are the backbone of automation. Whether you are processing files in a directory, parsing command output, or building CLI tools, loops let you write concise, maintainable scripts instead of duplicating code. Zsh supports several loop types — for, while, until, repeat, and select — each suited to different scenarios.
Why Loops Matter in Zsh Scripting
Loops reduce code duplication and make scripts easier to maintain. Instead of writing ten lines to process ten files, a single loop handles them all. They also enable dynamic behavior: your script adapts to runtime data such as file counts, user input, or API responses.
Zsh offers advantages over Bash when looping:
- Native array support with cleaner syntax (
${array[@]}or simply$array). - The unique
repeatloop for fixed iteration counts. - Advanced globbing (e.g., recursive
**/*) that pairs naturally with loops. - Better word splitting behavior, reducing quoting headaches.
For Loops
Basic For Loop Over a List
The simplest form iterates over a space-separated list of items.
#!/usr/bin/env zsh
for fruit in apple banana cherry; do
echo "I like $fruit"
done
Iterating Over Arrays
Zsh arrays are first-class citizens. You can iterate cleanly without worrying about word splitting.
#!/usr/bin/env zsh
languages=(Python Rust Go TypeScript)
for lang in $languages; do
echo "Language: $lang"
done
C-Style For Loops
For numeric ranges or counter-based iteration, Zsh supports C-style syntax.
#!/usr/bin/env zsh
for ((i = 1; i <= 5; i++)); do
echo "Iteration $i"
done
Range Expansion
Zsh supports brace expansion for numeric ranges, which is concise and readable.
#!/usr/bin/env zsh
for n in {1..10}; do
echo "Number: $n"
done
# With a step value
for n in {0..20..2}; do
echo "Even: $n"
done
Looping Over Files with Globbing
Zsh's globbing makes file iteration powerful. Use **/* for recursive matching.
#!/usr/bin/env zsh
# All .zsh files in the current directory
for file in *.zsh; do
echo "Found: $file"
done
# Recursive search
for file in **/*.md; do
echo "Markdown file: $file"
done
While Loops
A while loop runs as long as a condition evaluates to true. It is ideal when the number of iterations is unknown in advance.
#!/usr/bin/env zsh
count=1
while (( count <= 5 )); do
echo "Count is $count"
(( count++ ))
done
Reading Lines from a File
Use while read to process a file line by line safely.
#!/usr/bin/env zsh
while IFS= read -r line; do
echo "Line: $line"
done < input.txt
Reading Command Output
You can pipe command output directly into a while loop.
#!/usr/bin/env zsh
git status --short | while IFS= read -r line; do
echo "Modified: $line"
done
Until Loops
The until loop is the inverse of while: it runs until a condition becomes true. This is useful for polling or waiting for a resource.
#!/usr/bin/env zsh
attempts=0
until ping -c 1 example.com &>/dev/null; do
(( attempts++ ))
echo "Attempt $attempts failed. Retrying..."
sleep 2
if (( attempts >= 5 )); then
echo "Giving up."
exit 1
fi
done
echo "Connection established!"
The Repeat Loop (Zsh-Specific)
Zsh has a unique repeat construct for executing a command a fixed number of times. It is concise and elegant for simple repetition.
#!/usr/bin/env zsh
repeat 3 echo "Hello, Zsh!"
# With a compound command
repeat 5 {
echo "Working..."
sleep 1
}
Select Loops for Menus
The select loop creates interactive menus. It is perfect for CLI tools that need user input.
#!/usr/bin/env zsh
PS3="Choose an option: "
select option in "Start" "Stop" "Restart" "Quit"; do
case $option in
Start) echo "Starting service..." ;;
Stop) echo "Stopping service..." ;;
Restart) echo "Restarting service..." ;;
Quit) echo "Bye!"; break ;;
*) echo "Invalid choice." ;;
esac
done
Loop Control: break and continue
Control the flow of loops with break (exit the loop) and continue (skip to the next iteration).
#!/usr/bin/env zsh
for n in {1..10}; do
if (( n == 3 )); then
continue # Skip 3
fi
if (( n == 7 )); then
break # Stop at 7
fi
echo "Processing $n"
done
Breaking Nested Loops
Use break N to exit multiple loop levels.
#!/usr/bin/env zsh
for outer in 1 2 3; do
for inner in a b c; do
if [[ $outer == 2 && $inner == b ]]; then
break 2
fi
echo "$outer-$inner"
done
done
Practical Examples
Batch File Renaming
#!/usr/bin/env zsh
for file in *.jpg; do
new_name="photo_${file:r:t}.jpg"
mv "$file" "$new_name"
echo "Renamed: $file -> $new_name"
done
Processing CSV Data
#!/usr/bin/env zsh
while IFS=, read -r name email role; do
echo "Name: $name | Email: $email | Role: $role"
done < users.csv
Parallel Task Execution
#!/usr/bin/env zsh
urls=(
"https://example.com/api/1"
"https://example.com/api/2"
"https://example.com/api/3"
)
for url in $urls; do
curl -s "$url" > "${url##*/}.json" &
done
wait
echo "All downloads complete."
Best Practices
- Quote variables: Always use
"$var"to handle filenames with spaces or special characters. - Use
(( ))for arithmetic: It is cleaner and more readable than[ ]for numeric comparisons. - Prefer
[[ ]]for tests: It avoids word-splitting issues and supports pattern matching. - Use
IFS= read -r: This preserves whitespace and backslashes when reading input. - Limit infinite loops: Add counters or timeouts to prevent runaway scripts.
- Enable error handling: Use
set -euo pipefailat the top of scripts to catch errors early. - Avoid subshells when possible: Variables modified inside
while readloops fed by pipes run in subshells. Use process substitution< <(cmd)instead. - Comment complex loops: Document the exit conditions and iteration logic for future maintainers.
Process Substitution to Preserve Variables
#!/usr/bin/env zsh
total=0
while IFS= read -r num; do
(( total += num ))
done < <(seq 1 100)
echo "Total: $total"
Conclusion
Loops are an essential tool in any Zsh scripter's toolkit. From simple list iteration with for to interactive menus with select, Zsh provides a rich set of looping constructs that are both expressive and efficient. By understanding when to use each loop type, applying proper quoting and arithmetic syntax, and following best practices like error handling and process substitution, you can write robust, maintainable scripts that scale with your automation needs. Start small, experiment with the examples above, and you will quickly find loops becoming second nature in your Zsh scripting workflow.