In a bash script, a `for` loop is nested inside a `while` loop. What do `break 2` and `continue 2` do in the inner body, and what does a plain `break` inside a `case` block within a loop affect?
answer
- the number counts loops, not blocks
- case is not a loop level
- one keyword, two nesting depths
- too big a number clamps silently
- break leaves the loop, not the script
basics
~20 sbreak 2 exits two levels of enclosing loop, so both the for and the while; continue 2 abandons the current inner iteration and starts the next iteration of the outer loop. A break inside case affects the enclosing loop, because case is not a loop.
solid answer
~50 sBoth `break` and `continue` take an optional count naming how many enclosing loops they act on, defaulting to 1. `break 2` inside the inner `for` leaves both loops and resumes after the `while`; `continue 2` stops the inner loop entirely and jumps to the next iteration of the `while`. If the count exceeds the number of enclosing loops, bash acts on the outermost one rather than erroring. The `case` part trips up people coming from C: a `case` block is terminated by `;;`, not by `break`, and `case` does not count as a loop level — so a `break` written inside a `case` arm inside a loop breaks the loop. That is occasionally what you want, and frequently a bug written by someone expecting switch semantics. Note also that `break` only leaves the loop; to end the whole script you need `exit`.
go deeper
Know that the number after break or continue counts enclosing loops, and that break 2 leaves both loops of a two-deep nest while continue 2 moves on to the outer loop's next iteration.
Add the case detail — a case arm ends with ;; and does not count as a loop level, so a break there hits the enclosing loop — and know that an over-large count clamps to the outermost loop silently.
Show judgment about readability: numeric levels couple control flow to nesting depth, so beyond two levels extract a function and use return, and set an explicit flag when the caller needs to know why the loop ended.
Treat deep nested loops with numeric breaks as a signal that logic has outgrown a shell script, and set the team's line for when iteration plus error handling should move into a language with real control flow and testing.
## `break` and `continue` take a level ```bash while read -r dir; do for f in "$dir"/*; do [[ -e $f ]] || continue # level 1 (default): next file if [[ $f == *.stop ]]; then break 2 # leave the for AND the while fi if [[ $f == *.skipdir ]]; then continue 2 # abandon this dir, read the next one fi process "$f" done done < dirs.txt ``` The optional numeric argument counts *enclosing loops*, starting at 1 for the innermost one containing the statement. `break n` terminates n levels; execution resumes at the statement after the outermost loop that was broken. `continue n` terminates the innermost n-1 loops and starts the next iteration of the nth. If n is larger than the number of enclosing loops, bash acts on the outermost loop rather than reporting an error — so an off-by-one here fails silently and does the wrong thing quietly. If n is less than 1, bash reports an error. Only loop constructs count as levels: `while`, `until`, `for` and `for (( ))`. Functions, `if` blocks, `case` arms and `{ ... }` groups do not. ## `break` inside `case` This is the trap for anyone whose reflexes come from C, Java or JavaScript, where `break` ends a `switch` arm: ```bash for arg in "$@"; do case $arg in -v) verbose=1 ;; --) break ;; # ends the FOR loop, not the case *) files+=("$arg") ;; esac done ``` A `case` arm is terminated by `;;`; there is no fallthrough to prevent, so `break` has no case-related meaning at all. It resolves to the nearest enclosing *loop*. In the snippet above that is exactly the desired behaviour — stop parsing options at `--` — but written by accident it silently ends a loop that was supposed to keep going. ## Related boundaries - `break` never ends the script. Use `exit` for that; inside a function, `return` leaves the function (and therefore any loop inside it). - Neither statement changes `$?` in a way you should rely on: after a loop, the exit status is that of the last command actually executed in the body, not a special "was broken" status. If you need to know whether the loop found what it was looking for, set a flag variable explicitly before breaking. - A `break` inside a loop that is running in a subshell — because the loop is a pipeline stage — leaves only that loop, in that child process, which is one more reason to feed loops by redirection rather than by a pipe. ## Style Deep nesting plus numeric levels reads badly: `break 3` forces the reader to count `done` lines to know where control lands. Two levels is about the practical limit; beyond that, extract the inner loops into a function and use `return`, which is self-documenting and does not depend on how the loops happen to be nested today.
- What happens if you write `break 5` inside two nested loops?Bash breaks out of the outermost loop rather than failing, so control resumes after both loops. There is no error message, which makes an off-by-one silently do the wrong thing. A count below 1 is an error instead. Prefer small, obvious counts, or a function with `return`.
- How do you leave a loop and the whole script at once?Use `exit` with a status; `break` only ends loops. Inside a function, `return` leaves the function, which also exits any loop inside it. If the loop runs in a subshell — for example as a pipeline stage — an `exit` there ends only that child process, not the parent script.
saying these in an interview costs you the question
- Says break inside case ends only the case arm
- Thinks break exits the script
- Claims break 2 means break after two iterations
- Counts if blocks or functions as loop levels
- Expects an error when the level exceeds the nesting