Shell – How to allow backspaces in unbuffered/non-canonical mode

cechoshellterminal

I'm working on multiple C programs like a shell and a text editor that require to be run without the ECHO and ICANON flags. I disabled these using termios.h and managed to write my own gets function that can relay returned strings to my program and do special things for escape characters. The only think I can't do is print a backspace. If, for example, I use this code:

void mgets(char *str)
{
    int c, i = 0;

    while((c = getchar()) != '\n')
        if(c == 27)
            // the user hit ESC, ignore it for now
        else if(c == '\b')
            puts("\b \b") // this is where it SHOULD backspace
        // else if it's a regular character:
        else {
            str+i = c; i++; // write that to the string...
            putchar(c); // ...and echo it to the screen
        }
}

It all works great but the program just doesn't respond when I backspace. if I change my if statement a bit…

if(c == '\b')
    printf("You hit a backspace!");

But it's still unresponsive. I know that puts("\b \b") works so the only conclusion is that my backspace key isn't being detected as a '\b'. What can I do? Please help? Thanks in advance!

Best Answer

Before you change the termios settings, save them. That information is the "same" as what you can see with stty -a.

For example

$ stty -a
speed 38400 baud; rows 40; columns 80; line = 0;
intr = ^C; quit = ^\; erase = ^H; kill = ^U; eof = ^D; eol = <undef>;
eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R;
werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
-parenb -parodd cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff
-iuclc -ixany -imaxbel -iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt
echoctl echoke

The value shown after erase is the character that you're referring to as "backspace". Depending on the system conventions that might be ^H (8, or ASCII backspace) or ^? (127, or ASCII DEL).

In the termios settings, e.g.,

#include <termios.h>
...
struct termios data;
tcgetattr(0, &data);

then

data.c_cc[VERASE]

is the same erase character (with some caveats when the data has never been set: stty would show "undef").

Unlike some of the other settings, the meaning of the erase character is still relevant in raw/cbreak modes, since it is a setting that tells the terminal driver what your terminal is expected to send. It doesn't hurt to test for both possibilities, in case your stty settings were incorrect...

Further reading:

Related Question