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 TERM
s 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.