【问题标题】:How to run a command using pipe?如何使用管道运行命令?
【发布时间】:2012-12-27 09:01:36
【问题描述】:

我正在尝试使用 execvp 运行 ls|wc。所以我创建了一个管道然后 fork 来创建一个孩子。我关闭父/子中的适当(读/写)端,然后将另一端映射到标准输出/标准输入。然后我在子进程中使用 execvp 和 wc 在父进程中运行 ls。当我运行程序时它说

 wc:standard input:bad file descriptor.
 0 0 0
 wc: -:Bad file descriptor

这是我的代码:

int main()
{
//int nbBytes = 0; //stream length
int pfd_1[2]; //file descriptor 
//char buffer[MAX_FILE_LENGTH]; 
char* arg[MAX_FILE_LENGTH];
pid_t processPid;

//Create a pipe

if(pipe(pfd_1) == -1)
{
    printf("Error in creating pipe");
    return 0;
}

//Create a child
processPid = fork();

if(processPid == -1)
{
    printf("Erro in fork");
    exit(1);
}   
else if(processPid == 0) //Child
{               
    //redirect read end file descriptor to standard input
    dup2(pfd_1[0],0);
    //Close the write end
    if(close(pfd_1[1] == -1))
    {
        printf("Error in closing the write end file descriptor");
        exit(1);
    }
    arg[0] = "wc";
    //arg[1] = "-l";
    arg[1] = '\0';

    if(execvp(arg[0],arg) == -1)
    {
        printf("Error in executing ls");
    }       

}
else //Parent
{               
    //redirect standard output to the file descriptor
    dup2(pfd_1[1],1);
    //Close the read end
    if(close(pfd_1[0] == -1))
    {
        printf("Error in closing the read end from parent");
        exit(1);
    }
    //Command 
    arg[0] = "ls";
    arg[1] = "/proc/1/status";
    arg[2] = '\0';

    if(execvp(arg[0],arg) == -1)
    {
        printf("Error in executing ls");
    }       
}

}

知道可能出了什么问题吗?为什么它会将标准输入视为错误的文件描述符?我的理解是因为标准输入和读取结束文件描述符是别名,所以 wc -l 会读取父进程的任何输出。我是否需要进行 scanf 才能从标准输入中读取?

【问题讨论】:

  • 错误信息属于标准错误。不要使用printf 来显示它们。相反,请使用perror。这还将显示系统错误消息,告诉您错误的原因。此外,您将打开文件描述符。尝试:dup2( pfd_1[0], 0 ); close( pfd_1[0]); close( pfd_1[1]); 并检查 dup2 是否成功。

标签: c unix pipe file-descriptor


【解决方案1】:

问题出在这一行:

if(close(pfd_1[1] == -1))

您正在关闭pfd_1[1] == -1 的结果,它必然等于0(因为它们永远不会相等)。正确的行可能是:

if (close(pfd_1[1]) == -1)

请注意,您稍后会在尝试关闭父进程中的读取端时再次执行此操作。

【讨论】:

  • 感谢您的指出!我改了,还是一样的问题。
  • 在更改这两行之后,我刚刚测试了代码(假设MAX_FILE_LENGTH 和正确的#includes 的值足够大),它可以在我的机器上运行。
【解决方案2】:

如果你要去fork子进程,你必须在父进程中调用wait()以避免“僵尸”子进程。因此,您不希望通过exec 将执行原始进程分叉的父进程与另一个可执行文件重叠。

以您想要的方式设置一系列管道的一种快速方法是为您要运行的每个可执行文件派生一个子级,并将该数据读回父级中的缓冲区。然后将来自第一个子进程的数据提供给父进程分叉的新子进程。因此,每个子进程都从父进程获取数据,处理数据,然后将数据写回父进程,父进程将转换后的数据存储在缓冲区中。然后将该缓冲区馈送到下一个子节点等。缓冲区中数据的最终结果是管道的最终输出。

这是一个小伪代码:

//allocate buffer
unsigned char buffer[SIZE];

for (each executable to run in pipeline)
{
    pipes[2];
    pipe(pipes);

    pid_t pid = fork();

    if (pid == 0)
    {
        //setup the pipe in the child process
        //call exec
    }
    else
    {
        //setup the pipe in the parent process

        if (child executable is not the first in the pipeline)
        {
            //write contents of buffer to child process
        }

        //read from the pipe until the child exits
        //store the results in buffer

        //call wait, and maybe also check the return value to make sure the 
        //child returned successfully
        wait(NULL);

        //clean up the pipe
    }
}

【讨论】:

  • 由于两个进程都快速退出,您可以避免不清理僵尸,因为它们被重新设置为 PID 1,它确实会进行此类清理。
  • 嗯,所有的僵尸都是 PID 1 的父级,这就是为什么它们是“僵尸”进程......最终依靠 PID 1 来做你的脏活根本不是一个好习惯形式。
  • 感谢您的提示。但是做任何这些都不能解决我的问题。
  • @Jason 僵尸直到他们的父母离开后才会重新成为 PID 1 的父母——而且他们仍然是僵尸,直到他们的父母(不管它可能是谁)清理它们;此外,在父级中进行缓冲几乎总是错误的做法——只需使用管道直接将子级连接在一起。
  • @JonathanCallen 为什么“几乎总是”做错事?我发现这是一种非常优雅的方式来管理长管道以及创建一个可以轻松调试的环境,而无需分离出一堆进程。您可以调试父进程并查看发生了什么。我没有看到避免这种情况的特定原因吗?如果缓冲区溢出,则始终可以通过使用动态内存分配而不是像我在代码示例中所做的那样使用静态缓冲区来管理。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-07-28
  • 2011-04-25
  • 1970-01-01
  • 1970-01-01
  • 2017-01-22
  • 1970-01-01
  • 2020-03-08
相关资源
最近更新 更多