Judging by your symptoms, it looks like you've redirected stdin to provide the list of words to the while loop either via an input file (foo < file) or via a pipeline (... | foo).
If so, your second read command won't automatically switch back to reading from the terminal; it is still reading from whatever stdin was redirected to, and if that input has been consumed (which is exactly what your while loop does, as chepner points out in a comment), read reads nothing, and returns with exit code 1 (which is what terminated the while loop to begin with).
If you explicitly want the second read command to get user input from the terminal, use:
read -r -p "Do stuff? [Yn] " ans </dev/tty
Note:
Stdin redirected from a (finite) file (or pipeline or process substitution with finite output) is a finite resource that eventually reports an EOF condition once all input has been consumed:
read translates the EOF condition into exit code 1, causing the while loop to exit:
- Specifically, if
read cannot read any more characters, it assigns the null string (empty string) to the specified variable(s) (or $REPLY if none were specified), and sets the exit code to 1.
Note: read may set exit code 1 even when it does read characters (and stores them in the specified variable(s) / $REPLY), namely if the input ends without a delimiter; the delimiter is \n by default, otherwise the delimiter explicitly specified with -d.
Once all input has been consumed, subsequent read commands cannot read anything anymore (the EOF condition persists, and the behavior is as described above).
By contrast, interactive stdin input from a terminal is potentially infinite: additional data is provided by whatever the user types interactively whenever stdin input is requested.
The way to simulate an EOF condition during interactive multiline input (i.e, to terminate an input loop) is to press ^D (Control-D):
When ^D is pressed once at the very start of a line, read returns without reading anything and sets the exit code to 1, just as if EOF had been encountered.
- In other words: the way to terminate unbounded interactive input in a loop is to press ^D after having submitted the last line of input.
By contrast, in the interior of an input line, pressing ^D twice is needed to stop reading and set the exit code to 1, but note that the line typed so far is saved to the target variable(s) / $REPLY.[1]
Since the stdin input stream wasn't actually closed, subsequent read commands work normally and continue to solicit interactive user input.
Caveat: If you press ^D at the shell's prompt (as opposed to while a running program is requesting input), you'll terminate the shell itself.
P.S.:
There is one incidental error in the question:
- The second
read command must place operand ans (the name of the variable to store the input in) after all options in order to work syntactically: read -r -p "Do stuff? [Yn] " ans
[1] As William Pursell points out in a comment on the question: ^D causes the read(2) system call to return with whatever is in the buffer at that point; the direct value returned is the count of characters read.
A count of 0 is how the EOF condition is signaled, and Bash's read translates that into exit code 1, causing termination of the loop.
Thus, pressing ^D at the start of a line, when the input buffer is empty, exits the loop immediately.
By contrast, if characters have already been typed on the line, then the first ^D causes read(2) to return however many characters were typed so far, upon which Bash's read reinvokes read(2), because the delimiter (a newline by default) hasn't been encountered yet.
An immediately following second ^D then causes read(2) to return 0, since no characters were typed, causing Bash's read to set exit code 1 and exit the loop.
whileloop terminates in the first place:readhas a non-zero exit status when there is no (complete) line to read.readcall, there is nothing left for the secondreadcall to read!readcall to the terminal (without input redirection)? In a terminalreadcall, there's nothing to read either, until I type something and press enter. So why doesreadwait for my input on that call and not here?read(the system call) to return 0 which in turn makesread(the bash builtin) return 0.read(the bash builtin) return [set exit code to] 1", not 0).