【问题标题】:How to redirect stdout to stdin using fork and pipe and execvp in c?如何在c中使用fork和pipe以及execvp将stdout重定向到stdin?
【发布时间】:2018-08-30 15:16:18
【问题描述】:

我需要自己实现 shell,但我无法正确实现管道。

如果我有类似ls -l | grep toto 的命令,我需要将ls -l 的输出(stdout)重定向到grep toto 的输入(stdin)。

我还想在调用 execvp 时在父级中显示命令的结果,而不是直接在子级中显示(这就是我有 2 个分叉的原因)。

目前,使用以下代码,我创建了一个子进程以使用 execvp 执行命令,然后将结果写入变量。父母得到它并显示它。 我还介绍了另一个 fork 来正确执行我的管道。 它正在工作(它执行并显示正确的结果),但我的孩子从未完成,在管道之后执行我的第二个命令后 execvp 被阻止。

通过一些研究,我发现这可能是因为我没有关闭我的文件描述符之一,但我检查了多次,我真的没有发现问题或描述符没有在这里关闭...

有人知道我做错了什么吗?

我的 c 语言不是很好,叉子和管道对我来说具有真正的黑暗魔力,而且我不太了解它最后是如何工作的......所以如果你有一些建议或想法我do 不好,在 cmets 随便说。

这是我的功能:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include<fcntl.h> // open function
#include<sys/types.h>
#include<sys/stat.h>
#include<getopt.h>
#include<stdbool.h>
#define STDOUT 1
#define STDERR 2
int main(int argc, char** argv)
{

    //PARENT
    pid_t pid, wpid, pid2;
    int status, status2;
    const int MIN_BUFF_SIZE = 1024;

    int fd[2];
    if (pipe(fd) == -1) {
        perror("pipe");
    }
    printf("After creating pipe \n");

    pid = fork();

    if (pid == 0) {
        // First child process (parent of second child)
        printf("First child process before fork again\n");
        pid2 = fork();
        if(pid2 == 0)
        {
            printf("Second child process begin\n");
            //second child we need to execute the left command

            close(fd[0]);
            printf("Second child |Redirect stdout > stdin\n");
            dup2(fd[1], STDOUT_FILENO); // Redirect stdout to stdin

            //test data
            char* test[3];
            test[0] = "ls\0";
            test[1] = "-l\0";
            test[2] = NULL;

            //TODO test to remove
            if (execvp(test[0], test) == -1) {
                perror("shell launch error : error in child process > execvp failed \n");
                exit(errno);
            }
            printf("Second child | After execvp\n");
            exit(errno);

        }else if(pid<0)
        {
            perror("shell launch error : error forking second child");
        }else{
            do {
                wpid = waitpid(pid2, &status2, WUNTRACED);
                printf("Second parent\n");
                //Parent
                close(fd[1]);
                printf("Second parent | Redirect stdout > stdin\n");
                dup2(fd[0], STDIN_FILENO);
                printf("Second parent | After Redirect stdout > stdin\n");

                //test data : grep toto 
                char* test2[3];
                test2[0] = "grep\0";
                test2[1] = "toto\0";
                test2[2] = NULL;
                printf("Second parent | Av dup2 fd stdout\n");
                dup2(fd[1], STDOUT_FILENO);
                close(fd[1]);
                printf("Second parent | Ap dup2 fd stdout\n");
                if (execvp(test2[0], test2) == -1) {
                    perror("shell launch error : error in child process > execvp failed \n");
                    exit(errno);
                }
                exit(errno);
            } while (!WIFEXITED(status2) && !WIFSIGNALED(status2));
        }

    } else if (pid < 0) {
        // Error forking
        perror("shell launch error : error forking");
    } else {
        do {
            //wait child process to finish it execution. So, according to project requirements,
            //we need to print the result of the command after the child process is finished
            wpid = waitpid(pid, &status, WUNTRACED);
            printf("Finished waiting for %d\n", wpid);

            close(fd[1]);  // close the write end of the pipe in the parent

            if(status != 0)
            {
                printf("Status : %d\n", status);

            }else{
                printf("We are in the first parent \n");
            }
        } while (!WIFEXITED(status) && !WIFSIGNALED(status));
    }
    printf("Finish ! \n");
    return 0;
}

这是执行后的输出:

After creating pipe 
First child process before fork again
Second child process begin
Second child |Redirect stdout > stdin
Second parent
Second parent | Redirect stdout > stdin
Second parent | After Redirect stdout > stdin
Second parent | Av dup2 fd stdout
Second parent | Ap dup2 fd stdout
-rw-r--r-- 1 ubuntu ubuntu    0 Mar  8 08:20 toto.txt
(and here it does not come back to my shell, it is waiting... something...)

我看到很多关于 Implementing pipe in cConnecting n commands with pipes in a shell? 的主题,但他们没有像我这样的 2 个 fork,这是我的问题。

编辑

我使用@William 和@Toby 的建议编辑我的帖子(使用 cmets 中给出的模式关闭我的描述符,并在等待孩子之前关闭祖父进程中管道的写入端)。我为我添加的所有新行添加了注释//new,以帮助其他有相同问题的人查看更改。我的程序不再被 execvp 阻止。

