创建 shell 样式的管道

shell 样式的管道由两个或多个进程组成,每个进程的标准输出连接到下一个的标准输入。输出到输入的连接建立在管道上。为了建立类似管道的进程集,可以创建另一个示例中描述的管道连接子进程,并且还使用 dup2(2)函数将每个管道端复制到其进程的相应标准文件描述符上。通常最好关闭原始的管道末端文件描述符,特别是如果通常情况下,一个人打算让孩子执行不同的命令。

// Most error handling omitted for brevity, including ensuring that pipe ends are
// always closed and child processes are always collected as needed; see other
// examples for more detail.
int demo() {
    int pipefds[2];
    pid_t child1 = -1, child2 = -1;

    pipe(pipefds);

    switch (child1 = fork()) {
        case -1:
            // handle error ...
            break;
        case 0:   /* child 1 */
            close(pipefds[0]);
            dup2(pipefds[1], STDOUT_FILENO);
            close(pipefds[1]);
            execl("/bin/cat", "cat", "/etc/motd", NULL);
            exit(1);  // execl() returns only on error
        default:  /* parent */
            // nothing
    }

    switch (child2 = fork()) {
        case -1:
            // handle error ...
            break;
        case 0:   /* child 2 */
            close(pipefds[1]);
            dup2(pipefds[0], STDIN_FILENO);
            close(pipefds[0]);
            execl("/bin/grep", "grep", "[Ww]ombat", NULL);
            exit(1);  // execl() returns only on error
        default:  /* parent */
            // nothing
    }

    // Only the parent reaches this point
    close(pipefds[0]);
    close(pipefds[1]);
    wait(NULL);
    wait(NULL);
}

设置管道的一个常见错误是忘记让一个或多个进程关闭它未使用的管道端。根据经验,最终结果应该是每个管道端都由一个进程拥有,并且只在该进程中打开。

在上面的演示函数的情况下,第二个孩子或父母关闭 pipefds[1] 失败将导致第二个孩子挂起,因为 grep 将继续等待输入,直到它看到 EOF,并且不会被观察到因为管道的写入端在任何进程中都保持打开状态,例如父进程或第二个子进程本身。