Как захватить сигнал Control + D? - PullRequest
51 голосов
/ 04 октября 2009

Я хочу перехватить сигнал Ctrl + D в моей программе и написать для него обработчик сигнала. Как я могу это сделать? Я работаю на C и использую Linux систему.

Ответы [ 6 ]

75 голосов
/ 04 октября 2009

Как уже говорили другие, для обработки Control + D , обработки "конца файла" s.

Control + D - это часть связи между пользователем и псевдофайлом, которую вы видите как stdin. Это не означает конкретно «конец файла», но в более общем смысле «очистить введенный мною ввод». Сбрасывание означает, что любой read() вызов stdin в вашей программе возвращается с длиной ввода, введенной с момента последнего сброса. Если строка не пустая, ввод становится доступным для вашей программы, хотя пользователь еще не набрал «return». Если строка пуста, read() возвращается с нулем, что интерпретируется как «конец файла».

Таким образом, при использовании Control + D для завершения программы, она работает только в начале строки или если вы делаете это дважды (первый раз, чтобы сбросить, второй раз для read() чтобы вернуть ноль).

Попробуйте:

$ cat
foo
   (type Control-D once)
foofoo (read has returned "foo")
   (type Control-D again)
$
24 голосов
/ 04 октября 2009

Ctrl + D - это не сигнал, это EOF (конец файла). Он закрывает трубу стандартного ввода. Если read (STDIN) возвращает 0, это означает, что стандартный ввод закрыт, что означает, что Ctrl + D был нажат (при условии, что на другом конце канала есть клавиатура).

13 голосов
/ 04 октября 2009

Минималистичный пример:

#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 голосов
/ 04 октября 2009

Насколько я знаю, Ctrl + D переводится системой в конец стандартного ввода, поэтому ваше приложение не получит никакого сигнала.

Я думаю, что единственный способ перехватить Ctrl + D - это работать напрямую с системным API (например, с доступом к tty)

2 голосов
/ 19 декабря 2014

Нет необходимости обрабатывать сигналы.

Вы должны убедиться, что 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 это «д». См .: http://en.wikipedia.org/wiki/ASCII#ASCII_control_characters для полной таблицы.

0 голосов
/ 05 октября 2009

Вы можете использовать poll () и следить за POLLHUP на fd # 1, потому что слой TTY переводит ^ D в EOF.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...