100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Bash

Advanced Loop Patterns

Go beyond basic for loops with C-style iteration, while-read idioms for safe line processing, and loop control with break, continue, and select.

Control FlowAdvanced10 min readJul 10, 2026
Analogies

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.

bash
#!/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
done

command | 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 N and continue N operate on the Nth enclosing loop, essential for controlling nested loops cleanly.
  • Use while IFS= read -r line; do ... done < file for safe line-by-line processing — never for line in $(cat file).
  • IFS= preserves leading/trailing whitespace; -r preserves backslashes exactly as written.
  • Piping into while read creates a subshell, so variable changes inside the loop don't persist afterward.
  • select builds an interactive numbered menu automatically, re-prompting until break is called.
  • Process substitution (< <(command)) avoids the subshell pitfall while keeping the while-read idiom.

Practice what you learned

Was this page helpful?

Topics covered

#Bash#AdvancedBashScriptingStudyNotes#DevOps#AdvancedLoopPatterns#Advanced#Loop#Patterns#Style#Loops#StudyNotes#SkillVeris