我想在程序中捕获 Ctrl + D 信号并为其编写信号处理程序。 我怎样才能做到这一点? 我正在使用 C 并使用 Linux 系统。
答案 0 :(得分:75)
正如其他人已经说过的那样,要处理 Control + D ,请处理“文件结束”。
Control + D 是用户和您看作stdin的伪文件之间的一段通信。它并不意味着具体的“文件结束”,但更一般地说是“刷新我输入的输入”。刷新意味着程序中对stdin的任何read()
调用都会返回自上次刷新以来输入的输入长度。如果该行是非空的,则输入可用于您的程序,尽管用户尚未输入“return”。如果该行为空,则read()
返回零,并将其解释为“文件结束”。
因此,当使用 Control + D 结束程序时,它只能在一行的开头工作,或者如果你这样做两次(第一次刷新, read()
第二次返回零。
试一试:
$ cat
foo
(type Control-D once)
foofoo (read has returned "foo")
(type Control-D again)
$
答案 1 :(得分:24)
Ctrl + D 不是信号,它是EOF(文件结束)。它关闭了stdin管道。如果read(STDIN)返回0,则表示stdin已关闭,这意味着 Ctrl + D 被击中(假设管道的另一端有一个键盘)。 / p>
答案 2 :(得分:13)
一个简约的例子:
#include <unistd.h>
#include <stdio.h>
#include <termios.h>
#include <signal.h>
void sig_hnd(int sig){ (void)sig; printf("(VINTR)"); }
int main(){
setvbuf(stdout,NULL,_IONBF,0);
struct termios old_termios, new_termios;
tcgetattr(0,&old_termios);
signal( SIGINT, sig_hnd );
new_termios = old_termios;
new_termios.c_cc[VEOF] = 3; // ^C
new_termios.c_cc[VINTR] = 4; // ^D
tcsetattr(0,TCSANOW,&new_termios);
char line[256]; int len;
do{
len=read(0,line,256); line[len]='\0';
if( len <0 ) printf("(len: %i)",len);
if( len==0 ) printf("(VEOF)");
if( len >0 ){
if( line[len-1] == 10 ) printf("(line:'%.*s')\n",len-1,line);
if( line[len-1] != 10 ) printf("(partial line:'%s')",line);
}
}while( line[0] != 'q' );
tcsetattr(0,TCSANOW,&old_termios);
}
程序将VEOF字符(从Ctrl-D)更改为Ctrl-C,将VINTR字符(从Ctrl-C)更改为Ctrl-D。如果按Ctrl-D,则终端驱动程序会将SIGINT发送到程序的信号处理程序。
注意:按下VINTR将删除终端输入缓冲区,因此在按下VINTR键之前无法读取行中输入的字符。
答案 3 :(得分:3)
据我所知 Ctrl + D 由系统翻译为标准输入的结尾,因此您的应用程序将不会获得任何信号。
我认为拦截 Ctrl + D 的唯一方法是直接使用系统api(比如访问tty)
答案 4 :(得分:2)
无需处理信号。
您需要确保未在终端标志上设置ISIG,这就是全部。
这是一个完整的包含示例,使用select来避免在stdin上阻塞:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <time.h>
#include <sys/select.h>
#define STDIN_FILENO 0
struct termios org_opts;
/** Select to check if stdin has pending input */
int pending_input(void) {
struct timeval tv;
fd_set fds;
tv.tv_sec = 0;
tv.tv_usec = 0;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds); //STDIN_FILENO is 0
select(STDIN_FILENO+1, &fds, NULL, NULL, &tv);
return FD_ISSET(STDIN_FILENO, &fds);
}
/** Input terminal mode; save old, setup new */
void setup_terminal(void) {
struct termios new_opts;
tcgetattr(STDIN_FILENO, &org_opts);
memcpy(&new_opts, &org_opts, sizeof(new_opts));
new_opts.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOK | ECHONL | ECHOPRT | ECHOKE | ISIG | ICRNL);
tcsetattr(STDIN_FILENO, TCSANOW, &new_opts);
}
/** Shutdown terminal mode */
void reset_terminal(void) {
tcsetattr(STDIN_FILENO, TCSANOW, &org_opts);
}
/** Return next input or -1 if none */
int next_input(void) {
if (!pending_input())
return -1;
int rtn = fgetc(stdin);
printf("Found: %d\n", rtn);
return(rtn);
}
int main()
{
setup_terminal();
printf("Press Q to quit...\n");
for (;;) {
int key = next_input();
if (key != -1) {
if ((key == 113) || (key == 81)) {
printf("\nNormal exit\n");
break;
}
}
}
reset_terminal();
return 0;
}
输出:
doug-2:rust-sys-sterm doug$ cc junk.c
doug-2:rust-sys-sterm doug$ ./a.out
Press Q to quit...
Found: 4
Found: 3
Found: 27
Found: 26
Found: 113
Normal exit
NB。 3是对照C,4是对照D; 26是控制z。 113是&#39; q&#39;。 有关完整的表格,请参阅:http://en.wikipedia.org/wiki/ASCII#ASCII_control_characters。
答案 5 :(得分:0)
您可以使用poll()并在fd#1上观察POLLHUP,因为TTY图层会将^ D转换为EOF。
答案 6 :(得分:0)
Ctrl + D 在 ascci 表中的值为 4,是一个不可打印的字符。
因此您可以使用以下代码在终端中捕获它。
当 getline 函数 get Ctrl + D 时发生错误,返回值为 -1。
您可以对返回值设置条件。
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char *buf = malloc(sizeof(char) * 500);
size_t size = 500;
int nb = getline(&buf, &size, stdin);
if (nb == -1)
printf("CTRL + D captured\n");
free(buf);
return (0);
}