web-dev-qa-db-fra.com

Comment capturer le signal Control + D?

Je veux capturer le Ctrl+D signaler dans mon programme et écrire un gestionnaire de signal pour cela. Comment puis je faire ça? Je travaille sur C et utilise un système Linux.

51
Ryan

Comme d'autres l'ont déjà dit, pour gérer Control+D, gérer "fin de fichier" s.

Control+D est un élément de communication entre l'utilisateur et le pseudo-fichier que vous voyez en tant que stdin. Cela ne signifie pas spécifiquement "fin de fichier", mais plus généralement "vider l'entrée que j'ai tapée jusqu'à présent". Le vidage signifie que tout appel read() sur stdin dans votre programme est renvoyé avec la longueur de l'entrée saisie depuis le dernier vidage. Si la ligne n'est pas vide, l'entrée devient disponible pour votre programme bien que l'utilisateur n'ait pas encore tapé "return". Si la ligne est vide, alors read() renvoie zéro et est interprété comme "fin de fichier".

Donc en utilisant Control+D pour terminer un programme, cela ne fonctionne qu'au début d'une ligne, ou si vous le faites deux fois (première fois pour vider, deuxième fois pour read() pour retourner zéro).

Essayez le:

$ cat
foo
   (type Control-D once)
foofoo (read has returned "foo")
   (type Control-D again)
$
75
Pascal Cuoq

Ctrl+D n'est pas un signal, c'est EOF (End-Of-File). Il ferme le tuyau stdin. Si read (STDIN) renvoie 0, cela signifie que stdin est fermé, ce qui signifie Ctrl+D a été touché (en supposant qu'il y ait un clavier à l'autre bout du tuyau).

24
Etienne Dechamps

Un exemple minimaliste:

#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);
}

Le programme change le caractère VEOF (de Ctrl-D) en Ctrl-C et le caractère VINTR (de Ctrl-C) en Ctrl-D. Si vous appuyez sur Ctrl-D, le pilote du terminal enverra un SIGINT au gestionnaire de signaux du programme.

Remarque: une pression sur VINTR effacera la mémoire tampon d’entrée du terminal afin que vous ne puissiez pas lire les caractères saisis dans la ligne précédant la touche VINTR.

12
sambowry

Pour autant que je sache Ctrl+D est traduit par le système jusqu'à la fin de l'entrée standard afin que votre application ne reçoive aucun signal.

Je pense que le seul moyen d'intercepter Ctrl+D est de travailler directement avec l'api du système (comme accéder à tty)

3
Piotr Czapla

Il n'y a pas besoin de traiter les signaux. 

Vous devez vous assurer que ISIG n'est pas défini sur les indicateurs de terminal, c'est tout. 

Voici un exemple complet complet utilisant select pour éviter le blocage sur 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;
}

Sortie:

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 est le contrôle C et 4 est le contrôle D; 26 est le contrôle z. 113 est 'q' . Voir: http://fr.wikipedia.org/wiki/ASCII#ASCII_control_characters pour un tableau complet.

2
Doug

Vous pouvez utiliser poll () et surveiller POLLHUP sur le disque n ° 1, car la couche TTY traduit ^ D en EOF.

0
user175104