Module 6 — Loops
Updated 3 September 2026
🧠 concept → 🧩 analogy → 🧪 exercise → ✅ expected result (hidden) → 🎯 interview questions (hidden)
Part A — for: once per item on a list
A1. The for-in loop
The for loop runs its body once per word in a list, with a variable holding the current word each time:
for name in alice bob carol; do
echo "Provisioning account for $name"
doneRead it as a sentence: for each name in this list, do the body, done. The pieces: name is an ordinary variable (Module 2 rules apply — read it as "$name" inside the body); the list is any sequence of words; do…done bracket the body the way then…fi bracketed a conditional. And the crucial mechanical fact, on which both the power and the traps of this module hang: the list is built by the shell's ordinary rewriting machinery first — expansion, splitting, globbing (Module 2's pipeline) — and then the loop runs over whatever words emerged.
Where the analogy stops working. The nurse reads names as lines on a form. The shell's clipboard is built by word splitting — one name containing a space becomes two visits to two nonexistent patients. Every for-loop trap in this module is that one mismatch wearing different costumes, so keep the clipboard image and this correction together.
🧪 Exercise 6.1 — the round
for name in alice bob carol; do
echo "Provisioning account for $name"
done✅ Expected result — click to reveal
Provisioning account for alice
Provisioning account for bob
Provisioning account for carolWhat to read out of it: three words in, three iterations out, in list order. Nothing ran in parallel — iteration 2 started only after iteration 1's body finished (parallel fan-out is Module 12's &). If a body command failed mid-round, the loop would carry on to the next item — loops inherit bash's carry-on default (Module 3), and Part D returns to what production does about that.
A2. Counted rounds: ranges, C-style, and a first taste of arithmetic
"Do this N times" is just a for-in over a list of numbers, and Module 1's brace expansion builds the list: for i in {1..5} — bash rewrites {1..5} to 1 2 3 4 5 before the loop ever looks. For loops whose count is computed rather than fixed, bash borrows C's syntax: for ((i=1; i<=3; i++)) — start, keep-going condition, step. Inside (( )) you are in arithmetic land: a small dialect where variables need no $, <= compares numbers (not strings — no Module 5 dictionary surprises), and i++ means add one. The same land is reachable inline anywhere as $(( expression )) — arithmetic expansion, the missing tool for counters: n=$((n-1)) computes n minus one and assigns it back. (Bash arithmetic is integer-only; $((7/2)) is 3. Decimals need other tools — Module 11's awk.)
🧪 Exercise 6.2 — three ways to count
for i in {1..5}; do echo "attempt $i"; done
for ((i=1; i<=3; i++)); do echo "tick $i"; done
n=10
echo "$((n / 3)) and a remainder of $((n % 3))" # integer division; % = remainder✅ Expected result — click to reveal
attempt 1
attempt 2
attempt 3
attempt 4
attempt 5
tick 1
tick 2
tick 3
3 and a remainder of 1What to read out of it: the one-line loop form (; do … ; done) is the same grammar compacted — semicolons standing in for newlines (Module 3's glue). The last line is the arithmetic preview earning its keep: 10 / 3 is 3, not 3.33… — bash truncates, silently. Scripts that compute percentages or averages in bash arithmetic are quietly wrong in the decimals; know the limitation now, reach for awk later.
A3. Looping over files: globs, and the no-match surprise
The right way to loop over files is to let globbing build the clipboard: for f in logs/*.log. Module 2 taught that unquoted * expands to matching filenames — here that is not a hazard but the design: each matching path arrives as one word, spaces and all, because glob results do not get re-split. This is the one place the quoting reflex relaxes on the list side (the glob must be unquoted to work) while staying strict in the body ("$f", always).
🧪 Exercise 6.3 — files done right, and the landmine stepped on
The second loop misbehaves on purpose.
cd ~/bash-course
mkdir -p logs
touch logs/app.log "logs/db backup.log" logs/web.log
for f in logs/*.log; do echo "found: $f"; done
for f in logs/*.zip; do echo "processing: $f"; done # ← no .zip files exist✅ Expected result — click to reveal (contains a deliberate misbehavior)
found: logs/app.log
found: logs/db backup.log
found: logs/web.log
processing: logs/*.zipWhat to read out of it: line 2 is the quiet triumph — db backup.log, spaces included, arrived as one item with no quoting gymnastics: glob-built lists are split-safe. Line 4 is the landmine: no zip files exist, so the loop ran once anyway, holding the raw pattern as if it were a filename. In a report loop this prints nonsense; in a rm/mv loop it acts on nonsense. Production loops over globs either guard ([ -e "$f" ] || continue) or set shopt -s nullglob at the top of the script — pick one and be consistent.
Interview questions — Part A
🎯 "Write down the Syntax for all the loops in Shell Scripting." — asked verbatim at Edureka; FinalRound AI asks "What are the different types of loops in shell scripting? Write a script using a for loop to print numbers 1 to 10."
The direct answer: three constructs. for var in list; do …; done (once per word); while command; do …; done (repeat while the guard succeeds); until command; do …; done (repeat while it fails). Plus bash's C-style counter: for ((i=1; i<=10; i++)); do …; done. The 1-to-10 script: for i in {1..10}; do echo "$i"; done.
Going deeper: say what the list is — the product of expansion, splitting, and globbing — because every follow-up trap question ("what if a filename has a space?") is really asking that; and note while/until take a command whose exit status is the condition, the same mechanism as if (Module 5).
The details that separate candidates: offering {1..10} and the C-style form unprompted, with the rule for choosing (fixed range → brace; computed bound → C-style, since {1..$n} does not expand — brace expansion runs before variables exist, Module 2's pipeline order); and mentioning seq only to say modern bash rarely needs it.
🎯 "How do you use loops to iterate over files in a directory in Bash?" — asked verbatim at Zero To Mastery
The direct answer: for f in /path/*.log; do … "$f" …; done — glob builds the list, each match is one word regardless of spaces; quote "$f" everywhere in the body.
Going deeper: name the two classic failures — parsing ls output instead of globbing (splits on spaces, mangles odd names; the same disease as looping over cat, Part C) and the unmatched-glob pass-through (guard with [ -e "$f" ] || continue or shopt -s nullglob). For recursive trees, find + while read (Part C) or bash's globstar ** are the tools.
The details that separate candidates: stating why glob lists are split-safe (filename expansion happens after word splitting in the pipeline, and its results are never re-split) — mechanism, not folklore; plus the habit of ./-prefixing or using full paths so a filename beginning with - cannot masquerade as an option to the body's commands.
Part B — while and until: repeat on a verdict
B1. while: if that keeps coming back
while command; do … done re-runs its body as long as the guard command keeps exiting 0 — Module 5's if, on repeat. The guard is checked before each round, so a guard that fails immediately means zero iterations. Everything that could stand guard for if can stand guard here: [ ] tests, [[ ]], grep -q, your own scripts. The mirror-image until command loops while the guard fails — tailor-made for "keep waiting until the service answers."
n=3
while [ "$n" -gt 0 ]; do
echo "countdown: $n"
n=$((n-1)) # A2's arithmetic: the loop must CHANGE what the guard measures
done
echo "liftoff"Where the analogy stops working. The seat count changes because guests exist — the world updates itself. In a while loop, nothing updates unless the body does it: forget n=$((n-1)) and the guard measures the same unchanged world forever — the infinite loop. Every accidental infinite loop is a body that stopped moving the thing its guard measures; check that connection first when a script hangs (and Ctrl-C, Module 13's polite kill, gets you out meanwhile).
🧪 Exercise 6.4 — countdown and stakeout
n=3
while [ "$n" -gt 0 ]; do echo "countdown: $n"; n=$((n-1)); done
echo "liftoff"
n=1
until [ "$n" -gt 3 ]; do echo "poll $n"; n=$((n+1)); done✅ Expected result — click to reveal
countdown: 3
countdown: 2
countdown: 1
liftoff
poll 1
poll 2
poll 3What to read out of it: the countdown's guard measured n, and the body moved n — that pairing is what ends a while loop. Before liftoff, the guard ran a fourth time (n=0, -gt 0 fails) — guards always run once more than the body. The until loop polled exactly three times: it keeps going while its guard fails, and [ "$n" -gt 3 ] failed for n=1,2,3 then passed for n=4. If you ever misread an until as "loop while true," the printed poll count is the tell.
B2. break and continue: leaving early, skipping one
Two builtins steer a running loop. break abandons the whole loop immediately — the search-is-over move. continue abandons only the current iteration and jumps to the next item — the skip-this-one move. Both read naturally with Module 5's conditionals: guard, then steer.
🧪 Exercise 6.5 — steer the round
for host in web-1 web-2 db-1 web-3; do
case "$host" in
db-*) echo "skipping $host (not a web host)"; continue ;;
esac
echo "checking $host"
done
for i in {1..10}; do
if [ "$i" -eq 4 ]; then echo "found it at attempt $i"; break; fi
echo "attempt $i: not yet"
done✅ Expected result — click to reveal
checking web-1
checking web-2
skipping db-1 (not a web host)
checking web-3
attempt 1: not yet
attempt 2: not yet
attempt 3: not yet
found it at attempt 4What to read out of it: continue let db-1 announce itself and step aside — web-3 still got its turn; the round survived the skip. break ended the second loop with six attempts unspent — attempts 5–10 simply never printed. Note the division of labor: case recognized the shape (db-*), continue did the steering — Module 5 and this module composing, which is how real scripts read.
Interview questions — Part B
🎯 "What is the difference between break and continue commands?" — asked verbatim at Edureka
The direct answer: break exits the entire loop at once; continue ends only the current iteration and moves to the next. Both work in for, while, and until.
Going deeper: give each its canonical shape — break for searches ("stop at first match") and retry loops ("stop on first success"); continue for filters ("skip items that don't apply"). Both take an optional numeric argument for nested loops: break 2 exits two levels — powerful, rarely wise (a comment is mandatory when you use it).
The details that separate candidates: knowing continue in a while loop still runs the guard next (so a continue above the counter-update line can create an infinite loop — the B1 analogy's broken connection, self-inflicted); and that break/continue affect only loops — in a case you exit a branch with ;;, and mixing those up is a syntax error interviewers bait with.
🎯 "How do you use loops in Bash?" — asked verbatim at Zero To Mastery; KnowledgeHut asks "How do you use loops (e.g., for, while) in a shell script?"
The direct answer: for over lists (words, ranges, globs), while on a repeating verdict, until on a repeating failure, steered by break/continue — each with do…done bodies.
Going deeper: choose by data shape — known list → for; condition-driven → while/until; file lines → while read (never for — the trap has its own section in this module). Show the retry idiom that fuses the family: until curl -fsS "$url" >/dev/null 2>&1; do sleep 2; done — Modules 3, 4, and this one in a single line.
The details that separate candidates: mentioning that loop bodies inherit carry-on-past-failure semantics, and what production does about it (guards in the body, or set -e with its loop subtleties, Module 14); plus the resource nuance that while read < file opens the file once, not per-iteration — a preview of why Part C's pattern scales to gigabyte files.
Part C — Reading a file line by line (the famous trap, and the real pattern)
C1. Why for over a file is wrong
The tempting spelling — for f in $(cat files.txt) — is one of bash's most-written bugs, and you already own every piece of its autopsy. $(cat …) substitutes the file's entire text (Module 2 A3); it is unquoted, so word splitting carves it at every space, tab, and newline alike (Module 2 B1). The loop does not iterate over lines — it iterates over words, and a line monthly report.txt becomes two items. Globbing even runs on each word (a line containing * explodes into filenames — Module 2 B3). The loop's clipboard was built by the wrong machine.
🧪 Exercise 6.6 — the autopsy, live
The first loop mangles its data on purpose.
cd ~/bash-course
printf 'monthly report.txt\nnotes.txt\n' > files.txt
for f in $(cat files.txt); do echo "would archive: [$f]"; done
while IFS= read -r line; do echo "would archive: [$line]"; done < files.txt✅ Expected result — click to reveal (contains a deliberate mangling)
would archive: [monthly]
would archive: [report.txt]
would archive: [notes.txt]
would archive: [monthly report.txt]
would archive: [notes.txt]What to read out of it: two lines in, three archive candidates out of the for-loop — the brackets (Module 2's probe style) show exactly where the split fell. The while-loop below it got both lines intact. Now imagine the body was mv "$f" /archive/ — the for-version just tried to archive two files that don't exist, and if a file named monthly happened to exist, it archived the wrong file with no error at all. That silent last case is why this trap has a section, not a footnote.
C2. The real pattern, word by word: while IFS= read -r line
The correct tool reads one line per iteration, using machinery you now own end to end:
while IFS= read -r line; do
echo "processing: $line"
done < input.txtread is a builtin that reads one line from stdin into a variable, exiting 0 on success and non-zero at end-of-file — a verdict, which is exactly what while wants: the loop runs once per line and stops itself at the file's end. The < input.txt is Module 4's stdin redirection, feeding the whole loop (the file opens once, streams line by line — gigabytes welcome). The two amulets guard the edges: -r stops read from treating backslashes as escapes (without it, a Windows path C:\new\folder arrives as C:newfolder — data silently altered); IFS= blanks the split-variable for just this command, preserving leading and trailing whitespace exactly (indented config lines survive). You met IFS as word splitting's delimiter list (Module 2); setting it empty for the duration of read is the "do not trim anything" instruction.
Where the analogy stops working. A scanner reads any parcel identically. read without its amulets edits the parcels as they pass — eating backslashes (no -r) and trimming whitespace (default IFS). The full incantation while IFS= read -r is not superstition; each token disarms one specific mutilation, and dropping one reintroduces exactly that mutilation and no other.
🧪 Exercise 6.7 — the amulets, tested one at a time
The first read mutilates on purpose.
cd ~/bash-course
printf 'C:\\new\\folder\n' > paths.txt
read line < paths.txt
echo "no -r: [$line]"
read -r line < paths.txt
echo "with -r: [$line]"✅ Expected result — click to reveal (contains a deliberate mutilation)
no -r: [C:newfolder]
with -r: [C:\new\folder]What to read out of it: without -r, the backslashes were consumed as escape prefixes — \n in the data became n, and nobody was told. With -r, the line arrived byte-for-byte. Any data that can contain backslashes — Windows paths, regexes, JSON fragments — is corrupted by a bare read. There is no situation in modern scripting where you want the unescaping, which is why ShellCheck (SC2162) flags every read without -r: the correct spelling is simply read -r, always.
Part D — Choosing your loop, and loops at fleet scale
D1. The decision tree
Diagram source
flowchart TD
A["I need to repeat something"] --> B{"Driven by what?"}
B -->|"a known list<br>of words"| C["for x in a b c"]
B -->|"a range<br>of numbers"| D{"bound known<br>when typed?"}
D -->|"yes"| E["for i in {1..N}"]
D -->|"computed"| F["for ((i=1; i<=n; i++))"]
B -->|"files matching<br>a pattern"| G["for f in *.log<br>+ nullglob or -e guard"]
B -->|"lines of a file<br>or command output"| H["while IFS= read -r line<br>done < file"]
B -->|"a condition<br>not yet true"| I["until check; do<br>sleep; done"]
B -->|"a condition<br>still true"| J["while check; do<br>work; done"]Interview questions — Parts C–D
🎯 "Write a shell script that uses a while loop to read a file line by line and prints each line." — asked verbatim at FinalRound AI; their companion: "Write a shell script that reads a list of names from a file and prints each name on a new line."
The direct answer:
#!/bin/bash while IFS= read -r line; do printf '%s\n' "$line" done < "$1"
Going deeper: justify every token, because that is what the question actually tests — read's per-line verdict drives while; < "$1" streams the file once; -r preserves backslashes; IFS= preserves edge whitespace; printf '%s\n' rather than echo prints hostile lines faithfully (Module 2's echo caveat). Then name the anti-pattern you did not write: for line in $(cat file) splits on all whitespace and glob-expands — words, not lines.
The details that separate candidates: handling the last line when the file lacks a trailing newline (while IFS= read -r line || [ -n "$line" ] — read returns non-zero but still fills the variable); and reading from a command instead of a file with done < <(command) (process substitution, Module 12) rather than piping into while — because a piped while runs in a subshell and its variables vanish (Module 12's headline gotcha, worth naming a module early).
🎯 "What are the different types of loops in shell scripting?" — asked verbatim (with the 1-to-10 exercise) at FinalRound AI; the deeper cut interviewers add: "when is each the wrong choice?"
The direct answer: for (lists, ranges, globs), while (repeat on success), until (repeat on failure), plus C-style counting — with break/continue steering all of them.
Going deeper than the published version — the wrong-choice map: for is wrong for file lines (splitting) and for unbounded conditions (it needs a finite list up front); while true with a manual break is wrong where until condition says the same thing declaratively; C-style is wrong in #!/bin/sh scripts (bashism — dash rejects it, Module 1 C2's dialect trap again).
The details that separate candidates: the clipboard principle stated once, cleanly — a for-list is fully built by expansion before iteration begins, so mutating the list mid-loop does not change the round — and its corollary, that {1..$n} cannot work (brace expansion precedes variable expansion). Mechanism answers outrank syntax answers on every one of these.
Part E — Production
E1. 🏭 Production practices
File lines are read with while IFS= read -r, never for $(cat …). The full incantation is the team spelling; ShellCheck (SC2013 for the cat-loop, SC2162 for bare read) enforces it mechanically.
Glob loops declare their empty-match policy. shopt -s nullglob at the top of the script, or [ -e "$f" ] || continue as the body's first line — one of the two, in every glob loop, so a quiet directory can never feed a literal *.log to the body.
Loop bodies are failure-isolated. Fleet rounds log-and-continue per item (do_thing "$host" || { echo "$host" >> failed.txt; continue; }), and the failures file is the next run's input — retries are designed in, not improvised.
Bodies are idempotent. mkdir -p, versioned or timestamped outputs, checks-before-writes — so re-running a partially completed round is always safe.
Counters and math use $(( )), and its integer-only truncation is respected. Percentages, averages, and anything decimal go to awk — bash arithmetic silently floors, and monitoring thresholds computed with floored division have paged people at 3 a.m.
Unbounded waits get bounds. Every until poll carries a max-attempts counter and a sleep — a stuck dependency should fail the script loudly after N tries, not hang it forever (the pattern appears fully built in Module 15).
E2. Production-practice table
| Symptom | What is really happening | What to run | The fix |
|---|---|---|---|
| A filename loop processed "two files" that are halves of one real name | The list came from $(cat …) or $(ls) — word splitting built the clipboard | Rerun the list-builder through printf '[%s]\n' and count brackets (Module 2's probe) | while IFS= read -r for line data; a glob for directory contents |
| A cleanup loop acted on a literal *.zip | The glob matched nothing and passed through as text — the no-match default | for f in *.zip; do echo "[$f]"; done in an empty dir — watch the pattern print | shopt -s nullglob, or [ -e "$f" ] || continue first in the body |
| Script hangs forever, no output, no error | A while/until guard measures something the body no longer changes | Ctrl-C, then inspect the loop: find the guard's variable, find where the body updates it | Restore the update line; add a max-attempts counter to every polling loop |
| Windows-origin paths in a processed list lost their backslashes | read without -r consumed backslashes as escape prefixes | printf 'a\\b\n' | { read x; echo "$x"; } vs the same with read -r | read -r, always; let ShellCheck SC2162 make it unforgettable |
| Indented lines from a config arrived with their leading spaces stripped | Default IFS trimmed whitespace at both ends of each read line | Compare read -r x vs IFS= read -r x on a line with leading spaces | The full spelling: while IFS= read -r line |
| One dead host ended the whole 500-host round at host 37 | The body's failing command was the last word — or set -e ended the script | Check the loop body for unguarded commands that can fail per-item | Per-item guard: cmd || { log "$host"; continue; }; failures file for retry |
E3. 🎓 Capstone — four tickets from the queue
🎓 Ticket 1 — "Our log-rotation script ran in a freshly provisioned environment and created a compressed archive named *.log.gz. Literally. That is the filename."
Diagnosis. The no-match pass-through (A3): the fresh environment had no logs yet, for f in /var/log/app/*.log iterated once with the literal pattern, and the body faithfully compressed a file it first created by redirecting into "$f".gz. The loop worked exactly as written; the written thing assumed matches always exist.
Work the steps: reproduce in an empty dir: for f in *.log; do echo "[$f]"; done → [*.log]. One line, whole bug.
Fix and prevention: shopt -s nullglob at the top (zero matches → zero iterations), or open the body with [ -e "$f" ] || continue. Then delete the impostor file — carefully: it is named with a glob character, so quote it everywhere: rm '/var/log/app/*.log.gz' (unquoted, that rm is a Module 2 globbing incident waiting to happen — on a now-populated directory).
🎓 Ticket 2 — "A migration script reads paths from manifest.txt and copies each to the new volume. QA reports some files landed with mangled names — C:newconfig instead of the documented C:\new\config — and files with spaces threw 'cannot stat' errors in pairs."
Diagnosis. Two mutilations, two missing amulets (C1/C2). The paired cannot-stat errors are word splitting's fingerprint (Module 2 B1) — the loop is for p in $(cat manifest.txt). The eaten backslashes are read without -r — so somewhere a second reader, or the rewrite of the first, dropped the escape guard.
Work the steps: run the manifest through both list-builders with the bracket probe (Exercise 6.6's exact experiment) and compare item counts against wc -l manifest.txt; then Exercise 6.7's -r test on a backslashed line.
Fix and prevention: the canonical reader — while IFS= read -r path; do cp -- "$path" "$dest/"; done < manifest.txt — plus the || [ -n "$path" ] guard if manifests may lack trailing newlines. Prevention is mechanical: ShellCheck flags both the cat-loop (SC2013) and the bare read (SC2162); wire it into CI and this ticket class retires.
🎓 Ticket 3 — "A 'wait for the database' init loop has hung a deployment for 4 hours: until pg_isready -h $DB_HOST; do sleep 5; done. The DB was actually up the whole time."
Diagnosis. Two stacked faults. The hang-forever part is a missing bound (E1's rule): an until with no attempt limit turns any persistent failure into an infinite stakeout. The "DB was up" part means the guard itself was failing for a different reason — with DB_HOST unset (Module 2's silent empty expansion), pg_isready -h with no value errors out with a usage failure — non-zero forever, truth never consulted.
Work the steps: run the guard alone and read its actual complaint: pg_isready -h "$DB_HOST"; echo $? — the stderr line names the missing argument. printf '[%s]\n' "$DB_HOST" confirms the empty jar.
Fix and prevention: validate inputs before looping ([ -n "$DB_HOST" ] || { echo "DB_HOST unset" >&2; exit 2; }); bound the loop (for i in {1..60}; do pg_isready … && break; sleep 5; done with a final "gave up" exit); and distinguish the guard's failure kinds if the tool provides them — Module 3's not-found-vs-couldn't-look discipline applies to readiness probes too.
🎓 Ticket 4 — "Fleet patch round: for host in $(cat hosts.txt); do ssh $host 'apply-patch'; done. It ran for 40 minutes, then stopped at host 212 when one box refused SSH — 288 hosts unpatched, and nobody can say which of the first 211 actually succeeded."
Diagnosis. Every fleet sin from D1's callout in one line: serial round (40 minutes of single-file SSH), no per-item failure isolation (the refused connection's failing verdict ended the round via the script's set -e), no record of outcomes (success and failure both vanished into scrollback), and the hostlist read by the splitting-vulnerable cat-loop (dormant until a hostname needs escaping, but present).
Work the steps: immediate triage needs the missing record — rerun read-only checks in a loop that writes files: while IFS= read -r host; do ssh -o ConnectTimeout=5 "$host" 'check-patch' && echo "$host" >> ok.txt || echo "$host" >> failed.txt; done < hosts.txt.
Fix and prevention: rebuild the round with the production shape: while read over the manifest; per-host || { echo "$host" >> failed.txt; continue; }; outcomes logged to files that feed the retry; a connect timeout so dead hosts cost 5 seconds, not a hang; and batched parallelism when Module 12's wait arrives. The design goal, stated plainly in the runbook: a patch round must be interruptible, resumable, and auditable — three properties, three files, one while-loop.
E4. Documentation reference
| Topic | Authoritative reference | What you'll find there |
|---|---|---|
| for, while, until | Bash manual — Looping Constructs | Exact syntax and semantics of all three loops, including the C-style for |
| break, continue | Bash manual — Bourne Shell Builtins | Both steering builtins, including their numeric nested-loop arguments |
| read and its options | Bash manual — Bash Builtins | The read builtin: -r, IFS interaction, exit status at end-of-file |
| Why the cat-loop splits | Bash manual — Word Splitting | The IFS mechanism that dismembers unquoted substitutions |
| Glob loops and their empty-match behavior | Bash manual — Filename Expansion | Pattern rules, plus nullglob and friends under "Pattern Matching" |
E5. Self-assessment
Answer from memory, out loud or on paper. Every answer is in this module.
- What exactly is the list a for loop iterates over — built by what, and when?
- Why does {1..$n} fail while for ((i=1; i<=n; i++)) works? Which pipeline fact explains it?
- What happens when for f in *.zip finds no zip files, and what are the two production defenses?
- Why are glob-built lists safe for filenames with spaces while $(ls)-built lists are not?
- What does $((n-1)) do, and what does bash arithmetic silently do to 7/2?
- A while loop hangs forever. What is the first structural question to ask about its guard and body?
- while vs until — one sentence each, and the canonical use of until in deployments.
- break vs continue — and what does continue placed above the counter-update line risk?
- Perform the autopsy on for f in $(cat files.txt): name each rewriting step and what it does to a line containing a space.
- In while IFS= read -r line; do … done < file — justify all four unusual tokens (IFS=, -r, the read-as-guard, the trailing < file).
- What happens to a file's last line if it lacks a trailing newline, and what is the guard?
- Name the three properties a fleet patch round must have, and the file-based pattern that provides them.
E6. Sources
Edureka — Top 60 Shell Scripting Interview Questions and Answers — "Write down the Syntax for all the loops in Shell Scripting.", "What is the difference between break and continue commands?" (page updated Dec 9, 2024)
Zero To Mastery — Bash Interview Prep: 31 Essential Questions and Answers — "How do you use loops in Bash?", "How do you use loops to iterate over files in a directory in Bash?" (published June 18, 2026)
FinalRound AI — 25 Essential Shell Scripting Interview Questions You Need to Know — "What are the different types of loops in shell scripting? Write a script using a for loop to print numbers 1 to 10.", "Write a shell script that uses a while loop to read a file line by line and prints each line.", "Write a shell script that reads a list of names from a file and prints each name on a new line." (page updated April 2, 2025)
KnowledgeHut — Shell Scripting Interview Questions and Answers — "How do you use loops (e.g., for, while) in a shell script?" (no publication date shown on page)
A note on the corpus: loop syntax and file-reading are richly represented in published question lists; the specific traps this module dwells on — the unmatched-glob pass-through and the missing -r/IFS= amulets — are asked about in real interviews mostly as follow-ups ("what breaks if…?") rather than as published questions, and the module treats them at the depth the follow-ups demand. No questions were invented. All expected-result outputs were produced by running the commands on Ubuntu 24.04 / bash 5.2.