我现在拥有的代码将提示发送到stdout,然后从stdin中读取一行。任何时候接收SIGINT
都会中断执行并退出程序。我不确定应该在哪里捕获SIGINT
,并且我知道当使用当前代码接收到信号时,无法启动新提示。是否有适当的方法来完成此操作(最终目标是使它像大多数shell一样工作(SIGINT取消当前提示并开始一个新提示))?
该代码将在Linux上运行,但是平台越少越好。get_line
将一条行从stdin读入缓冲区,并生成char []并将其分配给line。split_args
占用一行并将其转换为char []数组,在空白处分割。is_command_valid
确定用户是否键入了已知的内部命令。外部程序无法执行。
static int run_interactive(char *user)
{
int done = 0;
do
{
char *line, **args;
int (*fn)(char *, char **);
fprintf(stderr, "gitorium (%s)> ", user);
get_line(&line);
if (line[0] == '\0')
{
free(line);
break;
}
split_args(&args, line);
if (!strcmp(args[0], "quit") || !strcmp(args[0], "exit") ||
!strcmp(args[0], "logout") || !strcmp(args[0], "bye"))
done = 1;
else if (NULL == args[0] ||
(!strcmp("help", args[0]) && NULL == args[1]))
interactive_help();
else if ((fn = is_command_valid(args)) != NULL)
(*fn)(user, args);
else
error("The command does not exist.");
free(line);
free(args);
}
while (!done);
return 0;
}
这是两个最重要的帮助程序功能
static int split_args(char ***args, char *str)
{
char **res = NULL, *p = strtok(str, " ");
int n_spaces = 0, i = 0;
while (p)
{
res = realloc(res, sizeof (char*) * ++n_spaces);
if (res == NULL)
return GITORIUM_MEM_ALLOC;
res[n_spaces-1] = p;
i++;
p = strtok(NULL, " ");
}
res = realloc(res, sizeof(char*) * (n_spaces+1));
if (res == NULL)
return GITORIUM_MEM_ALLOC;
res[n_spaces] = 0;
*args = res;
return i;
}
static int get_line(char **linep)
{
char *line = malloc(LINE_BUFFER_SIZE);
int len = LINE_BUFFER_SIZE, c;
*linep = line;
if(line == NULL)
return GITORIUM_MEM_ALLOC;
for(;;)
{
c = fgetc(stdin);
if(c == EOF || c == '\n')
break;
if(--len == 0)
{
char *linen = realloc(*linep, sizeof *linep + LINE_BUFFER_SIZE);
if(linen == NULL)
return GITORIUM_MEM_ALLOC;
len = LINE_BUFFER_SIZE;
line = linen + (line - *linep);
*linep = linen;
}
*line++ = c;
}
*line = 0;
return 0;
}
最佳答案
如果我对您的理解正确,那么您想知道如何处理信号以及获得信号后该怎么做。
建立信号处理程序的方式是使用sigaction()
。您没有说明您所使用的平台,所以我假设使用Linux,尽管sigaction()
由POSIX标准定义,并且应该在大多数其他平台上可用。
您可以通过多种方式执行此操作。一种方法是建立一个信号处理程序,将简单的全局变量设置为1,表示已捕获信号。然后在getline()
函数中建立检查以查看是否捕获了SIGINT
,然后返回NULL
并允许run_interactive()
再次运行。
捕获信号的方法如下:
#include <signal.h>
static int sigint_caught = 0;
static void sigint_handler(int sig) {
sigint_caught = 1;
}
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0; // or SA_RESTART if you want to automatically restart system calls interrupted by the signal
sa.sa_handler = sigint_handler;
if (sigaction(SIGINT, &sa, NULL) == -1) {
printf("could not establish handler\n");
exit(-1); // or something
}
然后也许在
getline()
的无限循环中,您将建立检查以查看是否已捕获SIGINT
:for (;;) {
if (sigint_caught) {
return NULL;
}
// ...
然后在您的
run_interactive()
调用中,您可以通过检查来检查返回值,以查看是否捕获了SIGINT
:// ...
get_line(&line);
if (line == NULL && sigint_caught) {
sigint_caught = 0; // allow it to be caught again
free(line);
continue; // or something; basically go to the next iteration of this loop
} else if (line[0] == '\0') {
free(line);
break;
} else {
// rest of code
没有测试它,所以我不能保证它会起作用,因为您的问题很广泛(必须仔细阅读更多代码等),但是希望它能给您足够的想法,使您可以做些什么你在你的情况。这也许是一个非常幼稚的解决方案,但它可能满足您的需求。对于更健壮的东西,也许可以查看流行的shell(例如bash或zsh)的源代码。
例如,可能发生的一件事是
fgetc()
可能会阻塞,因为stdin中没有新数据,并且可能是在发送信号时。 fgetc()
将被中断,而errno
将是EINTR
,因此您可以在getline()
中为此添加检查:c = fgetc(stdin);
// make sure to #include <errno.h>
if (errno == EINTR && sigint_caught)
return NULL;
仅当您未将
sa_flags
设置为SA_RESTART
时,才会发生这种情况。如果这样做,则fgetc
应该会自动重新启动并继续阻止,直到收到新输入为止,而这可能是您想要的,也可能不是。