C-Style for Loops and Nested Loop Control
Bash's C-style for (( init; cond; update )) loop is the right tool when you need explicit numeric control over an index, such as generating a fixed number of retries with a backoff delay, rather than iterating over an existing list. Inside nested loops, plain break and continue only affect the innermost loop, but both accept an optional numeric argument — break 2 or continue 2 — to affect an outer loop directly, which avoids the need for flag variables or goto-style workarounds that Bash doesn't have anyway.
Cricket analogy: A C-style loop counting down overs remaining, for (( over=1; over<=20; over++ )), mirrors a T20 innings ticking through exactly 20 overs with a known start and stop condition, unlike a while loop watching for an arbitrary stoppage.
The while read Idiom for Safe Line Processing
The idiomatic way to process a file line by line in Bash is while IFS= read -r line; do ... done < file, not for line in $(cat file), because the for version word-splits on whitespace and expands globs, silently mangling any line containing spaces, tabs, or *. Setting IFS= prevents read from trimming leading and trailing whitespace from each line, and -r prevents backslash sequences from being interpreted, so the line is captured exactly as written in the file. Piping into a while read loop, as in cmd | while read -r line; do ..., runs the loop in a subshell, which means variables modified inside the loop won't persist after it — a frequent surprise this pattern requires you to plan around.
Cricket analogy: Using for line in $(cat file) on a scorecard file is like a scorer splitting 'Virat Kohli' into two separate entries because of the space, whereas while IFS= read -r line treats the whole name as one intact record.
select Menus and Loop Control with break/continue
The select construct builds an interactive numbered menu from a word list, printing options, reading a numeric choice into a variable, and looping until the script explicitly breaks out — it's purpose-built for simple CLI menus without hand-rolling a while loop around read and case. Combined with break and continue, select gives you a clean interactive REPL-style loop: continue re-prompts the menu, while break exits it once the user picks a valid action or an explicit 'quit' option. Because select relies on $PS3 for its prompt and re-displays the menu on invalid input by default, it requires almost no boilerplate compared to writing the equivalent logic manually.
Cricket analogy: A select menu offering 'Bat First', 'Bowl First', 'Quit' at the toss mirrors a captain's decision loop that keeps re-presenting choices until a valid one, like winning the toss, is confirmed.
#!/usr/bin/env bash
set -euo pipefail
# C-style loop with nested break/continue
for (( row=1; row<=3; row++ )); do
for (( col=1; col<=3; col++ )); do
if (( col == 2 && row == 2 )); then
continue 2 # skip rest of this row, move to next row
fi
printf "(%d,%d) " "$row" "$col"
done
done
echo
# Safe line-by-line file processing
while IFS= read -r line; do
echo "Processing: $line"
done < "input.txt"
# Interactive menu with select
PS3="Choose an action: "
select action in "Deploy" "Rollback" "Quit"; do
case $action in
Deploy) echo "Deploying..."; break ;;
Rollback) echo "Rolling back..."; break ;;
Quit) echo "Bye."; break ;;
*) echo "Invalid option, try again." ;;
esac
donecommand | while read -r line; do ... done runs the loop body in a subshell because it's on the right side of a pipe. Any variables set inside the loop (e.g. counters, accumulated strings) are lost once the loop ends. Fix it with process substitution — while read -r line; do ... done < <(command) — which keeps the loop in the current shell, or by using readarray/mapfile to load all lines first.
- Use
for (( init; cond; update ))when you need explicit numeric control over an index or counter. break Nandcontinue Noperate on the Nth enclosing loop, essential for controlling nested loops cleanly.- Use
while IFS= read -r line; do ... done < filefor safe line-by-line processing — neverfor line in $(cat file). IFS=preserves leading/trailing whitespace;-rpreserves backslashes exactly as written.- Piping into
while readcreates a subshell, so variable changes inside the loop don't persist afterward. selectbuilds an interactive numbered menu automatically, re-prompting untilbreakis called.- Process substitution (
< <(command)) avoids the subshell pitfall while keeping the while-read idiom.
Practice what you learned
1. Why should you avoid `for line in $(cat file)` for line-by-line processing?
2. What does `continue 2` do inside a doubly-nested for loop?
3. Why does `cmd | while read -r line; do count=$((count+1)); done; echo $count` often print an unexpected value?
4. What does setting `IFS=` before `read -r line` accomplish?
5. What construct automatically builds a numbered interactive menu in Bash?
Was this page helpful?
You May Also Like
Case Statements in Depth
Learn how Bash's case statement uses glob patterns, fall-through terminators, and pattern lists to replace long if/elif chains.
Advanced Conditionals and Test Operators
Master Bash's test, [ ], and [[ ]] constructs with string, numeric, and file test operators to write robust, bug-free conditional logic.
Process Substitution
Learn how Bash's <(...) and >(...) process substitution let commands read from or write to another command's output as if it were a file.
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
DevOpsApache Kafka Study Notes
Kafka · 30 topics
DevOpsDocker & Kubernetes Study Notes
YAML · 40 topics
DevOpsCI/CD Tools & Pipelines Study Notes
YAML · 37 topics