C 如何设计信号安全的shell解释器

C 如何设计信号安全的shell解释器,c,shell,signals,C,Shell,Signals,我现在的代码向stdout发送一个提示,然后从stdin读取一行。在任何点接收SIGINT,都会中断执行并退出程序。我不确定我应该在哪里捕获SIGINT,我知道当收到带有当前代码的信号时,我无法启动新的提示。有没有合适的方法来实现这一点(最终目标是让它像大多数shell一样工作(SIGINT取消当前提示并启动一个新的提示)) 此代码将在Linux上运行,但平台独立性越低越好 get_line将stdin中的一行读入缓冲区,并生成一个字符[],该字符被分配给该行。 split_args获取一行并将

我现在的代码向stdout发送一个提示,然后从stdin读取一行。在任何点接收
SIGINT
,都会中断执行并退出程序。我不确定我应该在哪里捕获
SIGINT
,我知道当收到带有当前代码的信号时,我无法启动新的提示。有没有合适的方法来实现这一点(最终目标是让它像大多数shell一样工作(SIGINT取消当前提示并启动一个新的提示))

此代码将在Linux上运行,但平台独立性越低越好

get_line
将stdin中的一行读入缓冲区,并生成一个字符[],该字符被分配给该行。
split_args
获取一行并将其转换为一个char[]数组,按空格进行拆分。
命令是否有效
确定用户是否键入了已知的内部命令。无法执行外部程序

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

如果我理解正确的话,你想知道如何处理信号,以及得到信号后要做什么

建立信号处理程序的方法是使用。您没有说明您所在的平台,因此我假设是Linux,尽管POSIX标准定义了
sigaction()
,并且应该可以在大多数其他平台上使用

有多种方法可以做到这一点。一种方法是建立一个信号处理程序,它将一个全局变量设置为1,表示信号被捕获。然后在
getline()
函数中建立一个检查,查看是否捕获了
SIGINT
,然后返回
NULL
,并允许
run\u 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
}
然后在您的
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
我没有测试它,所以我不能保证它会工作,因为你的问题相当广泛(必须查看更多的代码等),但希望它能让你充分了解在你的情况下你能做些什么。这也许是一个相当幼稚的解决方案,但它可能满足您的需要。要获得更健壮的功能,可以查看bash或zsh等流行shell的源代码

例如,可能发生的一件事是,
fgetc()
可能会阻塞,因为stdin中没有新数据,这可能是在发送信号时
fgetc()
将被中断,
errno
将被
EINTR
,因此您可以在
getline()
中添加此检查:

c=fgetc(标准输入法);
//确保包括
如果(errno==EINTR&&sigint\u被捕获)
返回NULL;

只有在未将
sa_标志设置为
sa_重启
时,才会发生这种情况。如果您这样做,则
fgetc
应自动重新启动并继续阻塞,直到收到新的输入,这可能是您想要的,也可能不是您想要的。

StackOverflow用于询问有关您试图解决的问题的特定问题。“这是我想做的。你可以通过我的(其他网站)阅读我的代码。”这些问题在这里很快就结束了。如果您有特定问题要问,请在此处发布相关代码,并询问有关该代码的问题,我们可以尝试帮助您解决。(代码应该在这里,因为如果您的非现场位置因某种原因不可用,问题将变得毫无意义。未来的读者也无法搜索该问题。)谢谢::-)作为一个元提示:你可能想在重写问题时删除它,以免吸引不必要的接近票和反对票。您可以在编辑后取消删除它。@KerrekSB谢谢您的提示。我为
get\u line()
函数添加了代码。我会试试这个。我试着阅读bash的等效代码,但有些部分要么对我来说太复杂,要么我不理解。我再试试。我设法捕捉到了信号,但无法重新启动输入提示符。@xav0989您必须更具体一些。当信号被捕获时会发生什么?你看过我文章的最后一部分了吗?如果设置
SA_RESTART
,则可能是
fgetc
阻塞。可能不要设置
SA_RESTART
以在捕获信号时强制中断
fgetc
。应用程序似乎跳过了“return null”部分,而是将行字符串设置为空数组,导致应用程序认为它没有收到输入,因此可以结束。
// ...

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
c = fgetc(stdin);

// make sure to #include <errno.h>
if (errno == EINTR && sigint_caught)
  return NULL;