65

我想在我的程序中捕获Ctrl+D信号并为它编写一个信号处理程序。我怎样才能做到这一点?我正在使用C 语言并使用Linux系统。

4

7 回答 7

90

正如其他人已经说过的那样,要处理Control+ D,请处理“文件结尾”。

Control+D是用户与您看到的伪文件之间的一条通信,您将其视为标准输入。这并不意味着具体的“文件结尾”,而是更一般地说“刷新我到目前为止输入的输入”。刷新意味着read()程序中对 stdin 的任何调用都返回自上次刷新以来键入的输入的长度。如果该行不为空,则输入对您的程序可用,尽管用户尚未键入“return”。如果该行为空,则read()返回零,这被解释为“文件结束”。

因此,当使用Control+D结束程序时,它仅适用于行首,或者如果您执行两次(第一次刷新,第二次read()返回零)。

试试看:

$ cat
foo
   (type Control-D once)
foofoo (read has returned "foo")
   (type Control-D again)
$
于 2009-10-04T11:16:28.467 回答
37

Ctrl+D不是信号,它是 EOF(文件结束)。它关闭标准输入管道。如果 read(STDIN) 返回 0,则表示标准输入已关闭,这意味着Ctrl+D被击中(假设管道的另一端有键盘)。

于 2009-10-04T10:58:35.970 回答
16

一个简约的例子:

#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 键之前输入的行中的字符。

于 2009-10-04T13:33:38.887 回答
5

无需处理信号。

您需要确保未在终端标志上设置 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

注意。3是控制C,4是控制D;26是对照z。113是'q'。完整表格请参见:http ://en.wikipedia.org/wiki/ASCII#ASCII_control_characters。

于 2014-12-19T03:02:21.110 回答
4

据我所知Ctrl+D被系统翻译为标准输入的结尾,因此您的应用程序不会收到任何信号。

我认为拦截Ctrl+的唯一方法D是直接使用系统api(如访问tty)

于 2009-10-04T10:59:37.027 回答
1

Ctrl+ Dascci 表中的值为 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);
}
于 2021-01-31T23:10:44.050 回答
0

您可以使用 poll() 并在 fd #1 上观察 POLLHUP,因为 TTY 层将 ^D 转换为 EOF。

于 2009-10-04T22:20:51.633 回答