Is Ctrl+D really like Enter?
- Posted by Michał ‘mina86’ Nazarewicz on 30th of March 2025
- Share on Bluesky
- Cite
‘Ctrl+D in terminal is like pressing Enter,’ Gynvael claims. A surprising proclamation, but pondering on it one realises that it cannot be discarded out of hand. Indeed, there is a degree of truth to it. However, the statement can create more confusion if it’s made without further explanations which I provide in this article.
To keep things focused, this post assumes terminal in canonical mode. This is what one gets when running bash --noediting
or one of many non-interactive tools which can read data from standard input such as cat
, sed
, sort
etc. Bash, other shells and TUI programs normally run in raw mode and provide their own line editing capabilities.
Talk is cheap
To get to the bottom of things it’s good to look at the sources. In Linux, code handling the tty device resides in drivers/tty
directory. The canonical line editing is implemented in n_tty.c
file which includes the n_tty_receive_char_canon
function containing the following fragment:
if (c == EOF_CHAR(tty)) { c = __DISABLED_CHAR; n_tty_receive_handle_newline(tty, c); return true; } if ((c == EOL_CHAR(tty)) || (c == EOL2_CHAR(tty) && L_IEXTEN(tty))) { if (L_ECHO(tty)) { if (ldata->canon_head == ldata->read_head) echo_set_canon_col(ldata); echo_char(c, tty); commit_echoes(tty); } if (c == '\377' && I_PARMRK(tty)) put_tty_queue(c, ldata); n_tty_receive_handle_newline(tty, c); return true; }
The EOF_CHAR
and EOL_CHAR
cases both end with a call to n_tty_receive_handle_newline
function. Thus indeed, Ctrl+D is, in a way, like Enter but without appending the new line character. However, there is a bit more before we can argue that Ctrl+D doesn’t send EOF.
Source of the confusion
Gynvael brings an example of a TCP socket where read
returns zero if the other side closes the connection. This muddies the waters. Why bring networking when the concept can be compared to regular files? Consider the following program (error handling omitted for brevity):
#include <fcntl.h> #include <stdio.h> #include <unistd.h> int main() { int wr = creat("tmp", 0644); int rd = open("tmp", O_RDONLY); char buf[64]; for (unsigned i = 0; ; ++i) { int len = sprintf(buf, "%u", i); write(wr, buf, len); len = read(rd, buf, sizeof buf); printf("%d:%.*s\n", len, len, buf); len = read(rd, buf, sizeof buf); printf("%d:%.*s\n", len, len, buf); sleep(1); } }
It creates a file and also opens it for reading. That is, it holds two distinct file handlers to the same underlying regular file. One allows writing to it; the other reading from it. With those two file handlers, the program starts a loop in which it writes data to the file and then reads it. The output of the program is as follows:
$ gcc -o test-eof test-eof.c $ ./test-eof 1:0 0: 1:1 0: 1:2 0: 1:3 0: ^C $
Crucially, observe that each read
encounters an end of file. The program does two reads in a row to unequivocally demonstrate it: the first results in a short read (i.e. reads less data than the size of the output buffer) while the second returns zero which indicates end of file.
Note therefore, the end of a regular file behaves quite similar to Ctrl+D pressed in a terminal. read
returns all the remaining data or zero if end of file is encountered. The program can choose to continue reading more data. Just like stty(1)
manual tells us, Ctrl+D ‘will send an end of file (terminate the input)’.
Conclusion
Pressing Ctrl+D in terminal sends end of file, which is a bit like Enter in the sense that it sends contents of the terminal queue to the application. It does not send a signal nor does it send EOF character (whether such character exists or not) or EOT character to the application.
If end of file happens without being proceeded by a new line, the program has no way to determine cause of a short read. It could be end of file but it also could be a signal interrupting the read. To avoid discarding data, the program needs to assume more is incoming. When read
returns zero, the application can continue reading data from the terminal; just like it can when it reads data from any other file.
Appendix
One final curiosity is that in C each line of a text file must end with a new line character. In other words, for a C program to be well-behaved on Linux, a text file (which standard input is an example of) must be empty or end with a new line character. Sending end of file after a non-newline is, I’m afraid, undefined behaviour as far as the language is concerned.
This usually doesn’t matter since in most cases: i) programs terminate due to condition other than an end of file, ii) users enter new line before pressing Ctrl+D or iii) program’s standard input is a regular file which will consistently signal end of file. On the other hand, because it is an undefined behaviour, the C implementation can do the simplest thing without having to worry about any corner cases.