我总是有一个问题,如果在我的祖父进程中我试图读取标准输出,他们什么都不是,而不是我的命令,我错过了一个重定向或者我这次关闭了很多描述符?

    //PARENT
    pid_t pid, wpid, pid2;
    int status, status2;
    const int MIN_BUFF_SIZE = 1024;

    int fd[2];
    if (pipe(fd) == -1) {
        perror("pipe");
    }
    printf("After creating pipe \n");

    pid = fork();

    if (pid == 0) {
        // First child process (parent of second child)
        printf("First child process before fork again\n");
        pid2 = fork();
        if(pid2 == 0)
        {
            printf("Second child process begin\n");
            //second child we need to execute the left command

            close(fd[0]);
            printf("Second child |Redirect stdout > stdin\n");
            dup2(fd[1], STDOUT_FILENO); // Redirect stdout to stdin
            close(fd[1]); // NEW                

            //test data
            char* test[3];
            test[0] = "ls\0";
            test[1] = "-l\0";
            test[2] = NULL;

            //TODO test to remove
            if (execvp(test[0], test) == -1) {
                perror("shell launch error : error in child process > execvp failed \n");
                exit(errno);
            }
            printf("Second child | After execvp\n");
            exit(errno);

        }else if(pid<0)
        {
            perror("shell launch error : error forking second child");
        }else{
            do {
                wpid = waitpid(pid2, &status2, WUNTRACED);
                printf("Second parent\n");
                //Parent
                close(fd[1]);
                printf("Second parent | Redirect stdout > stdin\n");
                dup2(fd[0], STDIN_FILENO);
                close(fd[0]); // NEW
                printf("Second parent | After Redirect stdout > stdin\n");

                //test data : grep toto 
                char* test2[3];
                test2[0] = "grep\0";
                test2[1] = "toto\0";
                test2[2] = NULL;
                printf("Second parent | Av dup2 fd stdout\n");

                close(fd[0]); // NEW
                dup2(fd[1], STDOUT_FILENO);
                close(fd[1]);

                printf("Second parent | Ap dup2 fd stdout\n");
                if (execvp(test2[0], test2) == -1) {
                    perror("shell launch error : error in child process > execvp failed \n");
                    exit(errno);
                }
                exit(errno);
            } while (!WIFEXITED(status2) && !WIFSIGNALED(status2));
        }

    } else if (pid < 0) {
        // Error forking
        perror("shell launch error : error forking");
    } else {
        do {
            close(fd[1]);  //NEW close the write end of the pipe in the parent
            //wait child process to finish it execution. So, according to project requirements,
            //we need to print the result of the command after the child process is finished
            wpid = waitpid(pid, &status, WUNTRACED);
            printf("Finished waiting for %d\n", wpid);

            char* line_to_display = malloc(1);
            line_to_display = '\0';

            if(status != 0)
            {
                printf("Status : %d\n", status);

            }else{
                printf("We are in the first parent \n");
                ssize_t bytes_read = 1;
                do {

                    line_to_display = realloc(line_to_display, 1024); 
                    //sizeof(char) = 1 so don't need to do MIN_BUFF_SIZE * sizeof(char)
                    bytes_read = read(fd[0], line_to_display, 1024);

                } while (bytes_read > 0);

                printf("%s\n", line_to_display);
            }
        } while (!WIFEXITED(status) && !WIFSIGNALED(status));
    }
    printf("Finish ! \n");
    return 0;
}

【问题讨论】:

  • {和}的匹配有问题。如果您在编辑器中重新缩进程序,我想您会看到它。另外,请贴出整个程序,附上头文件和main定义。
  • 我用主要功能更新了我的帖子并包含。当我在这篇文章的代码中添加评论时,这是一个错字。
  • 请注意,void main() 是错误的——您使用的是 Unix 调用,因此您甚至不能声明“Windows 异常”。有关详细信息,请参阅 [main() 在 C 和 C++ 中返回什么?](stackoverflow.com/questions/204476)。
  • 您没有在孩子中关闭足够的文件描述符。 经验法则:如果您将管道的一端dup2() 连接到标准输入或标准输出,请尽快关闭pipe() 的两个原始文件描述符。特别是,这意味着在使用任何exec*() 系列函数之前。该规则也适用于dup()fcntl()F_DUPFD
  • 一般模式是close(fd[0); dup2(fd[1], STDOUT_FILENO); close(fd[1]);dup它之后,您需要关闭管道的末端。

标签: c pipe fork posix


【解决方案1】:

管道的写入端在祖父进程中仍然打开。我们可以通过在第一个孩子中创建管道来避免这个问题,如下所示:

    pid = fork();

    if (pid == 0) {
        // First child process (parent of second child)

        int fd[2];
        if (pipe(fd) == -1) {
            perror("pipe");
        }
        fprintf(stderr, "After creating pipe \n");

        fprintf(stderr, "First child process before fork again\n");
        pid2 = fork();

然后我们需要删除现在超出范围的close(fd[1]);(依靠编译器)。


我顺便注意到有一个错字:if(pid&lt;0) 而不是 pid2&lt;0

【讨论】:

  • 我认为我需要在第一次分叉之前声明管道,因为我需要在祖父进程中读取 fd[0] (stdout) 以获得命令的结果。我将编辑我的帖子以关闭祖父进程中管道的写入端,谢谢。 (有了这个,当我用@William 模式关闭所有描述符时,第二个 exec 不再阻止我的程序)
  • 我认为您想读取祖父母中的不同管道(使用grep 命令的输出),而不是连接lsgrep 的管道。这里很容易混淆,挑战在于使用好的名称和结构,以便您知道什么是什么!
  • 但是 grep 命令的输出在 stdout 不是吗?当我在第二个子进程中时,我将标准输出重定向到标准输入,然后它执行ls -l,然后我在使用标准输入执行的父进程中并填充标准输出,然后我回到我想要阅读的祖父母这个标准输出...我需要调用别的东西吗?
  • 只有一个进程应该读取每个管道。为什么你认为你需要ls-to-grep 管道的输出端,而不是在变成grep 的过程中?也许我不明白你要做什么。
猜你喜欢
  • 2013-01-10
  • 2011-05-14
  • 1970-01-01
  • 2021-07-23
  • 2021-04-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-18
相关资源
最近更新 更多