经典的C语言:在execvp函数中使用管道,进行标准输入和输出重定向。

16

我想在我的Linux C程序中使用管道和execvp函数模拟bash。

ls -l | wc -l  

这是我的程序:

if(pipe(des_p) == -1) {perror("Failed to create pipe");}

if(fork() == 0) {    //first fork
  close(1);          //closing stdout
  dup(des_p[1]);     //replacing stdout with pipe write 
  close(des_p[0]);   //closing pipe read
  close(des_p[1]);   //closing pipe write

  if(execvp(bash_args[0], bash_args)) // contains ls -l
    /* error checking */
}
else {
  if(fork() == 0) {  //creating 2nd child
    close(0);        //closing stdin
    dup(des_p[0]);   //replacing stdin with pipe read
    close(des_p[1]); //closing pipe write
    close(des_p[0]); //closing pipe read

    if(execvp(bash_args[another_place], bash_args)) //contains wc -l
      /* error checking */
  }

  close(des_p[0]);
  close(des_p[1]);
  wait(0);
  wait(0);
}

这段代码实际上可以运行,但是没有达到预期的效果。有什么问题吗?它不起作用,而我不知道为什么。

2个回答

40

你需要在父进程中关闭管道文件描述符,否则子进程将无法收到EOF信号,因为在父进程中,该管道仍然处于写入状态。这将导致第二个wait()挂起。以下方法可行:

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


int main(int argc, char** argv)
{
        int des_p[2];
        if(pipe(des_p) == -1) {
          perror("Pipe failed");
          exit(1);
        }

        if(fork() == 0)            //first fork
        {
            close(STDOUT_FILENO);  //closing stdout
            dup(des_p[1]);         //replacing stdout with pipe write 
            close(des_p[0]);       //closing pipe read
            close(des_p[1]);

            const char* prog1[] = { "ls", "-l", 0};
            execvp(prog1[0], prog1);
            perror("execvp of ls failed");
            exit(1);
        }

        if(fork() == 0)            //creating 2nd child
        {
            close(STDIN_FILENO);   //closing stdin
            dup(des_p[0]);         //replacing stdin with pipe read
            close(des_p[1]);       //closing pipe write
            close(des_p[0]);

            const char* prog2[] = { "wc", "-l", 0};
            execvp(prog2[0], prog2);
            perror("execvp of wc failed");
            exit(1);
        }

        close(des_p[0]);
        close(des_p[1]);
        wait(0);
        wait(0);
        return 0;
}

1
老兄,你太棒了。终于成功了(我修改了第一段代码,现在它可以正常工作了)。 - krzakov
8
有一个相当可靠的经验法则:如果您使用dup()dup2()将管道的一个端口复制到标准输入或标准输出,则需要关闭原始管道的两个端口。可能存在不需要这样做的情况,但是这种情况极少遇到。 - Jonathan Leffler
1
只想指出,如果您有多个使用管道相互通信的 execvp 调用,请仅在父进程中关闭管道的写入端(在子进程中两者都关闭)。如果在父进程中同时关闭两者,则后续对 waitpid 的调用将挂起。 - Asad-ullah Khan
你是想要关机而不是关闭吗?当分叉一个子进程时,你确实需要关闭子进程将使用的每个文件描述符。 - Nicholas Wilson
2
@Khan,你应该发布一个新的问题,因为你实际上是在寻求调试代码的帮助!你确实需要关闭子进程使用的每个fd,但你的代码有一个无关的问题。在被注释掉的那一行中,你正在关闭下一次循环将要使用的fd!你应该在父进程中关闭所有fds - 只是在你完成在父进程中使用它们之后才这样做。如果你检查了dup2的返回代码,你会注意到(你还需要检查pipe/fork/close/execvp/waitpid的返回代码)。 - Nicholas Wilson
显示剩余2条评论

1

了解一下wait函数的作用。它会等待直到一个子进程存在。在启动第二个子进程之前,您正在等待第一个子进程退出。第一个子进程可能不会退出,直到有一些进程从管道的另一端读取。


当我删除第一个wait()时,屏幕上会出现混乱。Execvp在主程序结束后启动。那么如何更改呢? - krzakov
创建带有管道的进程,然后使用wait等待两个进程都完成。 - Art
1
@krzakov,你需要在父进程中关闭管道文件描述符,否则第二个子进程将无法接收到EOF并会挂起(除非第一个子进程非常热心并调用shutdown()来关闭其端口)。 - Nicholas Wilson
@krzakov 看起来很熟悉...那是我的答案,除了你没有像Art指出的那样等待两个子进程。如果它能做到你想要的,那就是正确的。我只能保证我的代码可以编译并且能够实现我认为你想要的功能!(我撤销了你对问题所做的更改,因为否则会令人困惑。) - Nicholas Wilson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接