3

This is about the behaviour of the backspace (\b) character.  I have the following C program:

int main() {
    printf("Hello\b\b");
    sleep(5);
    printf("h\n");
    return 0;
}

The output on my terminal is

Helho

with the cursor advancing to the first position of the following line.

First, the entire thing prints only after the 5 second sleep, so from that I deduced the output from the kernel to the terminal is line buffered.  So now, my questions are:

  1. Since the \b\b goes back two spaces, to the position of the (second) l, then similar to how l was replaced by h, the o should have been replaced by \n. Why wasn't it?
  2. If I remove the line printf("h\n");, it prints Hello and goes back two characters, without erasing. This I got from other answers is because of a non-destructive backspace. Why is this behaviour different for input and output? That is, if I input something into the terminal (even the very same program) and press Backspace, it erases the last character, but not for the output. Why?

I'm on an Ubuntu system on the xterm terminal using bash, if that helps.

  • 1
    I don't understand in point 1 how backspace being nondestructive implies that other characters necessarily are destructive. In particular, \n isn't destructive (unless you consider scrolling at the bottom of a limited-size window to be destructive). Can you clarify? – Mark Plotnick Jan 1 '18 at 17:50
  • @MarkPlotnick I didn't mean the other chars are destructive, only that the cursor position moved back to l, so l is now replaced by h, and then the cursor moves to position o. Why is o not similarly replaced? – forumulator Jan 1 '18 at 17:56
  • Isn't really a Unix/Linux question. You will find a lot of people with knowledge of C here I am sure but probably even more over here – bu5hman Jan 1 '18 at 17:57
  • 2
    off-topic, but the o isn't replaced by a newline, because that goes to the new line without clearing the remainder of the current line. – Thomas Dickey Jan 1 '18 at 18:07
  • 1
    Attention, close-voters: This is not a programming question. It is primarily about the Unix/Linux TTY driver / line discipline. At a minor level, it is about the behavior of the Unix/Linux Standard I/O library. Both of these subjects are on-topic here. – G-Man Jan 2 '18 at 6:41
3

First, the entire thing prints only after the 5 second sleep so from that I deduced the output from the kernel to the terminal is line buffered.

No, the output from your program to the kernel is line-buffered. That's the default behaviour for stdio when stdout is a terminal. Add the call setbuf(stdout, NULL) to turn output buffering off for stdout. See setbuf(3).

  1. Since the \b\b goes back two spaces, to the position of l then similar to how l was replaced by h, the o should have been replaced by \n. Why wasn't it?

Because the newline character just moves the cursor (and scrolls the screen), it doesn't print as a visible character that would take up the place of a glyph on the terminal. If we assume it would take the place of a glyph, what would it look like?

  1. if I input something into the very same program, and press Backspace, it erases the last character, but not for the output. Why?

Well, what happens when you type depends on what mode the terminal is in. Roughly, it can be in the usual "cooked" mode where the terminal itself provides elementary line editing (handles backspaces); or in a "raw" mode, where all keypresses go to the application, and it's up to the application to decide what to do with them, and what to output in response. Cooked mode usually goes along with "local echo" where the terminal (local to the user) prints out the characters as they are typed. In raw mode, the application usually takes care of echoing the typed characters, to have full control over what's visible.

See e.g. this question for discussion on the terminal modes: What’s the difference between a “raw” and a “cooked” device driver?

If you run e.g. cat, the terminal will be in cooked mode (the default) and handle the line editing. Hitting for example xBackspaceCtrl-D will result in cat just reading the empty input, signalling the end of input . You can check this with strace. But if you run an interactive Bash shell instead, it will handle the backspace by itself, and output what it considers appropriate to do what the user expects, i.e. wipe one character.


Here's part of the output for strace -etrace=read,write -obash.trace bash, after entering the mentioned sequence xBackspaceCtrl-D:

read(0, "x", 1)                         = 1
write(2, "x", 1)                        = 1
read(0, "\177", 1)                      = 1
write(2, "\10\33[K", 4)                 = 4
read(0, "\4", 1)                        = 1

