Home > Net >  How does bash handle control characters?
How does bash handle control characters?

Time:07-10

I'm writing a shell that tries to simulate bash's behaviour. The problem is that I've read that when Bash gets the user input, it uses a non-canonical mode and turn off the ECHO kind of like this :

(*conf)->newterm.c_lflag &= ~(ICANON | ECHO);

But if it turns ECHO off, how is SIGINT still displayed as ^C on the temrinal ? When I try this, I get the character which is -1 (if you try to print it).

If we pay enough attention to bash behaviour, control characters are never displayed except for ctrl-c AKA SIGINT. My only theory is that bash hard coded ^C and just print it to the screen when SIGINT is detected. Am I right saying this ? Of not, how does BASH or ZSH display ^C having ECHO and ICANON turned off ? I've looked everywhere and I really can't seem to find an answer to this...

Thank you for your time and your explanation !

CodePudding user response:

I believe the ^C is actually being generated by the terminal device interface (tty), as opposed to Bash or Z Shell. For example, and following stty -echoctl and stty -echo, ^C is no longer displayed. As another example, a simple C program with sleep(1)s in an infinite loop and SIGINT set to SIG_IGN (and with stty echo), still displays ^C.

This example works for me, the most relevant lines being 17, 19, 20, 36, and 37 (gcc -Wall compiled, in macOS and Linux, in Bash and Z Shell, with Terminal [Apple], Terminator, and GNOME Terminal, within and without GNU Screen and/or tmux, and with TERMs of xterm-256color and screen-256color):

     1  #include <errno.h>
     2  #include <signal.h>
     3  #include <stdio.h>
     4  #include <termios.h>
     5  #include <unistd.h>
     6  
     7  void sh( int _sig )
     8  {
     9      puts("SIGINT");
    10  }
    11  
    12  int main()
    13  {
    14      struct termios t;
    15      void (*s)(int);
    16  
    17      if ( tcgetattr(fileno(stdin),&t) )
    18          return(1);
    19      t.c_lflag &= ~ECHO;
    20      if ( tcsetattr(fileno(stdin),TCSANOW,&t) )
    21          return(2);
    22      if ( ( s = signal(SIGINT,&sh) ) == SIG_ERR )
    23          return(3);
    24  
    25      /**
    26       ** While  the  following  `sleep`  is  running,  [ctrl] c  will
    27       ** trigger a  SIGINT, which will  lead to a  `sh(SIGINT)` call,
    28       ** which  will  lead to  "SIGINT\n"  being  written to  STDOUT,
    29       ** and  `sleep` will  be interrupted,  returning sleep  seconds
    30       ** remaining. "^C" should not be printed to the terminal.
    31       **/
    32      puts("Tap [ctrl] c within 60 seconds and expect \"SIGINT\"...");
    33      if ( sleep(60) == 0 || errno != EINTR )
    34          return(4);
    35  
    36      t.c_lflag |= ECHO;
    37      if ( tcsetattr(fileno(stdin),TCSANOW,&t) )
    38          return(5);
    39      if ( signal(SIGINT,s) == SIG_ERR )
    40          return(6);
    41  
    42      /**
    43       ** With the default SIGINT handler restored, the following will
    44       ** only return  if the time  fully elapses. And, with  the ECHO
    45       ** bit restored, "^C" should be printed to the terminal (unless
    46       ** such echo'ing was otherwise disabled).
    47       **/
    48      puts("Tap [ctrl] c within 60 seconds, expect \"^C\", and expect a 130 return (128 SIGINT)...");
    49      sleep(86400);
    50  
    51      return(7);
    52  }

P.S. Keep in mind that [ctrl]-c (generally) triggers a SIGINT, that SIGINT can be generated without a [ctrl]-c, and that, for example, kill -INT "$pid", should not trigger a tty-driven ^C output.

CodePudding user response:

If Bash does use some non standard input mode, it's probably just to support line editing, and not part of its core functionality as a shell. I think a traditional unix shell would just read lines of input, exectute them, and then prompt. An interactive shell would capture sigint so that the shell wouldn't log you out when you hit ^C, but that's about it. Quite a few things that you might imagine are handled in the shell, are actually not, they're in the kernel, in terminal drivers, input drivers and so forth.

Why are you trying to emulate bash, is this for fun and learning? If you want to do that, you should go start by looking at the source code of older and simpler shells. You could certainly go look at the old bsd "csh" code, or maybe the original bourne shell code. These shells do the essence of what it is to be a shell. There's been even simpler shells if you look for them. Later shells complicate things by adding line editing, and if you want to understand those, you can get the gnu readline library, and add that to your own app. You could add it to your shell for that matter.

  • Related