我正在写一个代码,它可以 echo
缗 sed
两次。我的输出是正确的,但当我试图将该字符串放置在一个数组上时,它在 read
并继续进行其他调用。
这是代码。
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
char **sendout=NULL;
int send_i=0;
void sender2(char* str_) {
int fd[2];
int fd1[2];
int fd2[2];
int pid;
char* echo[] = {"echo", str_, NULL};
char* sed[] = {"sed", "regex1", NULL};
char* sed2[] = {"sed", "regex2", NULL};
int status;
if (pipe(fd) < 0) {
exit(100);
}
pid = fork();
if (pid == 0) {
close(fd[0]);
dup2(fd[1], 1);
close(fd[1]);
execvp(echo[0], echo);
printf("Error in execvp1\n");
}
if (pipe(fd1) < 0) {
exit(100);
}
pid = fork();
if (pid == 0) {
close(fd[1]);
close(fd1[0]);
dup2(fd[0], 0);
dup2(fd1[1], 1);
dup2(fd1[1], 2);
close(fd[0]);
close(fd1[1]);
execvp(sed2[0], sed2);
printf("Error in execvp2\n");
}
if (pipe(fd2) < 0) {
exit(100);
}
pid = fork();
if (pid == 0) {
close(fd1[1]);
close(fd2[0]);
dup2(fd1[0], 0);
dup2(fd2[1], 1);
dup2(fd2[1], 2);
close(fd2[1]);
close(fd1[0]);
execvp(sed[0], sed);
}
pid = fork();
if (pid == 0) {
close(fd2[1]);
char* line = NULL;
size_t len = 0;
ssize_t read_;
FILE* f_pipe;
f_pipe = fdopen(fd2[0], "r");
printf("1\n");
while ((read_ = getline(&line, &len, f_pipe)) != -1) {
printf("2\n");
sendout = realloc(sendout, sizeof(char*) * (send_i + 1));
sendout[send_i] = strdup(line);
send_i++;
printf("%s\n", line);
}
fclose(f_pipe);
close(fd2[0]);
return;
}
close(fd[1]);
close(fd[0]);
close(fd1[1]);
close(fd1[0]);
close(fd2[1]);
close(fd2[0]);
if (pid != 0) {
wait(&status);
}
}
int main() {
sender2("hello");
}
就像我说的那样,一切都很正常,直到... read
. 如果我把3个字符串传给函数,输出是这样的。
1
1
1
如果我不在最后一根管子上重复,它就能很好地打印出我所需要的东西,我还在最后一个分叉中使用了return,因为它是唯一一个没有被杀死的子进程。execvp
. 但它甚至没有达到第一个打印。我甚至尝试打开管道作为一个文件或与一个经典的打开,所以它去,我试着打开,还 fopen
如你所见 我失败了,因为它无法读取任何东西。这将是一个时间问题。
叉子和文件描述符
当你fork一个进程时,所有文件描述符的副本都会被继承。由于这些都是副本,所以在子进程和父进程中都必须关闭描述符。你应该总是尽快地关闭它们。如果你多次分叉,这一点尤其正确。
这里很容易漏掉一些东西。因此,最好仔细检查所有文件描述符是否已经关闭。
最小修改量
所以,你的代码要想得到一个结果,最少的修改次数如下。
如果第41行的第一次分叉成功,那么在父文件中你需要关闭管道文件描述符fd[0]和fd[1],例如第56行。
pid = fork();
if (pid == 0) {
...
}
close(fd[0]); //<-- add these two lines
close(fd[1]);
if (pipe(fd2) < 0) {
...
同样的,你也需要在fd1的第二次分叉后做同样的事情,所以。
pid = fork();
if (pid == 0) {
...
}
close(fd1[0]); //<-- add these two lines
close(fd1[1]);
pid = fork();
当你现在运行你的代码时,你已经得到了这样的输出:
1
2
hello
更好的测试案例
这还不能验证这两个 sed
命令将正确运行。在测试用例中,将main中的调用改为。
sender2("hello mars");
并将 sed 命令改为:
char* sed[] = {"sed", "s/moon/world/", NULL};
char* sed2[] = {"sed", "s/mars/moon/", NULL};
(sed2
命令在 sed
在你的代码中,如果能在你的代码中加入 sed
前执行 sed2
)
这样就会得到输出。
1
2
hello world
因此,两个 sed 命令都被执行。
补充说明
以下是一些言论,不分先后,主要是关于错误处理。
呼吁 fork
返回 pid_t
而不是int。所以你应该把变量pid的定义改为: pid_t pid;
.
如果 execvp
失败,应该打印错误原因,并以错误状态退出,例如这样。
perror("execvp of command xyz failed");
exit(EXIT_FAILURE);
如果打开管道失败,也要在stderr上打印一条描述性信息。
同时 fork
调用可能失败,这也应该被处理。在这种情况下,fork返回-1。和上面一样,在stderr上打印错误信息并返回错误状态。
在main中,你应该返回一个成功或失败的状态(如 return EXIT_SUCCESS;
).
你不需要使用变量 read_
. 然后就可以删除该变量。
如果 fdopen
失败,它返回NULL。这种错误情况应该被处理。
用realloc分配的内存永远不会被释放。