Readline:获取SIGINT的新提示

我使用readline获得了类似于以下的代码:

#include  #include  #include  #include  #include  #include  #include  #include  #include  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)提到的,但这看起来并不是一个好主意。

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

作为使用代码作为基础的快速示例:

 #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  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。

调用rl_clear_signals()

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

有关如何管理readline()信号处理的更多详细信息,请参见此处 。

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

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

实际上,这并不完全正确,因为在rl_set_prompt()中有一些对malloc()free() readline()调用,而rl_set_signals()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代码之外活动。

创建一个跳跃似乎很容易出错并且容易出错。 我添加此支持的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(); } 

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