在C++中实现管道命令时,我无法在子进程中使用`dup2`将输入从管道重定向到STDIN_FILENO

问题描述 投票:0回答:1

我想在shell中实现管道命令,例如

ls | cat | wc
。这是我的管道命令实现。

我使用管道来存储每个命令生成的结果。对于每个子进程,它:

  • 从管道获取输入(由最后一个命令生成)
  • 将标准输出重定向到管道
  • 调用
    execvp
    来执行输入的命令

这是我的代码:

while(getline(std::cin, s)) {
    if(s == "exit") {
       return EXIT_SUCCESS;
    }

    // initialize a pipe
    int fd[2];
    if(pipe(fd) == -1) {
        perror("pipe creation failed!");
        return EXIT_FAILURE;
    }
    
    // split the command into multiple parts
    vector<string> tokens;
    boost::algorithm::split(tokens, s, boost::is_any_of("|"),boost::token_compress_on);
    for(auto& command: tokens) {
        // prepare to run the current command
    
        // get the current command
        boost::algorithm::trim(command);
        // split the command into an array of args
        vector<string> args;
        boost::algorithm::split(args,command,boost::is_any_of(" "),boost::token_compress_on);
        int argc = args.size();
        if(argc < 1) {
            cerr << "We need a command!" << endl;
            return EXIT_FAILURE; 
        }
    
        // run the current command
        pid_t child = fork();
        if(child == 0) {
            // setup the file name and input arguments
            const char* filename = args[0].c_str();
            char** argv = new char*[argc + 1];
            for(int i = 0; i < argc; i++) {
                string args_str = args[i];
                argv[i] = new char[10];
                strcpy(argv[i],args_str.c_str());
            }
            argv[argc] = nullptr;
    
            // write the pipe value into stdin
            dup2(fd[0], STDIN_FILENO);
            close(fd[0]);
    
            // write stdout to the pipe
            dup2(fd[1], STDOUT_FILENO);
            close(fd[1]);
    
            // use execvp() to run the commmand
            execvp(filename,argv);
    
            // exec didn't work, so an error must have been occurred
            cerr << strerror(errno) << endl;
            delete[] argv;
            return EXIT_FAILURE;
        }
        
        // wait for the child process to complete
        int status;
        waitpid(child,&status,0);
    }
    // read out the pipe
    char* buffer = new char[BUF_SIZ];
    int count = read(fd[0],buffer,BUF_SIZ);
    if(count > 0) {
        fprintf(stdout, "%s", buffer);
    }
    
    delete buffer;        
    
    close(fd[0]);
    close(fd[1]);
    cout << "$ ";

}

当我通过输入

ls | cat
来测试程序时。最后一个命令成功地将
ls
的结果放入管道中。但在执行
cat
命令时,我无法将输入从管道重定向到 STDIN_FILENO。

程序在执行时只是阻塞

ls | cat
。经过调试,我发现这是因为
cat
命令还在等待用户输入一些东西,而父进程正在等待子进程终止。

c++ pipe fork execvp dup2
1个回答
0
投票

我是提出这个问题的人。现在我已经解决了这个问题并成功实现了多管道程序。我对之前的代码做了 3 处更改:

  1. 在前面的代码中,只有一个管道。我之前想做的就是实现这个过程
    pipe -> STDIN -> execvp -> STDOUT -> pipe
    。但如果我只使用 1 个管道,则
    STDIN
    STDOUT
    是连接的。因此,在我的程序的当前版本中,我在
    fork()
    之前创建了一个新管道并关闭其写入端,但我将其读取端记录在变量
    fd_in
    中。
  2. 变量
    fd_in
    首先设置为0。因为有些命令如
    cat
    需要从
    STDIN
    而不是管道获取输入。每次命令执行后,
    fd_in
    都会更新为当前管道的读取端,以便执行下一个命令。在解决这个问题时,我发现管道与局部变量不同,局部变量被推送到进程内存的堆栈上。它存储在内核内存中而不是进程内存中,因此创建的管道不会在循环退出后立即消失。但是如果我们在循环中声明一个局部变量,那么它在循环退出后就会消失。如果我们记录管道的读取端,我们仍然可以在下一个循环中到达它。
  3. 我使用变量
    count
    并将其与命令向量的长度进行比较,以确定它是否是最后执行的命令。如果不是最后一个命令,则将结果写入管道。否则,只需将结果打印到控制台即可。因此,不需要使用系统调用
    read
    从管道中获取结果并将其存储在父进程的缓冲区中。主要针对那些立即将结果打印到控制台的命令,例如
    cat
    。除此之外,它简化了设计。

新版本代码如下:

    while (getline(std::cin, s))
    {
        if (s == "exit")
        {
            return EXIT_SUCCESS;
        }

        int fd[2];
        int in_fd = 0; // input fd

        // split the command into multiple parts
        vector<string> tokens;
        boost::algorithm::split(tokens, s, boost::is_any_of("|"), boost::token_compress_on);

        int count = 1;
        int command_num = tokens.size();

        for (auto &command : tokens)
        {
            // initialize a pipe
            if (pipe(fd) == -1)
            {
                perror("pipe creation failed!");
                return EXIT_FAILURE;
            }

            // prepare to run the current command

            // get the current command
            boost::algorithm::trim(command);
            // split the command into an array of args
            vector<string> args;
            boost::algorithm::split(args, command, boost::is_any_of(" "), boost::token_compress_on);
            int argc = args.size();
            if (argc < 1)
            {
                cerr << "We need a command!" << endl;
                return EXIT_FAILURE;
            }

            // run the current command
            pid_t child = fork();
            if (child == 0)
            {
                // setup the file name and input arguments
                const char *filename = args[0].c_str();
                char **argv = new char *[argc + 1];
                for (int i = 0; i < argc; i++)
                {
                    string args_str = args[i];
                    argv[i] = new char[10];
                    strcpy(argv[i], args_str.c_str());
                }
                argv[argc] = nullptr;

                if (in_fd != 0)
                {
                    // write the pipe value into stdin
                    dup2(in_fd, STDIN_FILENO);
                    close(in_fd);
                }

                if (count != command_num)
                {
                    // write stdout to the pipe
                    dup2(fd[1], STDOUT_FILENO);
                    close(fd[1]);
                }

                // use execvp() to run the commmand
                execvp(filename, argv);

                // exec didn't work, so an error must have been occurred
                cerr << strerror(errno) << endl;
                delete[] argv;
                return EXIT_FAILURE;
            }

            // wait for the child process to complete
            int status;
            waitpid(child, &status, 0);

            // close the current pipe write fd
            close(fd[1]);
            in_fd = fd[0];
            count += 1;
        }

        // // read out the pipe
        // char buffer[BUF_SIZ];
        // int count = read(in_fd, buffer, BUF_SIZ);
        // buffer[count] = '\0';
        // if (count > 0)
        // {
        //     fprintf(stdout, "%s", buffer);
        // }
        close(in_fd);

        cout << "$ ";
    }

感谢一些程序员兄弟的帮助。我在解决这个问题的过程中参考了这个问题的答案。希望我的实现能给你一些启发。

© www.soinside.com 2019 - 2024. All rights reserved.