【问题标题】:Redirection of stdin and stdout via pipes in C works for external programmes but not for recursive call通过 C 中的管道重定向标准输入和标准输出适用于外部程序,但不适用于递归调用
【发布时间】:2018-12-03 00:33:38
【问题描述】:

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

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

  • 我创建了一个管道,读端是给父进程的,子进程应该写到写端。
  • 进程分叉,两个进程分别关闭未使用的端。
  • 管道的写入端重定向到STDOUT_FILENO并关闭
  • 子进程递归执行程序(称为./to2)

如前所述,如果我在子进程中执行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&gt;1))。 在最终的程序中,通过其他方式避免了无限递归。

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

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

【问题讨论】:

  • 欢迎来到 Stack Overflow。请尽快阅读AboutHow to Ask 页面。当您向我们展示代码时,您应该旨在展示一个 MCVE (minimal reproducible example)。你的代码中有相当多的头文件没有被使用——&lt;signal.h&gt;&lt;sys/netdb.h&gt;&lt;sys/socket.h&gt;&lt;time.h&gt;&lt;fcntl.h&gt;&lt;sys/stat.h&gt; 我相信没有使用,而&lt;sys/types.h&gt; 没有使用在现代 POSIX(自 2004 年以来,IIRC)中并不真正需要,尽管 Linux 手册页仍然倾向于列出它。请确保您的错误消息以换行符结尾;它提高了可读性。当execlp() 失败时,使用EXIT_SUCCESS 退出是不常见的。
  • 你说的是递归,但这里只显示了两个函数——main()usage()。我看不到对main()usage() 的递归调用肯定不是递归的,所以不清楚你的意思。
  • 您说 //This works fine: 后跟一个损坏的 execlp("ls", "ls"); - 这应该不起作用,因为您没有提供空指针来标记参数列表的结尾(execlp("ls", "ls", (char *)0); 是正确的) .它可能碰巧起作用,但它肯定是不正确的。您是正确的,您不需要测试来自execlp() 的返回值——它永远不会在成功时返回,只会在执行指定的子程序失败时返回。
  • 空指针丢失很可能是您在编写程序以再次运行时遇到问题的原因。但是,这不是“递归”通常的含义。通常,递归是指在单个进程中直接或间接调用自身的函数。

标签: c recursion pipe fork io-redirection


【解决方案1】:

主要问题与 cmets 中概述的完全一样——您没有正确调用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 没有t 在exec*() 调用中更改)。

【讨论】:

  • 非常感谢,这正是我的问题。由于ls 似乎在使用和不使用NULL 终止的情况下都可以无缝地工作,所以我不认为这是我的问题。也感谢您的进一步评论,我非常感谢您的反馈。
猜你喜欢
  • 1970-01-01
  • 2016-04-26
  • 1970-01-01
  • 2012-02-23
  • 1970-01-01
  • 1970-01-01
  • 2013-07-16
  • 2017-03-24
  • 2012-07-05
相关资源
最近更新 更多