Readline:获取SIGINT的新提示

时间:2013-05-30 05:10:56

标签: c signals readline sigint

我使用readline:

获得了类似于以下内容的代码
#include <errno.h>
#include <error.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <readline/readline.h>
#include <readline/history.h>

void handle_signals(int signo) {
  if (signo == SIGINT) {
    printf("You pressed Ctrl+C\n");
  }
}

int main (int argc, char **argv)
{
   //printf("path is: %s\n", path_string);
  char * input;
  char * shell_prompt = "i-shell> ";
  if (signal(SIGINT, handle_signals) == SIG_ERR) {
    printf("failed to register interrupts with kernel\n");
  }

  //set up custom completer and associated data strucutres
  setup_readline();

  while (1) 
  {
    input = readline(shell_prompt);
    if (!input)
      break;
    add_history(input);

    //do something with the code
    execute_command(input);

  }  
  return 0;
}

我已将其设置为拦截SIGINT(即用户按Ctrl+C),因此我可以告诉信号处理程序handle_signals()正在运行。但是,当控件返回readline()时,它使用输入之前使用的相同文本行。我想要发生的是readline“取消”当前文本行并给我一个新行,就像BASH shell一样。像这样:

i-shell> bad_command^C
i-shell> _

有机会让它发挥作用吗?我在使用longjmp(2)时提到的邮件列表上的内容,但这似乎不是一个好主意。

4 个答案:

答案 0 :(得分:7)

你认为使用longjmp是正确的。但是因为longjmp将在信号处理程序中,所以你需要使用sigsetjmp / siglongjmp。

作为使用代码作为基础的简单示例:

#include <setjmp.h>
#include <errno.h>
#include <error.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <readline/readline.h>
#include <readline/history.h>

sigjmp_buf ctrlc_buf;

void handle_signals(int signo) {
  if (signo == SIGINT) {
    printf("You pressed Ctrl+C\n");
    siglongjmp(ctrlc_buf, 1);
  }
}

int my_cmd_loop(int argc, char **argv)
{
   //printf("path is: %s\n", path_string);
  char * input;
  char * shell_prompt = "i-shell> ";
  if (signal(SIGINT, handle_signals) == SIG_ERR) {
    printf("failed to register interrupts with kernel\n");
  }

  //set up custom completer and associated data strucutres
  setup_readline();

  while (1) 
  {
    while ( sigsetjmp( ctrlc_buf, 1 ) != 0 );

    input = readline(shell_prompt);
    if (!input)
      break;
    add_history(input);

    //do something with the code
    execute_command(input);

  }  
  return 0;
}

siglongjmp将一个非0的值(在本例中为1)返回到sigsetjmp,因此while循环再次调用sigsetjmp(sigsetjmp的成功返回值为0),然后再次调用readline。

设置rl_catch_signals = 1然后调用rl_set_signals()也可能会有帮助,以便readline信号处理在将信号传递给程序之前清除所需的任何变量,然后跳转到程序第二次打电话给readline。

答案 1 :(得分:4)

致电rl_clear_signals()

这将禁用已安装的信号处理程序libreadline。处理SIGINT的人负责观察到恢复提示的行为。

More details on how to manage readline()s signal handling can be read here

答案 2 :(得分:4)

我最初被jancheta的答案搞糊涂了,直到我发现siglongjmp的目的是在进行跳转之前解锁信号掩码中的接收信号。信号在信号处理程序的入口处被阻塞,因此处理程序不会自行中断。当我们恢复正常执行时,我们不想阻止信号被阻止,这就是我们使用siglongjmp代替longjmp的原因。 AIUI,这只是简写,我们也可以调用sigprocmask后跟longjmp,这似乎是glibc在siglongjmp中所做的。

我认为跳转可能不安全因为readline()调用了mallocfree。如果在某些异步信号不安全函数(如mallocfree正在修改全局状态时接收到信号,则如果我们跳出信号处理程序,则可能会导致某些损坏。但是Readline安装了自己的信号处理程序,对此非常谨慎。他们只是设置了一面旗帜并退出;当Readline库再次获得控制权时(通常在中断的&#39; read()&#39;调用之后),它会调用RL_CHECK_SIGNALS(),然后使用kill()将任何待处理信号转发给客户端应用程序。因此,使用siglongjmp()退出信号处理程序来中断调用readline()的信号是安全的 - 在异步信号不安全函数期间保证不会收到信号。

实际上,这并不完全正确,因为在malloc()内有free()rl_set_prompt()的几个来电,readline()rl_set_signals()之前调用}}。我想知道这个呼叫顺序是否应该改变。无论如何,竞争条件的可能性非常小。

我查看了Bash源代码,似乎跳出了它的SIGINT处理程序。

您可以使用的另一个Readline接口是回调接口。那些需要同时监听多个文件描述符的应用程序(如Python或R)使用它,例如,在命令行界面处于活动状态时判断是否正在调整绘图窗口的大小。他们将在select()循环中执行此操作。

这是来自Chet Ramey的一条消息,它提供了一些关于在回调接口中接收到SIGINT后如何获得类似Bash行为的想法:

https://lists.gnu.org/archive/html/bug-readline/2016-04/msg00071.html

这些消息表明你做了这样的事情:

    rl_free_line_state ();
    rl_cleanup_after_signal ();
    RL_UNSETSTATE(RL_STATE_ISEARCH|RL_STATE_NSEARCH|RL_STATE_VIMOTION|RL_STATE_NUMERICARG|RL_STATE_MULTIKEY);
    rl_line_buffer[rl_point = rl_end = rl_mark = 0] = 0;
    printf("\n");

收到SIGINT后,您可以设置一个标记,然后检查select()循环中的标记 - 因为select()调用将被errno==EINTR的信号中断。如果您发现已设置标志,请执行以上代码。

我的观点是Readline应该在自己的SIGINT处理代码中运行类似上面的片段。目前它或多或少只执行前两行,这就是为什么像增量搜索和键盘宏这样的东西被^ C取消,但该行没有被清除。

另一张海报说&#34;打电话给rl_clear_signals()&#34;,这仍然让我困惑。我还没有尝试过但是我不知道如何实现它(1)Readline的信号处理程序无论如何都会向你转发信号,并且(2)readline()安装进入时的信号处理程序(并在退出时清除它们),因此它们通常不会在Readline代码之外处于活动状态。

答案 3 :(得分:2)

创建跳转对我来说似乎很容易出错并容易出错。我正在添加此支持的shell实现不允许进行此更改。

幸运的是,readline有一个更清晰的替代解决方案。我的SIGINT处理程序如下所示:

static void
int_handler(int status) {
    printf("\n"); // Move to a new line
    rl_on_new_line(); // Regenerate the prompt on a newline
    rl_replace_line("", 0); // Clear the previous text
    rl_redisplay();
}

在其他地方没有采用其他额外代码来实现这一点 - 没有全局变量,没有设置跳转。