Loops๐
Part of a deep dive: Bash Scripting
Consult the map
-
Bash Scripting โ step 5 of 6
โ Conditionals ยท you are here ยท 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 Might Have 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.
flowchart TD
Q{"Do you know the full<br/>list before looping?"}
Q -->|"Yes โ an array, a glob,<br/>a range"| F["for loop"]
Q -->|"No โ reading a stream,<br/>waiting on a condition"| W["while loop"]
style Q fill:#1a202c,stroke:#cbd5e0,stroke-width:2px,color:#fff
style F fill:#2f855a,stroke:#cbd5e0,stroke-width:2px,color:#fff
style W fill:#2f855a,stroke:#cbd5e0,stroke-width:2px,color:#fff
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
What's Next?๐
Head to Functions โ how to group reusable logic, scope variables with local, and structure larger scripts with the main "$@" pattern.
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