在 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
,它成功地打印出正确的输出并且等待下一行输入,因为它应该。
我的程序在尝试执行每个命令(下面省略)之前将每一行输入解析为 struct
。 struct
的设计使得我可以轻松地将解析的参数传递给 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 后,退出循环。
编辑:我只是想到了另一种更简单的方法,它需要更少的代码更改,但我还没有完全考虑清楚。 :) 问题是您正在破坏自己的标准输入。如果您在分叉的子进程中执行所有这些操作(即整个“处理一行命令”),则在进程之间替换标准输入根本不会影响父进程。不过,这将需要在内核中进行大量缓冲,因此它可能不会扩展。
我正在尝试在 C 中实现一个处理多个管道的基本 shell。它等待输入并在 for 循环中执行命令。当它收到 EOF 时,它停止等待输入并退出。
现在,当我输入流水线命令时,我的 shell 会输出正确的输出,例如ls | wc | grep ...
但 它停止等待输入并退出外部 while 循环 而不是等待下一行输入。
我发现发生这种情况是因为我的 while 循环中的 fgets 返回 null(stdin 不知何故得到 EOF?)。我在创建分叉、创建管道或执行时没有收到任何错误。
但是,如果我在没有任何管道的情况下一次输入一个命令,例如ls
,它成功地打印出正确的输出并且等待下一行输入,因为它应该。
我的程序在尝试执行每个命令(下面省略)之前将每一行输入解析为 struct
。 struct
的设计使得我可以轻松地将解析的参数传递给 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 后,退出循环。
编辑:我只是想到了另一种更简单的方法,它需要更少的代码更改,但我还没有完全考虑清楚。 :) 问题是您正在破坏自己的标准输入。如果您在分叉的子进程中执行所有这些操作(即整个“处理一行命令”),则在进程之间替换标准输入根本不会影响父进程。不过,这将需要在内核中进行大量缓冲,因此它可能不会扩展。