【问题标题】:How to make wc accept a pipe file to take input from instead of stdin?如何让 wc 接受管道文件而不是标准输入来获取输入?
【发布时间】:2020-02-27 17:25:39
【问题描述】:

这是一道作业题。任务是使用 execlpfork 和管道在 C 程序中复制命令:ls | wc -l

我的方法

我觉得问题可以这样解决:

  1. 创建管道文件:pipe.txt
  2. 使用fork() 创建子进程
    • 将子进程的stdout映射到pipe.txt
    • 使用execlp 执行ls
    • 这会将ls 的输出放入pipe.txt
  3. 父进程内部
    • 将父进程的stdin映射到pipe.txt
    • 使用execlp 执行wc -l 而不提供任何进一步的参数,因此它改为从标准输入读取
    • 由于这个父进程的stdout还是终端本身,所以应该打印出终端的行数

我的代码

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

int main() {
        int pipefds[2];
        int returnstatus;
        int pid;

        char argArr[30] = {'\n'};

        returnstatus = pipe(pipefds);
        if (returnstatus == -1) {
                printf("Unable to create pipe\n");
                return 1;
        }

        int file_desc = open("pipe.txt", O_RDWR | O_APPEND | O_CREAT); 

        pid = fork();

        if (pid == 0) {
                int copy_desc = dup2(file_desc, 1);
                execlp("ls", "ls", NULL);
        } else {
                int copy_desc = dup2(file_desc, 0);
                close(copy_desc);
                execlp("wc", "wc", "-l", NULL);
        }
        return 0;
}

实际输出

main.cpp blabla.cpp main pipe.txt
>

问题

这有两个问题:

  1. 既然我把孩子的stdout设置为pipe.txt文件,为什么还在终端上输出呢?注意:它也将输出放在pipe.txt 文件中。但是为什么终端也会显示呢?

  2. 它开始等待用户提供输入?它不应该从管道文件而不是用户获取输入吗?

预期输出

5

*如果当前目录有5个文件

尝试过的解决方案

  1. 仅使用管道:(遇到错误的文件描述符错误)
int main() {
        int pipefds[2];
        int returnstatus;
        int pid;

        returnstatus = pipe(pipefds);
        if (returnstatus == -1) {
                printf("Unable to create pipe\n");
                return 1;
        }

        pid = fork();

        if (pid == 0) {
                dup2(pipefds[0], 1);
                close(pipefds[1]);
                execlp("ls", "ls", NULL);
        } else {
                dup2(pipefds[1], 0);
                close(pipefds[0]);
                execlp("wc", "wc", "-l", NULL);
        }
        return 0;
}

【问题讨论】:

  • 一个文件“pipe.txt”不是管道,你应该使用pipe函数,见man pipe
  • 我刚刚注意到你的程序包含returnstatus = pipe(pipefds);,但你没有使用pipefds
  • @kronaemmanuel 管道有两个末端:一个可以从 (pipefds[0]) 读取的末端和一个可以写入 (pipefds[1]) 的末端,并且您的程序以错误的方式获取它们.
  • 您没有在子级或父级中关闭足够的文件描述符。 经验法则:如果您将管道的一端dup2() 连接到标准输入或标准输出,请尽快关闭pipe() 的两个原始文件描述符。特别是,在使用任何exec*() 系列函数之前这样做。该规则也适用于dup()fcntl()F_DUPFD
  • @kronaemmanuel 是的,还有很多需要关闭。样板代码是dup2(pipefds[0], x); close(pipefds[0]); close(pipefds[1]);.. 关闭它们。您要保留的已被复制,现在您有两个,其中一个应该关闭。

标签: c linux pipe exec


【解决方案1】:

感谢提供帮助的 cmets。

代码中的问题是我根本没有使用管道。我正在使用我创建的文件完成所有工作。这就是基本问题。

这是新代码:

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

int main() {
    // Step1. Create pipe file descriptors: pipefd[0] for reading from pipe, pipefd[1] for writing to the pipe
    int pipefds[2];

    // Helping variables
    int returnstatus;
    int pid;

    // Step2. Create a pipe with the file descriptors
    returnstatus = pipe(pipefds);

    // Check if pipe was successfully created
    if (returnstatus == -1) {
        printf("Unable to create pipe\n");
        return 1;
    }

    // Step3. Fork to create a child process
    pid = fork();

    if (pid == 0) {
        // Inside the child process

        // Step4. Duplicate the file descriptor of the write end of the pipe and set it equal to the stdout of the process 
        dup2(pipefds[1], 1);

        // Step5. Close both ends of the pipe
        close(pipefds[0]);
        close(pipefds[1]);

        // Step6. Execute the LS command. It ouputs to stdout which we set equal to the pipe in Step4.
        // So essentially, we send all output of ls to our pipe 
        returnstatus = execlp("ls", "ls", NULL);

        // Error checking the execlp command
        if (returnstatus == -1){
            perror("Error executing execlp: ");
        }
    } else {
        // Inside the parent process

        // Step7. Duplicate the file descriptor the READ end of the pipe and set it equal to the stdin of the process
        dup2(pipefds[0], 0);

        // Step8. Close the both ends of the pipe
        close(pipefds[0]);
        close(pipefds[1]);

        // Step9. Execute the WC command. It takes the file as an argument usually but if no file is given, it will take
        // stdin as input. Since the stdin is the pipe, therefore it will read all the data from the pipe.
        // The output of the wc command is stdout which is the terminal for this process so we will get the number of
        // files/directories in the current directory as an output on the terminal
        returnstatus = execlp("wc", "wc", "-l", NULL);

        // Error checking the execlp command
        if (returnstatus == -1){
            perror("Error executing execlp: ");
        }
    }
    return 0;
}

【讨论】:

  • 在子节点中,您需要在复制写结束后关闭 both 管道描述符。在父级中,您需要在复制读取端后关闭 both 管道描述符。在更复杂的场景中(管道中的多个阶段;与管道的双向通信),这是至关重要的。在这里,你也许可以摆脱马虎,但最好不要马虎。此外,如果 execlp() 调用失败,您应该报告错误并退出。同样,在这里您不会遇到太多麻烦,但是失控的“执行失败”的孩子会让生活变得非常困难。
  • @JonathanLeffler 谢谢,我已经编辑了代码以反映这些变化。
  • nit:(在我看来并不是真正的“nit”,而是对迈向可怕代码鸿沟的第一步的批评)。错误消息必须转到 stderr。 printf("Unable to create pipe\n"); 是一场灾难,导致数百小时的调试噩梦。这并不夸张。软件在标准输出(或错误的日志记录级别)上随机喷出废话,开发人员认为这是一种有用的诊断,当被大量实例放大时,这是一个悲剧。 perror("pipe") 是必要且充分的。
猜你喜欢
  • 2018-11-17
  • 2020-02-12
  • 1970-01-01
  • 2019-12-17
  • 1970-01-01
  • 2019-05-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多