First, Bash reads and writes the x, outputting it to the terminal. Then it reads the backspace (character code 0177 in octal or 127 in decimal), and outputs the backspace character (octal 010, decimal 8(*)) which moves the cursor back and outputs the control sequence for clearing the end of the line, <ESC>[K. The last \4 is the Ctrl-D, which is used by Bash to exit the program.

(* in input, Ctrl-H would have the decimal character code 8. Backspace is either the same or 127 as here, depending again on how the terminal is set up.)

In comparison, the same experiment with cat only shows a single read of zero bytes, the "end of file" condition. End of file can mean either a connected pipe or socket being closed, an actual end of file, or Ctrl-D being received from a terminal in cooked mode:

read(0, "", 131072)                     = 0

In particular, cat doesn't see the x, nor the backspace, nor the actual code for Ctrl-D: they're are handled by the terminal. Which might be the virtual terminal driver in the kernel; an actual physical terminal over a serial connection or such; or a terminal emulator like xterm either running on the same machine or at the remote end of an SSH connection. It doesn't matter for the userspace software.

  • Are the terminal modes different for input(cooked) vs. output(raw)? Also if the "output from your program to the kernel is line-buffered", what about the input? Is that line buffered in the kernel(as demo'd on typing bash commands, for example) – forumulator Jan 2 '18 at 4:51
  • @forumulator, no, as far as I understand, there are really no distinct modes for output (unless we count different character maps, or local echo where input is also visible on screen). Cooked vs raw mode is for input. Input is line buffered on the terminal, if it is in cooked mode. But for Bash, it usually isn't: it provides its own line editing via readline. – ilkkachu Jan 2 '18 at 13:26
3

The codes (\b, \n etc) move the cursor, not the text.

I think you will find that the behaviour stems from the 'good old days' when we had to manipulate the cursor on a printer to get such stunning effects as bold fonts and strikethrough on teletype, golfball printers and the like.

\b just moves the insertion point back and allows you to overtype. On a digital screen then this deletes the previous content but on an old printer you would get the overlain character.

\n moves the cursor to a new line but leaves the text on the old line behind. This would have been a CRLF which drops the cursor back to the beginning of the line and tells the printer to scroll up a line.

The CR, LF and CRLF codes are again linked to the need to manipulate old devices to get those newfangled effects.

..... and there wasn't any way to delete characters from a printed page, You were expected to get it right before posting to print.

EDIT

Re point 2. Input and output do different things because you are telling them to.

\b is equivalent to left arrow with INSERT off, not delete.

\n is equivalent to down arrow (LF) and HOME (CR), not to the 'enter' keystroke.

3

First of all, if you want to learn about this stuff, you should read

You ask,

… if I input something …, and press Backspace, it erases the last character, but not for the output. Why?

Here’s a bit of trivia for you: In the very early days of Unix (the 1970s, before Linux even existed), backspace1 didn’t erase input characters, and users hated it.  You’d type beast, hit Backspace three times, and you’d still have an evil-eyed be|ast staring at you (| represents the cursor).  Even after you typed st (because you meant to say best all along), the screen would still say best|t, and it was confusing.

Many users adopted the habit of typing BackspaceSpaceBackspace once for each character they wanted to erase, in order to use spaces to replace / destroy the bad characters; i.e., to manually erase them.  Eventually (1979 or the early 1980s), the developers relented and made this automatic:

When you hit backspace, the kernel tty line discipline rubs out your previous character by printing (in the simple case) Ctrl-H, a space, and then another Ctrl-H.
Source: How Unix erases things when you type a backspace while entering text

where “Ctrl-H” is the control code for a backspace.

In some situations you can turn off this option and get the old behavior by typing stty -echoe (echoe = “echo erase”, and - means “don’t do it”).
____
1Actually, in the very early days, backspace wasn’t Backspace — but that’s another story.

  • Thanks. Fun fact: I read those both those articles and a lot more like them in the two days(The first link is actually what got me interested in this stuff). Finally, to really understand it, I picked up Kernighan's "The Unix programming environment". – forumulator Jan 2 '18 at 9:01
  • Also the third link is pretty helpful. – forumulator Jan 2 '18 at 9:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.