Loops in Bash (for, while, until)
Loops let a script repeat a block of commands over a list of items, while a condition holds true, or until a condition becomes true. Bash provides three constructs — for, while, and until — that cover essentially every iteration pattern you'll need: iterating over a fixed list or glob (for), iterating while some state remains true such as reading lines from a file (while), and the less common but occasionally useful inverse, repeating until a condition finally becomes true (until). Choosing the right one, and reading input safely inside them, is central to writing scripts that behave correctly on real, messy data.
Cricket analogy: A captain repeats a set field for every over, keeps rotating bowlers while the pitch stays helpful, or keeps trying different fields until a wicket finally falls — choosing the right approach is central to good captaincy on messy, real match days.
for loops: lists, globs, and C-style
The classic for var in list; do ...; done iterates once per word in list, where the list can be literal values, a glob like *.log, command substitution output, or an array expansion. Bash also supports a C-style for (( i=0; i<n; i++ )) form for numeric counting, which is often clearer than manual arithmetic inside a while loop when you just need to repeat something a fixed number of times.
Cricket analogy: for var in list iterating over a fixed list is like going through specific fielding positions, a glob-style "all outfielders," or a whole squad roster pulled from selection; the C-style counting loop is like simply counting down the remaining ten overs one by one, clearer than tracking overs manually.
# Iterate over a glob (safe: no word-splitting issues since it's not a variable)
for logfile in /var/log/*.log; do
echo "Processing $logfile"
gzip "$logfile"
done
# Iterate over literal values
for name in Alice Bob Carol; do
echo "Hello, $name"
done
# Iterate over an array, always double-quoted with @
users=("alice" "bob" "carol")
for user in "${users[@]}"; do
id "$user" &>/dev/null || echo "$user does not exist"
done
# C-style numeric loop
for (( i=1; i<=5; i++ )); do
echo "Attempt $i of 5"
donewhile and until loops
while command; do ...; done repeats the body as long as command succeeds (exit status 0); until command; do ...; done is its mirror image, repeating as long as command fails. while read -r line; do ...; done < file is the canonical, safe way to process a file line by line in Bash — -r prevents backslash escapes from being interpreted, and redirecting the file directly (rather than piping cat file |) avoids an unnecessary subshell and preserves any variables set inside the loop after it exits.
Cricket analogy: while continuing as long as the pitch report says "batting-friendly" versus until continuing as long as it says "difficult" are mirror strategies; reading match updates ball-by-ball with -r is like taking notes literally rather than interpreting shorthand, and getting the feed directly from the stadium rather than a relay commentator avoids losing details.
# Safe line-by-line file reading
while IFS= read -r line; do
echo "Line: $line"
done < /etc/hosts
# Poll until a service responds, with a timeout
attempts=0
until curl -sf http://localhost:8080/healthz > /dev/null; do
attempts=$((attempts + 1))
if (( attempts >= 30 )); then
echo "Service did not become healthy in time" >&2
exit 1
fi
sleep 2
done
echo "Service is healthy"
# Infinite loop with an explicit break condition
while true; do
read -rp "Continue? [y/n] " answer
[[ "$answer" == "n" ]] && break
donePiping into a while loop, e.g. cat file | while read -r line; do count=$((count+1)); done, runs the loop in a subshell because it's the right side of a pipe. Any variables modified inside the loop (like count) are lost once the subshell exits, silently producing wrong results. Prefer input redirection (while read -r line; do ...; done < file) or process substitution (done < <(command)) so the loop runs in the current shell.
IFS= before read prevents leading/trailing whitespace from being stripped from each line, and -r stops backslashes from being treated as escape characters — together while IFS= read -r line is the idiomatic, safest way to read arbitrary lines, including ones with trailing spaces or embedded backslashes, without silent data mangling.
break and continue
break exits a loop immediately; continue skips the rest of the current iteration and moves to the next one. Both accept an optional numeric argument (break 2, continue 2) to affect an outer loop when loops are nested, which is occasionally useful but can hurt readability if overused — extracting the inner loop into a function is often clearer.
Cricket analogy: break is a captain immediately ending the over after a controversial no-ball dispute; continue is skipping to the next ball without dwelling on a dot ball; break 2 would end both the over and the innings segment at once, though it's clearer to hand that decision to a dedicated review process.
for file in *.csv; do
[[ -s "$file" ]] || continue # skip empty files
if [[ "$file" == "STOP.csv" ]]; then
break
fi
process_csv "$file"
donefor var in list; do ...; doneiterates over words, globs, command output, or array expansions.- C-style
for (( ; ; ))loops are convenient for numeric counting with explicit init/condition/increment. whilerepeats as long as its test command succeeds;untilrepeats as long as its test command fails.while IFS= read -r line; do ...; done < fileis the safe idiom for reading a file line by line without word mangling.- Piping into a while loop runs it in a subshell, silently discarding variable changes made inside; use redirection instead.
breakandcontinuecontrol loop flow and can target an outer loop with a numeric argument when nested.
Practice what you learned
1. What is the idiomatic, safe way to read a file line by line in Bash?
2. Why does `cat file | while read -r line; do count=$((count+1)); done; echo $count` often print the wrong (or empty) count afterward?
3. What is the difference between while and until in Bash?
4. What does `continue` do inside a for loop?
5. Which for loop syntax is best suited for iterating a fixed number of times with an explicit counter?
Was this page helpful?
You May Also Like
Conditionals in Bash (if, case)
Master Bash's conditional constructs — if/elif/else, test expressions, [[ ]] versus [ ], and case statements — to make scripts branch correctly on real-world conditions.
Arrays in Bash
Learn how to create, index, and iterate indexed and associative arrays in Bash, and how to use them to write scripts that handle lists of data correctly.
Sorting and Cutting Text with sort, cut, uniq
Combine sort, cut, and uniq to reorder lines, extract specific columns, and deduplicate or count repeated entries in text data pipelines.
grep and Regular Expressions
Master text searching with grep and the regular expression syntax that powers it, from basic literal matches to extended regex patterns and useful flags.
Related Reading
Related Study Notes in DevOps
Browse all study notesNginx Study Notes
DevOps · 30 topics
DevOpsAnsible Study Notes
DevOps · 30 topics
DevOpsAdvanced Kubernetes Study Notes
Kubernetes · 30 topics
DevOpsAdvanced Bash Scripting Study Notes
Bash · 30 topics
DevOpsApache Kafka Study Notes
Kafka · 30 topics
DevOpsDocker & Kubernetes Study Notes
YAML · 40 topics