我正在编写一个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 execvp1n");
}
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 execvp2n");
}
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("1n");
while ((read_ = getline(&line, &len, f_pipe)) != -1) {
printf("2n");
sendout = realloc(sendout, sizeof(char*) * (send_i + 1));
sendout[send_i] = strdup(line);
send_i++;
printf("%sn", 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
,如您所见。我失败了,因为它无法读取任何内容。那将是一个时间问题。
分叉和文件描述符
分叉进程时,将继承所有文件描述符的副本。由于这些是副本,因此必须在子项和父项中关闭描述符。您应该始终尽快关闭它们。如果您分叉几次,则尤其如此。
在这里很容易错过一些东西。因此,最好非常仔细地检查所有文件描述符是否已关闭。
最小更改量
因此,代码获得结果的最小更改次数如下所示。
如果第 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
之前执行,如果在sed2
之前执行sed
会使代码更容易理解(
然后作为输出给出:
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 分配的内存永远不会释放。