如何在 C 中实现连续的管道?

How to implement successive pipes in C?

我正在开发一个使用 youtube-dl 下载最佳(标记为“(best)”)视频格式的程序。它读取一个 command-line 参数,然后启动一个 child 进程 'youtube-dl -F [url]'。然后它将带有“(best)”的行传递给提取格式并执行的例程,再次作为 child、'youtube-dl -f [best format] [url]'。问题是它仅适用于第一个 link。也许 child 没有正确写入管道,也许 parent 没有从管道读取。我迷路了。感谢您的帮助。

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

#define LINE_LEN 255

enum { ERROR=-1, CHILD };

void error(char *msg)
{
    fprintf(stderr, "%s: %s\n", msg, strerror(errno));
    exit(1);
}

void dl_best(char *format, char *url)
{
    char fmt[4];
    int status;
    pid_t pid;
    for (int i = 0; *format != ' '; format++, i++)
        fmt[i] = *format;
    fmt[3] = '[=10=]';
    switch(pid = fork()) {
    case ERROR:
        error("Failed to pipe in dl_best");
        break;
    case CHILD:
        if (execlp("youtube-dl", "youtube-dl", "-f", fmt, url, NULL) == -1)
            error("Failed to execle() in dl_best");
        break;
    default:
        if (waitpid(pid, &status, 0) == -1)
            error("Waitpid failed in dl_best()");
        break;
    }
}

void get_format(char *url)
{
    pid_t pid;
    int fd[2], status;
    char line[LINE_LEN];
    if (pipe(fd) == -1)
        error("Pipe failed");

    if ((pid = fork()) == ERROR) {
        error("Failed to create a child precess in get_format()");
    } else if (pid == CHILD) {
        if (close(fd[0]) == -1)
            error("Child failed to close reading pipe");
        if (dup2(fd[1],1) == -1)
            error("Dup2 failed in get_format()");
        if (execlp("youtube-dl", "youtube-dl", "-F", url, NULL) == -1)
            error("Failed to execute get_formats");
    } else {   //parent
        if (close(fd[1]) == -1)
            error("Parent failed to close writing pipe");
        if (dup2(fd[0],0) == -1)
            error("Dup2 failed in get_format()");
        if (waitpid(pid, &status, 0) == -1)
            error("Waitpid failed in get_format()");
        while (fgets(line, LINE_LEN, stdin)) {   
            if (strstr(line, "(best)") != NULL)
                dl_best(line, url);
        }
        if (close(fd[0]) == -1)
            error("Parent failed to close reading pipe");
    }
}


int main(int argc, char *argv[])
{
    //int fd[2], status, argc_cp = argc;
    //dl_best("22 ", argv[--argc]);

    while (--argc) 
        get_format(argv[argc]);

    return 0;
}
    if (dup2(fd[0],0) == -1)
        error("Dup2 failed in get_format()");
    if (waitpid(pid, &status, 0) == -1)
        error("Waitpid failed in get_format()");
    while (fgets(line, LINE_LEN, stdin)) {   
        if (strstr(line, "(best)") != NULL)
            dl_best(line, url);
    }

这是在 C 中从 child 管道传输到 parent 时的常见错误。使用此代码,child 将填满管道缓冲区,然后阻塞等待 parent 来排空管道,但是 parent 永远不会这样做,因为它在等待 child 退出时被阻塞,所以整个程序将死锁。 (在 child 的完整输出小于管道缓冲区大小的情况下,您不会遇到调用死锁。这可能就是它似乎仅适用于第一个命令行参数的原因。)您需要要读取 child 之前生成的所有数据,您需要等待 child 终止。对于这个程序,就像将 waitpid 及其条件移动到 while 循环下方一样简单。

您重复替换文件描述符 0 也可能 运行违反 C99 规则,即 end-of-file 是粘性条件。 (这也可以解释为什么它似乎只适用于第一个命令行参数。)您 可以 通过在 dup2 之后调用 clearerr 来解决这个问题,但是它最好不要乱用标准输入。相反,使用 fdopenfd[0] 转换为文件。

将这两个修复放在一起,您在 get_format 中的 parent-side 代码应该如下所示:

} else {   //parent
    if (close(fd[1]) != 0)
        error("Parent failed to close writing pipe");

    FILE *fp = fdopen(fd[0], "rt");
    if (!fp)
        error("Parent failed to allocate a FILE");

    while (fgets(line, LINE_LEN, fp)) {   
        if (strstr(line, "(best)") != NULL)
            dl_best(line, url);
    }
    if (fclose(fp) != 0)
        error("Parent failed to close reading pipe");

    if (waitpid(pid, &status, 0) != pid)
        error("Waitpid failed in get_format()");
}

(注意 fd[0]fp 一起被 fclose 关闭;这不是必须的(事实上,这将是 错误的 ) 在其上调用 close。)

我也会考虑允许 dl_best child 异步地 运行 - 也就是说,dl_best return child PID而不是等待它本身,然后 get_format 在其 while 循环之后等待 both children - 但这是优化,而不是错误修复。