for name in ... loop relays the exit status of the last command executed.
break a loop, the
break command itself has an exit status of 0 by default, which is therefore relayed by the
for, thus making your
&& test after the
done be equal to
One way to work around that is as you said in your self-answer. That is a nice and clean one, and works well with any POSIX compliant shell.
But it has one drawback in that, by being a sub-shell, it doesn't fully share its Execution Environment1 with its "outer" shell. It does inherit it on beginning execution, but does not "propagate" it back to its "outer" parent on finishing execution, meaning for instance that any variable set or modified within the loop won't be seen after outside the loop.
Speaking for Bash, there is an alternative: you can avoid the sub-shell and still use
break while forcing it to fail by passing it a
-1 argument, because
-1 is always an invalid number of loops to quit. Bash will display an error message for that, but you can mute it by redirecting to
However note also that this behavior is not properly POSIX compliant, because
break is considered a special built-in, and a "special built-in" incurring into error should exit a non-interactive shell (e.g. a script).
1[...]. Changes made to the subshell environment cannot affect the shell’s execution environment. [...]