Loops
Part of Essentials — Bash Scripting
Fifth in the Essentials Bash series. Assumes you understand Conditionals. Next up: Functions.
Loops are where scripting shifts from "commands in a file" to actual automation. Instead of running a command once, you run it against every server in a list, every log file in a directory, or every line of output from another command.
Where You've Seen This
If you've ever run the same command against a list of servers one at a time, you've already felt the problem loops solve — a loop is that repetition captured in a script. Windows batch files have FOR /F, every scripting language has the same concept; Bash just has two forms depending on whether you know the list upfront.
The for Loop
Use for when you have a defined list to work through — servers to ping, files to process, values to validate. The list can be hardcoded, a glob pattern, an array, or a numeric range.
Iterating Over a List
| Basic for Loop | |
|---|---|
- Always quote
"${array[@]}"— without quotes, elements with spaces split into separate words.
Iterating Over Files
The most common real-world use — process every file matching a glob pattern:
| Loop Over Files | |
|---|---|
When No Files Match
If *.log matches nothing, Bash passes the literal string *.log to your loop as the first item. Use shopt -s nullglob to expand an empty glob to nothing:
| Safe Glob Handling | |
|---|---|
- Unmatched globs now expand to nothing — the loop body never runs if there are no files.
- Restore default behaviour after the loop.
Iterating Over a Range
The while Loop
Use while when you don't know the list size upfront — reading a file line by line, waiting for a condition to become true, or processing a stream of unknown length.
| Basic while Loop | |
|---|---|
Reading a File Line by Line
This is the canonical safe pattern — every part matters:
| Read File Line by Line | |
|---|---|
IFS=clears the field separator so leading/trailing whitespace is preserved.read -rprevents backslash from being treated as an escape character.- Redirects the file into the loop's stdin — the loop reads it line by line without a subshell.
Reading Command Output
When the input is a live command rather than a file, use process substitution to feed it to the loop:
| Read Command Output Line by Line | |
|---|---|
< <(command)is process substitution — it runs the command and presents its output as a file-like stream. Unlike piping towhile(which runs the loop in a subshell), this keeps any variables you set inside the loop visible in the parent shell.
Loop Control
break and continue let you short-circuit a loop without restructuring the whole block:
| break and continue | |
|---|---|
- Skip empty files — move straight to the next iteration.
- Stop the loop entirely when a debug log is reached.
Real-World Loop Patterns
The patterns below draw on commands covered in other Essentials articles — grep for filtering and find for file discovery.
Iterate over a list of servers, track failures, and exit with a non-zero code if any are unreachable — making the script composable with monitoring and alerting tools:
Process every log file in a directory and write a summary report — the kind of script that runs nightly from cron:
Wait for a service to become available, retrying with increasing delays. Essential for deployment scripts that start a service and then need to use it:
- Increasing backoff: 5s, 10s, 15s, 20s, 25s — gives the service more time on each retry.
Practice Exercises
Exercise 1: Count Files by Extension
Write a script that accepts a directory as its argument and prints the count of files with each of these extensions: .log, .gz, and .txt.
Exercise 2: Process a Server List File
Write a script that reads a file of hostnames (one per line, # lines are comments) and for each hostname:
- Skips blank lines and lines starting with
# - Prints
OK: <hostname>if ping succeeds - Prints
FAIL: <hostname>if ping fails - Exits with a code equal to the number of failed hosts (0 if all pass)
Solution
| check-hosts.sh | |
|---|---|
Quick Recap
- Use
forwhen you have a defined list — hardcoded, a glob, an array, or a range - Use
whilewhen you don't know the size upfront — file input, streams, retry loops while IFS= read -r line; do ... done < file— the correct pattern for reading files line by line< <(command)— process substitution; feeds command output to a loop without a subshellshopt -s nullglobbefore glob loops — prevents the literal glob string when no files match- Quote array expansions:
"${array[@]}"— prevents word splitting on elements with spaces breakexits the loop;continueskips to the next iteration
Further Reading
Command References
man bash— the "Looping Constructs" section coversfor,while,until, andselecthelp read— documentation for thereadbuiltin, including all flags
Deep Dives
- Bash FAQ: How do I read a file line by line? — the authoritative guide to the
IFS= read -rpattern - Process Substitution — Wooledge wiki on
< <(command)and when to use it
Official Documentation
Exploring Python
- What Just Broke? — When
while IFS= read -rhits its limit: structured log parsing in Python with pattern matching and output you can actually act on - Run This Everywhere — When your loop-over-hosts script needs parallelism or structured error handling across the fleet
What's Next?
Head to Functions — how to group reusable logic, scope variables with local, and structure larger scripts with the main "$@" pattern.