首页 > 解决方案 > 通过 C 中的管道重定向标准输入和标准输出适用于外部程序,但不适用于递归调用

问题描述

我正在尝试通过 C 中的 stdin 和 stdout 的管道重定向与分叉的子进程通信。我已经设法让它适用ls于在子进程中执行的 shell 命令(例如)。但是,我无法递归执行相同的程序并通过管道从子进程到父进程(在此测试中对父进程)重定向输出(由printf()fprintf()to 、... 打印),尽管这可行适用于或类似的命令。stdoutstdoutls

以下是我尝试解决此问题的方法:

如前所述,如果我ls在子进程中执行,这确实有效,但如果我尝试递归调用同一个程序,则不会。这是我试图让它工作的测试程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <netdb.h>
#include <errno.h>
#include <time.h>
#include <signal.h>
#include <fcntl.h>


static void usage(void){
    fprintf(stderr,"RIP");
    exit(EXIT_FAILURE);
}

int main(int argc, char *argv[]){   
    if(argc > 1){
        dprintf(STDOUT_FILENO,"Please work\n"); 
        printf("\n THIS IS A MESSAGE FROM THE CHILD \n");
        fputs("Pretty Please!\n",stdout);
        fflush(stdout);
        exit(EXIT_SUCCESS);
    }
    int p1[2];
    if(-1 == pipe(p1)) { 
        fprintf(stderr,"pipe\n");
        fprintf(stderr,"%s\n",strerror(errno));
        usage();    
    }
    int f = fork();
    if(f == 0){
        close(p1[0]);
        if(dup2(p1[1],STDOUT_FILENO) < 0){
            fprintf(stderr,"dup2\n");
            usage();
        }
        close(p1[1]);

        //I want this to work:
        //execlp("./to2", "./to2", "-e");

        //This works fine:
        execlp("ls", "ls");

        exit(EXIT_SUCCESS);
    } else if (f == -1) {
        usage();
    } else  {
        close(p1[1]);
        int w = -1;
        if(-1 == wait(&w)) usage();
        char b[12];
        memset(b,0,12); 
        read(p1[0],&b,12);

        char reading_buf[1];

        while(read(p1[0], reading_buf, 1) > 0){ 
            write(1, reading_buf, STDOUT_FILENO);
        }
        close(p1[0]);
    }
}   

出于测试目的,使用附加参数递归调用该函数,而调用父程序时不使用附加参数(因此是if(argc>1))。在最终的程序中,通过其他方式避免了无限递归。

我理解错了吗?我很困惑,唯一似乎不起作用的是重定向我自己 程序的输出......

非常感谢您,非常感谢您的任何帮助或想法。

标签: crecursionpipeforkio-redirection

解决方案


主要问题正是评论中概述的 - 你没有execlp()正确调用(也不ls是替代方法)。您必须将这些函数调用的最后一个参数设置为显式空指针,如以下代码所示,这是问题中内容的大部分温和编辑版本:

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

static void usage(void)
{
    fprintf(stderr, "RIP\n");
    exit(EXIT_FAILURE);
}

int main(int argc, char *argv[])
{
    if (argc > 1)
    {
        dprintf(STDOUT_FILENO, "Please work\n");
        printf("THIS IS A MESSAGE FROM THE CHILD\n");
        fputs("Pretty Please!\n", stdout);
        fflush(stdout);
        exit(EXIT_SUCCESS);
    }
    int p1[2];
    if (-1 == pipe(p1))
    {
        fprintf(stderr, "pipe: %s\n", strerror(errno));
        usage();
    }
    int f = fork();
    if (f == 0)
    {
        close(p1[0]);
        if (dup2(p1[1], STDOUT_FILENO) < 0)
        {
            fprintf(stderr, "dup2: %s\n", strerror(errno));
            usage();
        }
        close(p1[1]);
        execlp(argv[0], argv[0], "-e", (char *)0);
        fprintf(stderr, "failed to exec %s again\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    else if (f == -1)
    {
        usage();
    }
    else
    {
        close(p1[1]);
        char b[13];
        memset(b, 0, 13);
        if (read(p1[0], &b, 12) < 0)
        {
            fprintf(stderr, "Failed to read from pipe (%s)\n", strerror(errno));
            exit(EXIT_FAILURE);
        }
        int len = strcspn(b, "\n");
        printf("M1 [%.*s]\n", len, b);

        char reading_buf[1];

        while (read(p1[0], reading_buf, 1) > 0)
        {
            write(1, reading_buf, STDOUT_FILENO);
        }
        close(p1[0]);
        int w = -1;
        if (-1 == wait(&w))
            usage();
    }
    return 0;
}

应该强调两个重要的变化:

  • 这段代码回显了第一行数据——由它写入的数据dprintf()——而原始代码只是读取它并丢弃它。
  • wait()调用是在输入之后,而不是之前。如果子进程要写入的数据多于一组固定消息,它可能会阻塞等待父进程读取一些数据,而父进程会阻塞等待子进程退出。这将是一个僵局。

usage()函数没有正确命名——它没有报告如何运行程序。如果子进程失败,我也会以失败状态退出,而不是成功execlp()

在特殊情况下,wait()调用可能会报告某个子节点的退出状态,而不是被分叉的子节点。通常最好使用循环来收获这样的孩子。然而,所需的情况是极其特殊的——使用exec*()函数启动父进程的进程必须先前创建了一些它没有等待的子进程,以便它们被父进程继承(因为 PID 不会改变一个exec*()电话)。


推荐阅读