如何在设计 shell 时处理 Control-C 信号?

How to handle Control-C signal while designing a shell?

我正在尝试实现 shell 中存在的一些功能,包括仅在用户输入 quit 而不是 Ctrl+C 时退出。下面是我尝试过的代码的简化版本。

代码 1:没有在信号处理程序中调用 loop()。

void loop(){
    while(1){
      char a[20];
      printf("Enter Command : " );
      scanf("%s",a);
      printf("%s\n", a);
    }
}

void sigintHandler(int sig_num)
{
    signal(SIGINT, sigintHandler);
    printf("\n");
}

int main(int argc, char const *argv[]) {

  signal(SIGINT, sigintHandler);
  loop();
  return 0;
}

代码1的输出:

从第三个输入可以看出,我换了一行并从我离开循环的地方继续。我想重新开始循环。因此,我通过在信号处理程序本身中调用循环进行了以下修改。

void loop(){
    while(1){
      char a[20];
      printf("Enter Command : " );
      scanf("%s",a);
      printf("%s\n", a);
    }
}

void sigintHandler(int sig_num)
{
    signal(SIGINT, sigintHandler);
    printf("\n");
    loop();
}

int main(int argc, char const *argv[]) {

  signal(SIGINT, sigintHandler);
  loop();
  return 0;
}

代码 2 的输出:

可以看出,当我第一次点击Ctrl+C时(在输入行3),它工作正常,我可以继续。但是当我第二次单击 Ctrl+C 时,我没有换行,我必须按 enter 键才能执行程序。

我回答了 this 个问题,但它似乎不适用于我的情况。这是我第一次使用信号和系统调用,所以我的问题可能很愚蠢。但是,如果有人可以帮助我找到正确的信号实现方式,那将是一个很大的帮助。谢谢。

Jonathan Leffler 提供了有用的提示(尽管还不够,至少在一些流行的操作系统上):

  • 看看什么scanf()returns.
  • 不要在信号处理程序中使用loop()
  • 不要使用 signal() — 使用 sigaction()。在您的情况下,这有两个优点:
    • 可以避免在调用信号处理程序时将信号操作恢复为默认状态,因此您不必在处理程序中再次更改操作。
    • 可以避免重新启动read()系统调用(在scanf()内),这样scanf()returns和你可以第一时间响应中断。

所以,信号处理程序可以只是

void sigintHandler(int sig_num)
{
    printf("\n");   // or perhaps better write(1, "\n", 1)
}

main()中的signal(SIGINT, sigintHandler);可以替换为

  sigaction(SIGINT, &(struct sigaction){ .sa_handler = sigintHandler }, NULL);

scanf("%s",a);

      if (scanf("%s", a) == EOF)
      {
          if (errno == EINTR) continue; // read operation interrupted by signal
          return;
      }

EOF 在中断和文件结束时返回,因此要通过 errno 区分情况。此外,如果您的程序提供退出它的方法,那就太好了,因此 return.