đŸ’Ÿ Archived View for tilde.team â€ș ~simonb â€ș termios.gmi captured on 2022-03-01 at 15:24:45. Gemini links have been rewritten to link to archived content

View Raw

More Information

-=-=-=-=-=-=-

Things I learned from termios(4)

You can change the special character associated with each action! ^C and ^D and so forth are just the defaults.

$ stty eof  '^E'
$ stty intr '^K'
$ cat
this is some text
this is some text
^E
$ cat
this is some more text^K
$

Or from C:

struct termios ts;
tcgetattr(STDIN_FILENO, &ts);
ts.c_cc[VEOF]  = 5;
ts.c_cc[VINTR] = 11;
tcsetattr(STDIN_FILENO, TCSAFLUSH, &ts);

A useful feature: REPRINT (^R) reprints the current line, leaving the previous contents onscreen as a reference. For example, say you mistype the start of a line:

$ cat >/tmp/a
Tihs is some text

No problem: type ^R and ^U to start over, leaving the old contents visible as a reference.

$ cat >/tmp/a
Tihs is some text^R^U
this is some text
^D
$ cat /tmp/a
this is some text
$

EOF (^D) doesn’t do what I thought it did, something about “closing standard input” or “signalling EOF”. Really it just flushes any active read(2) calls. Let me show you what I mean.

During normal operation the kernel handles line editing automatically, interpreting keys like DEL, ^W, and ^C without any code in userspace. When you press RET it adds a newline to the end and sends it off to anyone reading from your terminal.

If you press ^D instead of RET, the kernel still passes the line on! But this time, no trailing newline is added to terminate it. So ^D lets you send incomplete lines over to a program, if you wish to do so:

$ cat
This is some text^DThis is some text

The first ^D passes “This is some text” to cat, which echoes that phrase back to the terminal.

Here's the important bit: pressing ^D at the start of the line gives cat a “line” of text with 0 bytes of content. This is almost universally interpreted as EOF:

If successful, the number of bytes actually read is returned. Upon reading end-of-file, zero is returned. Otherwise, a -1 is returned and the global variable errno is set to indicate the error.

You can see this in various libc implementations: musl, OpenBSD, even 7th Edition Unix.

But remember: ^D hasn’t actually closed the file! It makes read() return 0, but *only once*. cat can keep reading from the terminal, if it wants to, and ed is one program that actually does this: ^D is like ‘.’ or ‘q’, depending on the context, and ed can keep reading commands after it.

$ ed file.c   # Open file.c.
31253         # (file.c has 31253 bytes.)
a             # Append some text.
/* CC0 */
^D            # End input mode.
p             # Print out the current line.
/* CC0 */
^D            # Quit ed.
?             # (file.c has unsaved changes.)
^D            # Okay, quit without saving.

(However, notice that ed always exits after a couple of ^Ds. This is by design: ed should handle real EOF gracefully when it’s reading from an ed script. zsh does a similar thing with the IGNORE_EOF option.)

⎯⎯⎯⎯

Written by Simon Branch <simonmbranch@gmail.com> in February 2022. Licensed under CC0.