Yes, it's expected.
We say that Ctrl-D makes cat
see "end of file" in the input, and it then stops reading and exits, but that's not really true. Since that's on the terminal, there's no actual "end", and in fact it's not really "end of file" that's ever detected, but any read()
of zero bytes.
Usually, the read()
system call doesn't return zero bytes except when it's known there's no more available, like at the end of a file. When reading from a network socket where there's no data available, it's expected that new data will arrive at some point, so instead of that zero-byte read, the system call will either block and wait for some data to arrive, or return an error saying that it would block. If the connection was shut down, then it would return zero bytes, though.
Then again, even on a file, reading at (or past) the end is not an interminably final end as another process could write something to the file to make it longer, after which a new attempt to read would return more data. (That's what a simple implementation of tail -f
would do.)
For a lot of use-cases treating "zero bytes read" as "end of file detected" happens to work well enough that they're considered effectively the same thing in practice.
What the Ctrl-D does here, is to tell the terminal driver to pass along everything it was given this far, even if it's not a full line yet. At the start of a line, that's all of zero bytes, which is detected as an EOF. But after the letter b
, the first Ctrl-D sends the b
, and then the next one sends the zero bytes entered after the b
, and that now gets detected as the EOF.
You can also see what happens if you just run cat
without a redirection. It'll look something like this, the parts in italics are what I typed:
$ cat
fooCtrl-Dfoo
When Ctrl-D is pressed, cat
gets the input foo
, prints it back and continues waiting for input. The line will look like foofoo
, and there's no newline after that, so the cursor stays there at the end.
readline
manual:end-of-file (usually C-d) The character indicating end-of-file as set, for example, by ``stty''. If this character is read when there are no characters on the line, and point is at the beginning of the line, Readline interprets it as the end of input and returns EOF.
– schrodingerscatcuriosity May 03 '22 at 22:10readline
does it by itself, whilecat
probably just relies on the raw tty behaviour.stty -a
should show the terminal's idea of the "eof" character, something likeeof = ^D
. And you could change it with e.g.stty eof ^Q
. – ilkkachu May 03 '22 at 22:16printf "a\nb" | wc -l
andprintf "a\nb\n" | wc -l
) – Olivier Dulac May 04 '22 at 09:41cat
? Try> test
instead. – studog May 04 '22 at 12:59> test
will juste create a new empty test file.cat > test
will repeat (cat) what is entered (after readline has interpreted any special chars such as ctrl-d, backspaces, etc) and send it line by line to the test file. – Olivier Dulac May 04 '22 at 13:24cat
behind the curtains. Or rather, what ever$NULLCMD
contains, which iscat
by default. In others it'd create an that empty file. – ilkkachu May 04 '22 at 19:20readline
is a userland library for fancy line editing, it's the one used by Bash.cat
very likely doesn't use it, or anything like it. Instead you just get the primitive editing the terminal driver provides. That does include the backspace and Ctrl-D for EOF though, butreadline
supports e.g. moving the cursor in the middle of the entered text too (and stuff like tab-completion), while the terminal driver probably just shows something like^[[D
for the left arrow key etc. (and anyway,cat
is supposed to just copy the bytes verbatim, any fancy editing would break that.) – ilkkachu May 04 '22 at 19:26