首页 > 解决方案 > 试图在 C 中实现管道 - shell 挂起并且没有运行命令

问题描述

我正在尝试运行此命令ps -j | more。我想我已经正确设置了管道,但由于某种原因它只是挂起:

在此处输入图像描述

我正在调用一个运行的叉子ps -j和第二个运行的叉子more并将它们与管道连接。

由于某种原因,这仍然无法按预期工作。

下面的代码:

#define BUF_SIZE 100

int main() {

    pid_t pid1, pid2;
    int save_stdin, save_stdout;
    int fd[2];

    char *argv1[] = { "/bin/ps", "-j", NULL };
    char *argv2[] = { "/bin/more", NULL };

    char prompt[] = "Press enter: ";
    char buffer[BUF_SIZE];

    write(STDOUT_FILENO, prompt, sizeof(prompt) - 1);

    ssize_t readIn = readIn = read(STDIN_FILENO, buffer, BUF_SIZE);
    buffer[readIn - 1] = '\0';

    printf("readIn: %d\n", readIn);

    pipe(fd);

    pid1 = fork();
    if (pid1 == 0) { // child
        close(fd[0]);
        save_stdout = dup(1);
        dup2(fd[1], STDOUT_FILENO);
        execvp(argv1[0], argv1);
        close(fd[1]);
    } else { // parent
        pid2 = fork();
        if(pid2 == 0) {
            save_stdin = dup(0);
            dup2(fd[0], STDIN_FILENO);
            execvp(argv2[0], argv2);
            close(fd[0]);
        } else {

        }
    }

    dup2(save_stdin, 0);
    dup2(save_stdout, 1);
    close(save_stdin);
    close(save_stdout);

    int i = 1;
    do {
        wait(NULL);
    } while (i-- > 0);


    exit(0);

}

非常感谢任何帮助!

编辑:

我试过在之后dup2()但之前关闭管道execvp(),但它仍然挂起:

pipe(fd);

pid1 = fork();
if (pid1 == 0) { // child
    dup2(fd[1], STDOUT_FILENO);
    close(fd[1]);
    close(fd[0]);
    int res1 = execvp(argv1[0], argv1);
    printf("exec1: %d\n", res1);
} else { // parent
    pid2 = fork();
    if(pid2 == 0) {
        dup2(fd[0], STDIN_FILENO);
        close(fd[0]);
        close(fd[1]);
        int res2 = execvp(argv2[0], argv2);
        printf("exec2: %d\n", res2);
    } else {

    }
}

close(fd[1]);
close(fd[0]);
printf("finished\n");

int i = 1;
do {
    wait(NULL);
} while (i-- > 0);

标签: cunixpipe

解决方案


您没有关闭子级管道或父级管道中的任何管道的足够文件描述符。

经验法则:如果您 将管道的一端连接到标准输入或标准输出,请 尽快dup2() 关闭返回的两个原始文件描述符 。pipe()特别是,您应该在使用任何 exec*() 函数系列之前关闭它们。

如果您使用 dup() 或 复制描述符,该规则也fcntl() 适用F_DUPFD

这是正确关闭管道的工作代码。execvp()我删除了命令的路径组件,因为 (a) 它们对我的机器来说是错误的,并且 (b)如果您指定命令的路径,则使用毫无意义。我报告了大多数错误(但我不检查read()andwrite()调用的结果。我还报告了孩子的退出状态。如果孩子们无法执行命令,他们会报告并退出。你的带有“save_stdin”等的代码是进一步混淆了事情,因为变量在父级中未初始化,这是序列的位置:

dup2(save_stdin, 0);
dup2(save_stdout, 1);
close(save_stdin);
close(save_stdout);

被处决。你和我都不知道这两个dup2()调用做了什么——但很可能它们失败了,或者它们对你的标准 I/O 通道做了一些奇怪的事情。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

#define BUF_SIZE 100

int main(void)
{
    pid_t pid1, pid2;
    int fd[2];

    char *argv1[] = { "ps", "-j", NULL };   // Remove PATH because using execvp()
    char *argv2[] = { "more", NULL };       // Remove PATH because using execvp()

    char prompt[] = "Press enter: ";
    char buffer[BUF_SIZE];

    write(STDOUT_FILENO, prompt, sizeof(prompt) - 1);

    ssize_t readIn = readIn = read(STDIN_FILENO, buffer, BUF_SIZE);
    buffer[readIn - 1] = '\0';

    printf("readIn: %zd\n", readIn);

    if (pipe(fd) < 0)
    {
        fprintf(stderr, "failed to create pipe\n");
        exit(EXIT_FAILURE);
    }

    if ((pid1 = fork()) < 0)
    {
        fprintf(stderr, "failed to fork - 1\n");
        exit(EXIT_FAILURE);
    }

    if (pid1 == 0)   // child
    {
        dup2(fd[1], STDOUT_FILENO);
        close(fd[0]);
        close(fd[1]);
        execvp(argv1[0], argv1);
        fprintf(stderr, "failed to execute %s\n", argv1[0]);
        exit(EXIT_FAILURE);
    }

    if ((pid2 = fork()) < 0)
    {
        fprintf(stderr, "failed to fork - 2\n");
        exit(EXIT_FAILURE);
    }

    if (pid2 == 0)
    {
        dup2(fd[0], STDIN_FILENO);
        close(fd[0]);
        close(fd[1]);
        execvp(argv2[0], argv2);
        fprintf(stderr, "failed to execute %s\n", argv2[0]);
        exit(EXIT_FAILURE);
    }

    close(fd[0]);
    close(fd[1]);

    int corpse;
    int status;
    while ((corpse = wait(&status)) > 0)
        fprintf(stderr, "%d: child %d exited with status 0x%.4X\n",
                (int)getpid(), corpse, (unsigned)status);

    return 0;
}

该程序是pipe41(对输出进行了一些小的编辑,以删除您实际上不需要知道我在其他窗口中所做的事情):

$ ./pipe41
Press enter: 
readIn: 1
USER              PID  PPID  PGID   SESS JOBC STAT   TT       TIME COMMAND
jonathanleffler  6618  6617  6618      0    1 S    s000    0:00.11 -bash
jonathanleffler  6629  6628  6629      0    1 S+   s001    0:00.04 -bash
jonathanleffler  6660  6645  6660      0    1 S+   s002    0:00.04 -bash
jonathanleffler  6716  6695  6716      0    1 S+   s003    0:00.04 -bash
jonathanleffler  6776  6746  6776      0    1 S+   s004    0:00.04 -bash
jonathanleffler  6800  6771  6800      0    1 S+   s005    0:00.04 -bash
jonathanleffler  7487  7486  7487      0    1 S    s006    0:00.04 -bash
jonathanleffler  9558  9557  9558      0    1 S    s007    0:00.06 -bash
jonathanleffler 10375  9558 10375      0    1 S+   s007    0:00.01 ./pipe41
jonathanleffler 10377 10375 10375      0    1 S+   s007    0:00.00 more
(END)10375: child 10376 exited with status 0x0000
10375: child 10377 exited with status 0x0000
$

推荐阅读