我使用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)
时提到的邮件列表上的内容,但这似乎不是一个好主意。
答案 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()
调用了malloc
和free
。如果在某些异步信号不安全函数(如malloc
或free
正在修改全局状态时接收到信号,则如果我们跳出信号处理程序,则可能会导致某些损坏。但是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();
}
在其他地方没有采用其他额外代码来实现这一点 - 没有全局变量,没有设置跳转。