Terminal · intermediate
Shell loops
Shell for, while, and until constructs repeatedly execute command lists over an argument sequence or while a status condition holds.
Why it matters
Loops automate repeated work, but input parsing and subprocess placement determine whether pathnames, whitespace, and accumulated state remain correct.
Mental model
How to reason about shell loops
Choose an iteration source with explicit record boundaries, bind one logical item, run the body, and use the body's commands and status to continue or stop.
Analogy
A loop is an assembly station receiving one sealed item at a time; reliability depends on the conveyor preserving where each item begins and ends.
Examples
See the boundary, not just the happy path
Worked example · Iterate over script arguments
for path in "$@"; do process "$path"; doneQuoted $@ expands to one word per original argument, and quoting $path preserves that boundary when passed onward.
Worked example · Read lines without backslash processing
while IFS= read -r line || [ -n "$line" ]; do printf '%s\n' "$line"; done < input.txtIFS= preserves edge whitespace, -r preserves backslashes, and the second condition processes a final nonempty record even when the file lacks a trailing newline.
Avoid · Splitting command output
for file in $(find . -type f); do process "$file"; doneThe substitution is split and globbed, so legal filenames can become multiple iterations or expand to unrelated paths.
Common mistakes
Misconceptions to remove early
Using for line in $(cat file)
This iterates over shell-split words rather than lines and also performs filename expansion; use read with explicit input semantics.
Expecting pipeline-loop assignments to persist
Many shells execute pipeline components in subshell environments, so variables changed inside producer | while ... may not survive after the loop.
Quick check
Can you predict the result?
1. Which loop header preserves each original script argument as one iteration?
- • for arg in "$@"
- • for arg in $*
- • for arg in $(printf '%s' "$@")
2. Why use IFS= read -r for general line input?
Keep building