Linux Terminal: CTRL+D is like pressing ENTER
-
$ cat You sound very nice :) You sound very nice :) Bye<ctl-d>Bye Oh wait, and cool too Oh wait, and cool too <ctl-d> $
The Ctl-D didn't end the file when i typed "Bye"
it only worked when I pressed Ctl-D on its own line. So how does cat know that it should ignore the EOF character if there is some text that comes before it?
What Ctl-D does is flush the input to the program, and the program sees how big that input is. If the length of the input is 0 that is interpreted as EOF. So Ctl-D is like Enter because they both flush the input, but Ctl-D is unlike Enter because it does not append a newline before flushing, and as a consequence you can send empty input (aka an EOF "character") with Ctl-D.
When running cat this way, you are in "cooked mode". A ctrl-d does nothing on a non-empty line.
The shell usually runs in non-cokked, or raw, mode as well as nonblocking mode. Where it sees (nearly) every key you press as you press them. Which is why it " sees" the ctrl-d even when you are not on an empty line.
You can learn more here:
- https://jvns.ca/blog/2024/11/26/terminal-rules/#rule-3-repls-should-quit-when-you-press-ctrl-d-on-an-empty-line. (And more generally, from her various blog posts on the matter)
- https://www.gnu.org/software/mit-scheme/documentation/stable/mit-scheme-ref/Terminal-Mode.html
-
$ cat You sound very nice :) You sound very nice :) Bye<ctl-d>Bye Oh wait, and cool too Oh wait, and cool too <ctl-d> $
The Ctl-D didn't end the file when i typed "Bye"
it only worked when I pressed Ctl-D on its own line. So how does cat know that it should ignore the EOF character if there is some text that comes before it?
What Ctl-D does is flush the input to the program, and the program sees how big that input is. If the length of the input is 0 that is interpreted as EOF. So Ctl-D is like Enter because they both flush the input, but Ctl-D is unlike Enter because it does not append a newline before flushing, and as a consequence you can send empty input (aka an EOF "character") with Ctl-D.
This!
It's merely a buffer flush, in case it's empty, the program handling the input can choose how to interpret,
cat
decides to do it as an EOF. -
Honestly I had no idea what ctrl+d even did, I just knew it was a convenient way for me to close all the REPL programs I use. The fact that it is similar to pressing enter really surprised me, so I wanted to share this knowledge with you
Itâs not. You keep insisting that ^D doesnât send EOF and yet:
$ stty -a | grep eof intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; $ man stty |grep -A1 eof |head -n2 eof CHAR CHAR will send an end of file (terminate the input)
^D is the EOF character. The thing is that in C every line of a text
file must be terminated by a new-line. And so, when you end a file
with ^D without a return, you get funky results.If you think ^D is like Enter, start a shell and without running a command press ^D.
-
Itâs not. You keep insisting that ^D doesnât send EOF and yet:
$ stty -a | grep eof intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; $ man stty |grep -A1 eof |head -n2 eof CHAR CHAR will send an end of file (terminate the input)
^D is the EOF character. The thing is that in C every line of a text
file must be terminated by a new-line. And so, when you end a file
with ^D without a return, you get funky results.If you think ^D is like Enter, start a shell and without running a command press ^D.
To be more precise, it's the "EOT" (end of transmission) control character, the 4th symbol in ASCII, from the non-printable character area.
-
CTRL+M is like pressing enter. Kernigan & Pike, 1984: UNIX Programming Enviornment
RETURN is an example of a control character â an invisible character that
controls some aspect of input and output on the terminal. On any reasonable
terminal, RETURN has a key of its own, but most control characters do not.
Instead, they must be typed by holding down the CONTROL key, sometimes
called CTL or CNTL or CTRL, then pressing another key, usually a letter. For
example, RETURN may be typed by pressing the RETURN key or,
equivalently, holding down the CONTROL key and typing an âmâ. RETURN
might therefore be called a control-m, which we will write as ctl-m.Yes,
^M
it's the "Carriage Return" (CR) character,/r
, the 13th control character in ASCII.That's why if you open in Linux a Windows file in vim with Windows style EOL, you always see a strange
^M
symbol at the end of each line. -
Honestly I had no idea what ctrl+d even did, I just knew it was a convenient way for me to close all the REPL programs I use. The fact that it is similar to pressing enter really surprised me, so I wanted to share this knowledge with you
Lol wrong again
-
Itâs not. You keep insisting that ^D doesnât send EOF and yet:
$ stty -a | grep eof intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; $ man stty |grep -A1 eof |head -n2 eof CHAR CHAR will send an end of file (terminate the input)
^D is the EOF character. The thing is that in C every line of a text
file must be terminated by a new-line. And so, when you end a file
with ^D without a return, you get funky results.If you think ^D is like Enter, start a shell and without running a command press ^D.
Note: for novices reading along at home, the notation
^X
means hold down the ctrl key and type x (without shift).ctrl-a though ctrl-z will send ASCII characters 1 through 26, which are called control characters (because they're for controling things, and also because you can type them by holding down the control key).
^D is the EOF character.
Nope, Chuck Testa: there is no EOF character. ^*^
"D" being the fourth letter of the alphabet, sends ASCII character 4, which (as you can see in
man ascii
) is called EOT or "end of transmission".$ stty -a | grep eof intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; $ man stty |grep -A1 eof |head -n2 eof CHAR CHAR will send an end of file (terminate the input)
What this means is that the character specified after
eof
(by default^D
, aka EOT) is configured to be intercepted (by the tty driver) and, instead of that character being sent to the process reading standard input, the kernel will "send an end of file (terminate the input)".::: spoiler (*)
One could also say there is an EOF character, but what it is can be configured on a per-tty basis.By default the EOF character is EOT, a control character, but it could be set to any character. For instance: run
stty eof x
and now, in that terminal, "x" (by itself, without the control key) will be the EOF character and will behave exactly as^D
did before.
:::But "send an end of file" does not mean sending any character to the reading process: as the blog post explains, it actually (counterintuitively) means flushing the buffer - meaning, causing the
read
syscall to return with whatever is in the buffer currently.It is confusing that this functionality is called
eof
, and thestty
man page description of it is even more so, given that it (really!) does actually flush the contents of the buffer toread
- even if the line buffer is not empty, in which case it is not actually indicating end-of-file!You can confirm this is happening by running
cat
and typing a few characters and then hitting^D
. (cat
will echo those characters, even though you have not hit enter yet.)Or, you can pipe
cat
intopv
and see that^D
also causespv
to receive the buffer contents prior to hitting enter.I guess unix calls this
eof
because this function is most often used to flush an empty buffer, which is how you "send an end of file" to the reader.Anyway, the empty-
read
-means-EOF semantics are documented, among other places, in the man page for theread()
syscall (man read
On success, the number of bytes read is returned (zero indicates end of file)
If you want
^D
to send an actual EOT character through to the reading process, you can escape it using the confusingly-namedlnext
function, which by default triggered by the^V
control character (aka SYN, "synchronous idle", ASCII character 22 - note V is the 22nd letter of the alphabet):$ man stty|grep lnext -A1 * lnext CHAR CHAR will enter the next character quoted $ stty -a|grep lnext werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0;
Try it: you can type
echo "
and then^V
and^D
and then"|xxd
(and then enter) and you will see that this is sending ascii character 4.You can also send it with
echo -e '\x04'
. Note that the EOT character does not terminate bash:$ echo -e '\x04\necho see?'|xxd 00000000: 040a 6563 686f 2073 6565 3f0a ..echo see?. $ echo -e '\x04\necho see?'|bash bash: line 1: $'\004': command not found see?
As you can see, it instead interprets it as a command.
::: spoiler (Control characters are perfectly cromulent filenames btw...)
$ echo -e '#!/bin/bash\necho lmao' > ~/.local/bin/$(echo -en '\x04') $ chmod +x ~/.local/bin/$(echo -en '\x04') $ echo -e '\x04\necho see?'|bash lmao see?
:::
Anyway, hopefully this all convinces you that EOF is not a character
-
When running cat this way, you are in "cooked mode". A ctrl-d does nothing on a non-empty line.
The shell usually runs in non-cokked, or raw, mode as well as nonblocking mode. Where it sees (nearly) every key you press as you press them. Which is why it " sees" the ctrl-d even when you are not on an empty line.
You can learn more here:
- https://jvns.ca/blog/2024/11/26/terminal-rules/#rule-3-repls-should-quit-when-you-press-ctrl-d-on-an-empty-line. (And more generally, from her various blog posts on the matter)
- https://www.gnu.org/software/mit-scheme/documentation/stable/mit-scheme-ref/Terminal-Mode.html
A ctrl-d does nothing on a non-empty line.
ctrl-d actually is flushing the buffer regardless of if the line is empty or not.
See my other comment for how you can observe it.
-
Note: for novices reading along at home, the notation
^X
means hold down the ctrl key and type x (without shift).ctrl-a though ctrl-z will send ASCII characters 1 through 26, which are called control characters (because they're for controling things, and also because you can type them by holding down the control key).
^D is the EOF character.
Nope, Chuck Testa: there is no EOF character. ^*^
"D" being the fourth letter of the alphabet, sends ASCII character 4, which (as you can see in
man ascii
) is called EOT or "end of transmission".$ stty -a | grep eof intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; $ man stty |grep -A1 eof |head -n2 eof CHAR CHAR will send an end of file (terminate the input)
What this means is that the character specified after
eof
(by default^D
, aka EOT) is configured to be intercepted (by the tty driver) and, instead of that character being sent to the process reading standard input, the kernel will "send an end of file (terminate the input)".::: spoiler (*)
One could also say there is an EOF character, but what it is can be configured on a per-tty basis.By default the EOF character is EOT, a control character, but it could be set to any character. For instance: run
stty eof x
and now, in that terminal, "x" (by itself, without the control key) will be the EOF character and will behave exactly as^D
did before.
:::But "send an end of file" does not mean sending any character to the reading process: as the blog post explains, it actually (counterintuitively) means flushing the buffer - meaning, causing the
read
syscall to return with whatever is in the buffer currently.It is confusing that this functionality is called
eof
, and thestty
man page description of it is even more so, given that it (really!) does actually flush the contents of the buffer toread
- even if the line buffer is not empty, in which case it is not actually indicating end-of-file!You can confirm this is happening by running
cat
and typing a few characters and then hitting^D
. (cat
will echo those characters, even though you have not hit enter yet.)Or, you can pipe
cat
intopv
and see that^D
also causespv
to receive the buffer contents prior to hitting enter.I guess unix calls this
eof
because this function is most often used to flush an empty buffer, which is how you "send an end of file" to the reader.Anyway, the empty-
read
-means-EOF semantics are documented, among other places, in the man page for theread()
syscall (man read
On success, the number of bytes read is returned (zero indicates end of file)
If you want
^D
to send an actual EOT character through to the reading process, you can escape it using the confusingly-namedlnext
function, which by default triggered by the^V
control character (aka SYN, "synchronous idle", ASCII character 22 - note V is the 22nd letter of the alphabet):$ man stty|grep lnext -A1 * lnext CHAR CHAR will enter the next character quoted $ stty -a|grep lnext werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0;
Try it: you can type
echo "
and then^V
and^D
and then"|xxd
(and then enter) and you will see that this is sending ascii character 4.You can also send it with
echo -e '\x04'
. Note that the EOT character does not terminate bash:$ echo -e '\x04\necho see?'|xxd 00000000: 040a 6563 686f 2073 6565 3f0a ..echo see?. $ echo -e '\x04\necho see?'|bash bash: line 1: $'\004': command not found see?
As you can see, it instead interprets it as a command.
::: spoiler (Control characters are perfectly cromulent filenames btw...)
$ echo -e '#!/bin/bash\necho lmao' > ~/.local/bin/$(echo -en '\x04') $ chmod +x ~/.local/bin/$(echo -en '\x04') $ echo -e '\x04\necho see?'|bash lmao see?
:::
Anyway, hopefully this all convinces you that EOF is not a character
Which is why I havenât wrote âEOF characterâ, âEOTâ or âEOT characterâ. Neither have I claimed that
\x4
character is interpreted by the shell as end of file. -
When running cat this way, you are in "cooked mode". A ctrl-d does nothing on a non-empty line.
The shell usually runs in non-cokked, or raw, mode as well as nonblocking mode. Where it sees (nearly) every key you press as you press them. Which is why it " sees" the ctrl-d even when you are not on an empty line.
You can learn more here:
- https://jvns.ca/blog/2024/11/26/terminal-rules/#rule-3-repls-should-quit-when-you-press-ctrl-d-on-an-empty-line. (And more generally, from her various blog posts on the matter)
- https://www.gnu.org/software/mit-scheme/documentation/stable/mit-scheme-ref/Terminal-Mode.html
Interesting, I have not heard of these terms before. Thanks for sharing!
I think this adds the bit of nuance that was bugging me: using something like ncurses or vim, presumably when you press a key like ctrl-z or ctrl-d it actually sends the character to the app. It would feel a bit silly if the terminal intercepted the ctrl-d, flushed some buffer, and the program had to reverse engineer whether you pressed ctrl-d or enter or something.
For raw mode, I assume the app asks the tty to please forward some characters to the app. Otherwise, in the default cooked mode, the tty intercepts those control characters to call certain functions. I suppose some REPLs may choose to emulate a cooked mode on top of raw mode, and so they have to handle the \x04 in the same way a tty would to keep it functioning like the user expects. I believe
readline
does something like this, which is why you had to usebash --noediting
for ctrl-d to run the command. Good food for thoughtI also have to say, naming it "cooked mode" is extremely funny as gen z. I love that
-
For some reason my mobile client didn't make the article link immediately obvious. That's actually really interesting. Apparently I was under the same common misconception. So the shell in this case is choosing to continue after detecting the flush.
Ohh I gotcha. Honestly no sweat, its kind of just a bit of fun trivia really
-