问题 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),但这似乎不是一个好主意。


7267
2018-05-30 05:10


起源



答案:


你在思考使用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 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 ( sigsetjmp( ctrlc_buf, 1 ) != 0 );

  while (1) 
  {
    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线。


5
2018-06-11 01:02



你不能安全地打电话 printf 来自信号处理程序。 - pat


答案:


你在思考使用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 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 ( sigsetjmp( ctrlc_buf, 1 ) != 0 );

  while (1) 
  {
    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线。


5
2018-06-11 01:02



你不能安全地打电话 printf 来自信号处理程序。 - pat


呼叫 rl_clear_signals()

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

有关如何管理的更多详细信息 readline()信号处理可以在这里阅读


4
2018-05-30 16:05





我最初被jancheta的回答搞糊涂了,直到我发现了它的目的 siglongjmp 是在跳转之前解锁信号掩码中的接收信号。信号在信号处理程序的入口处被阻塞,以便处理程序不会中断自身。当我们恢复正常执行时,我们不希望阻止信号,这就是我们使用的原因 siglongjmp 代替 longjmp。 AIUI,这只是简写,我们也可以打电话 sigprocmask 其次是 longjmp,这似乎是glibc正在做的事情 siglongjmp

我认为跳跃可能不安全因为 readline() 电话 malloc 和 free。如果接收到信号,而某些async-signal-unsafe函数就像 malloc 要么 free 正在修改全局状态,如果我们要跳出信号处理程序,可能会导致一些损坏。但Readline安装了自己的信号处理程序,这是谨慎的。他们只是设置了一面旗帜并退出;当Readline库再次获得控制权时(通常在中断的'read()'调用之后),它会调用 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取消,但是没有清除该行。

另一张海报上写着“调用rl_clear_signals()”,这仍然让我感到困惑。我没有尝试过,但是我没有看到它会如何完成任何事情(1)Readline的信号处理程序无论如何都会向你转发信号,(2) readline() 在进入时安装信号处理程序(并在退出时清除它们),因此它们通常不会在Readline代码之外处于活动状态。


4
2018-04-30 21:50





创建一个跳跃似乎很容易出错并且容易出错。我添加此支持的shell实现不允许此更改。

幸运的是, readline有一个更清晰的替代解决方案。我的 SIGINT handler看起来像这样:

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

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


2
2018-01-29 05:17