在 C 中实现 shell - 流水线输入具有正确的输出但退出循环

Implementing shell in C - pipelined input has correct output but exits loop

我正在尝试在 C 中实现一个处理多个管道的基本 shell。它等待输入并在 for 循环中执行命令。当它收到 EOF 时,它停止等待输入并退出。

现在,当我输入流水线命令时,我的 shell 会输出正确的输出,例如ls | wc | grep ... 它停止等待输入并退出外部 while 循环 而不是等待下一行输入。

我发现发生这种情况是因为我的 while 循环中的 fgets 返回 null(stdin 不知何故得到 EOF?)。我在创建分叉、创建管道或执行时没有收到任何错误。

但是,如果我在没有任何管道的情况下一次输入一个命令,例如ls,它成功地打印出正确的输出并且等待下一行输入,因为它应该。

我的程序在尝试执行每个命令(下面省略)之前将每一行输入解析为 structstruct 的设计使得我可以轻松地将解析的参数传递给 execvp,我不会在这里描述。

这是我的代码的高度简化版本,省略了大部分错误检查:

FILE* input;
char line[MAX_LINE];

input = stdin;
printf("> ");
fflush(stdout);

while (fgets(line, sizeof(line), input)) {
    int i;
    struct cmdLine;
    /* struct defined elsewhere
    ** commands = # of commands in parsed input
    ** start = index where a command and its args start
    ** args[] = array holding each command/arg
    */

    /* parse input line into cmdLine */
    ...

    /* exec all commands in pipeline except the last */
    for (i = 0; i < cmdLine.commands-1; ++i) {
        int pd[2];
        pipe(pd);

        if (fork() == 0) {
            dup2(pd[1], 1);
            execvp(cmdLine.args[cmdLine.start[i]], &(cmdLine.args[cmdLine.start[i]]));
        } else {
            wait(NULL);
        }

        dup2(pd[0], 0);
        close(pd[1]);
    }    

    /* exec last command */
    if (fork() == 0) {
        execvp(cmdLine.args[cmdLine.start[i]], &(cmdLine.args[cmdLine.start[i]]));
    } else {
        wait(NULL);
    }

    if (stdin == input) {
        printf("> ");  /* print shell prompt */
        fflush(stdout);
    }
}

我几乎可以肯定我的欺骗搞砸了某个地方,但我已经尝试了几个小时而且我不明白我做错了什么。 EOF 是否以某种方式被发送到标准输入,所以封闭的 fgets returns NULL?

通过使用 0 (=stdin) 作为第二个参数调用 dup2,您将在 for 的每次迭代结束时关闭原始 stdin循环,所以你不能再通过原来的标准输入与你的程序对话。

您的代码中的问题是您试图将所有管道的连接移交给其他人;那是行不通的。这是应该起作用的:

  • 对于 n 个程序,您至少需要 (n-1) 个管道。
  • 在数组中记录所有管道 FD:一个用于管道的输入端(即写入),一个用于输出端(即读取)。
  • 对于您分叉的每个进程,将前一个管道的输出(如果有)连接到它的 stdin,并将下一个管道的输入连接到它的 stdout(或您的主程序的 stdout 如果您正在处理管道链中的最后一个进程)。
  • 一旦你分叉了所有东西:在一个循环中,poll() 在你管道的输出 FD 上,从任何有 activity 的 FD 中读取,然后写入下一个管道的输入(你自己的 stdout 最后)。如果您在其中一个管道上获得 EOF,请关闭下一个管道的输入(并从输出数组中删除 EOF 的管道输出)。关闭所有 FD 后,退出循环。

编辑:我只是想到了另一种更简单的方法,它需要更少的代码更改,但我还没有完全考虑清楚。 :) 问题是您正在破坏自己的标准输入。如果您在分叉的子进程中执行所有这些操作(即整个“处理一行命令”),则在进程之间替换标准输入根本不会影响父进程。不过,这将需要在内核中进行大量缓冲,因此它可能不会扩展。