【问题标题】:Imitate unix shell pipe模仿unix shell pipe
【发布时间】:2016-02-05 23:04:13
【问题描述】:

教育任务:想模仿管道符号(命令、方法)“|”工作。程序从 STDIN 获取类似 unix shell 的命令:

command1 | command2 | command3 | ....

并且应该执行它将 STDIN|STDOUT 重定向到每个命令的管道。最终输出重定向到 result.out 文件。应该只使用 execlp 和 fork。

第一个变体:适用于 1-2 个命令,但冻结 3 个或更多。我做错了什么:似乎我关闭了所有管道描述符?

现在在第二个变体中,execute_line 被简化了,现在另一个问题是:输出混乱。如何在命令之间正确传递管道?

第三个变体:最接近正确,添加了更多调试信息。问题:如何正确连接中间孩子?

第 4 个变体,固定逻辑,几乎正确:在 1、3 或更多命令下工作正常,在 2 开始时失败(之前工作正常) - 很奇怪 :)

#include <stdio.h>
#include <unistd.h>
#include <iostream>
#include <fstream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

using namespace std;

void split(const string& str, vector<string> &tokens,
           const string &delimiters = " ")
{
    // Skip delimiters at beginning.
    string::size_type lastPos = str.find_first_not_of(delimiters, 0);
    // Find first "non-delimiter".
    string::size_type pos     = str.find_first_of(delimiters, lastPos);

    while (string::npos != pos || string::npos != lastPos)
    {
        // Found a token, add it to the vector.
        tokens.push_back(str.substr(lastPos, pos - lastPos));
        // Skip delimiters.  Note the "not_of"
        lastPos = str.find_first_not_of(delimiters, pos);
        // Find next "non-delimiter"
        pos = str.find_first_of(delimiters, lastPos);
    }
}

inline string trim(string &str)
{
    const string whitespaces(" \t\f\v\n\r");

    string::size_type pos = str.find_first_not_of(whitespaces);
    if(pos != string::npos)
        str.erase(0, pos);   // prefixing spaces

    pos = str.find_last_not_of(whitespaces);
    if(pos != string::npos)
        str.erase(pos + 1);    // surfixing spaces

    return str;
}

void parse_command(string &command, string &name, string &argc)
{
    command = trim(command);

    string::size_type pos = command.find_first_of(' ');
    if(pos != string::npos) {
        name = command.substr(0, pos);
        argc = command.substr(pos + 1, command.length() - pos - 1);
    } else {
        name = command;
        argc = "";
    }
}

void exec_command(uint n, vector<string> &commands)
{
    string name, args;
    parse_command(commands[n], name, args);
    if(args.length() > 0)
        execlp(name.c_str(), name.c_str(), args.c_str(), NULL);
    else
        execlp(name.c_str(), name.c_str(), NULL);
}

// who ----(stdout)---> pfd[1] --- pfd[0] ----(stdin)---> wc -l
void execute_line(vector<string> &commands, uint i, int *parent_pfd = 0)
{
    int pfd[2];
    pipe(pfd);
    if(i > 0 && !fork()) {
        // Child
        printf("Child, i: %d\n", i);
        if(i > 1) {
            execute_line(commands, i-1, pfd);
            close(pfd[1]);
            close(pfd[0]);
        } else {
            printf("Deeper child %d: %s, parent_pfd[0]=%d, parent_pfd[1]=%d, "
                   "pfd[0]=%d, pfd[1]=%d\n",
                   getpid(), trim(commands[i-1]).c_str(),
                   parent_pfd[0], parent_pfd[1], pfd[0], pfd[1]);

            close(STDOUT_FILENO);

//            if(parent_pfd)
//                dup2(parent_pfd[1], STDOUT_FILENO); // Copy STDOUT to parent pipe out
//            else
                dup2(pfd[1], STDOUT_FILENO);        // Copy STDOUT to pipe out

            close(pfd[1]);
            close(pfd[0]);

            exec_command(i - 1, commands);
        }
    } else {
        if(parent_pfd) {
            printf("Middle Child, i: %d\n", i);
            printf("Middle child %d: %s, parent_pfd[0]=%d, parent_pfd[1]=%d, "
                   "pfd[0]=%d, pfd[1]=%d\n",
                   getpid(), trim(commands[i]).c_str(), parent_pfd[0], parent_pfd[1],
                   pfd[0], pfd[1]);

            close(STDIN_FILENO);
            dup2(pfd[0], STDIN_FILENO);         // Copy STDIN to pipe in

            close(STDOUT_FILENO);
            dup2(parent_pfd[1], STDOUT_FILENO); // Copy STDOUT to parent pipe out

            close(pfd[1]);
            close(pfd[0]);

            exec_command(i, commands);
        } else {
            printf("Final, i: %d\n", i);
            printf("Final %d: %s, pfd=%p, parent_pfd=%p, pfd[0]=%d, pfd[1]=file\n",
                   getpid(), trim(commands[i]).c_str(), pfd, parent_pfd, pfd[0]);
            int fd = open("result.out", O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
            dup2(fd, STDOUT_FILENO);            // Copy stdout to file
            dup2(pfd[0], STDIN_FILENO);         // Copy STDIN to pipe in
            close(pfd[0]);  // Close as was redirected
            close(pfd[1]);  // Close WRITE as not necessary here
            close(fd);

            exec_command(i, commands);
        }
    }
}

int main()
{
    char buffer[1024];
    ssize_t size = read(STDIN_FILENO, buffer, 1024);

    if(size > 0) {
        buffer[size] = '\0';
        string command = buffer;
        vector<string> commands;
        split(command, commands, "|");
        execute_line(commands, commands.size() - 1);
    }
    return 0;
}

【问题讨论】:

    标签: c++ unix redirect pipe stdout


    【解决方案1】:

    用于将管道连接到标准输入和输出的逻辑看起来有问题。

    int pfd[2];
    pipe(pfd);
    

    您首先创建一个管道,大概是为了将一个进程的标准输出连接到另一个进程的标准输入。没关系。

    现在,让我们看一下将执行其中一个进程的代码部分:

    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    dup2(pfd[0], STDIN_FILENO);     // Copy STDIN to pipe in
    dup2(pfd[1], STDOUT_FILENO);    // Copy STDOUT to pipe out
    close(pfd[0]);  // Close as was redirected
    close(pfd[1]);  // Close as was redirected
    exec_command(i, commands);
    

    现在,我什至不需要解释这一点。您可以在这里阅读您自己的 cmets,然后尝试解释为什么要将同一管道的两端连接到同一进程的标准输入和输出?这是没有意义的。管道应该将一个进程的标准输入附加到另一个进程的标准输出。在这种情况下,执行一个进程并将其标准输入附加到标准输出是没有意义的。这让我很头疼。

    这是这里的问题之一,但这里可能还有其他一些问题,仔细观察后会很明显。

    这里的整体方法对我来说似乎太复杂了。这个建立管道的递归函数实际上应该只有一个决策点:这是管道中的最后一个命令。如果是这样,做一件事。如果没有,则执行其他涉及递归的操作,以设置管道的其余部分。

    在我看来,这里有三个或四个决策点,所以即使这里的整体逻辑有点过于复杂但没有错,也应该简化。正如您的 cmets 所描述的,您不必对管道的“中间”部分进行任何特殊编码。要么你正在处理管道中的最后一个命令,要么没有。就是这样。尝试以这种方式重写您的函数。它应该更简单,而且效果更好。

    【讨论】:

    • 是的,应该是一个决策点,但我不清楚在这种情况下如何发送 fd。对于 2 很清楚,对于更多...
    • 简化的 execute_line 方法,请检查:现在没有锁,但是在输出中看起来很乱。如何在命令之间正确传递管道?
    • 就像我说的,这不是这里唯一的问题。整体逻辑是错误的。简而言之,它应该是:递归地从第一个到最后一个开始命令,而不是相反。递归函数参数:下一个要执行的命令,以及输入文件描述符。初始参数:0 和 0 (stdin)。逻辑:这是最后一个命令吗?如果不是,则创建一个新管道,递归调用自身以递增下一个命令,并将 pipefd[0] 作为下一个命令的输入。现在,您可以使用 pipefd[1] 到下一个命令,或者打开最后一个命令的标准输出。自己完成逻辑。
    • 不要忘记其他细节,例如关闭所有相关进程中未使用的文件描述符。你必须弄清楚如何去做。递归调用只需要将 pipefd[0] 附加到分叉的进程,而 pipefd[1] 仍然是打开的。没有人说这是一件容易的事。
    • 第 4 个变体,固定逻辑,几乎正确:现在使用 1、3 或更多命令工作正常,开始失败 2(以前工作正确) - 奇怪 :)
    【解决方案2】:

    我使用了下一个逻辑(我的解决方案的伪代码):

    我首先通过分配每个相应的描述符将所有命令放在一个链表中,然后按顺序执行。

    int pfd_cur[2];
    int in = -1;
    int out = -1;
    while (number_of_commands) { // process all commands from first to last
        number_of_commands--; // and set cur_command_number
        if(cur_command_number == 0) {
            if(number_of_commands > 0) {
                pipe(pfd_cur);
                out = pfd_cur[1];
                in = pfd_cur[0];
                // We process first command
                // collect command (STDIN_FILENO, out);
            } else {
                // We process first command and we have only one command
                // collect command (STDIN_FILENO, STDOUT_FILENO);
            }
        } else {
            if(number_of_commands == 0)
            {
                // We process last command
                // collect command(in, STDOUT_FILENO);
            } else {
                pipe(pfd_cur);
                out = pfd_cur[1];
                // We process intermediate command
                // collect command (in, out);
                in = pfd_cur[0];
            }
        }
    }
    

    我在链表中​​存储连接过程数据的结构

    struct node {
        // pipe num
        int in_fd;
        int out_fd;
        const char *command;
        char **argv;
        int agrc;
        struct node *next;
        struct node *prev;
    };
    

    我执行每个存储节点的函数

    void execute(struct node* command)
    {
        if(!fork())
        {
            if(command->out_fd != 1)
            {
                close(STDOUT_FILENO);
                dup2(command->out_fd, STDOUT_FILENO);
    
            }
            if(command->in_fd != 0)
            {
                close(STDIN_FILENO);
                dup2(command->in_fd, STDIN_FILENO);
            }
    
            execvp(command->command, command->argv);
    
            close(command->out_fd);
            close(command->in_fd);
        }
    }
    

    【讨论】:

    • 感谢您的解决方案,但是想要修复我的问题 - 它非常接近正确的解决方案 :)
    【解决方案3】:

    在最终变体中,只是简单的 if 表达式(应该是 i &gt;= 1)错误。所以正确的 execute_line() 方法变体是:

    void execute_line(vector<string> &commands, size_t i, int *parent_pfd = 0)
    {
        int pfd[2];
        pipe(pfd);
        if(i > 0 && !fork()) {
            // Child
            if(i >= 1) {
                execute_line(commands, i-1, pfd);
                close(pfd[1]);
                close(pfd[0]);
            } else {
                printf("Deeper child %d: %s, parent_pfd[0]=%d, parent_pfd[1]=%d, "
                       "pfd[0]=%d, pfd[1]=%d\n",
                       getpid(), trim(commands[i-1]).c_str(),
                       parent_pfd[0], parent_pfd[1], pfd[0], pfd[1]);
    
                close(STDOUT_FILENO);
                dup2(pfd[1], STDOUT_FILENO);        // Copy STDOUT to pipe out
    
                close(pfd[1]);
                close(pfd[0]);
    
                exec_command(i - 1, commands);
            }
        } else {
            if(parent_pfd) {
                printf("Middle child %d: %s, parent_pfd[0]=%d, parent_pfd[1]=%d, "
                       "pfd[0]=%d, pfd[1]=%d\n",
                       getpid(), trim(commands[i]).c_str(), parent_pfd[0], parent_pfd[1],
                       pfd[0], pfd[1]);
    
                close(STDIN_FILENO);
                dup2(pfd[0], STDIN_FILENO);         // Copy STDIN to pipe in
    
                close(STDOUT_FILENO);
                dup2(parent_pfd[1], STDOUT_FILENO); // Copy STDOUT to parent pipe out
    
                close(pfd[1]);
                close(pfd[0]);
    
                exec_command(i, commands);
            } else {
                printf("Final %d: %s, pfd=%p, parent_pfd=%p, pfd[0]=%d, pfd[1]=file\n",
                       getpid(), trim(commands[i]).c_str(), pfd, parent_pfd, pfd[0]);
                int fd = open("result.out", O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
                dup2(fd, STDOUT_FILENO);            // Copy stdout to file
                dup2(pfd[0], STDIN_FILENO);         // Copy STDIN to pipe in
                close(pfd[0]);  // Close as was redirected
                close(pfd[1]);  // Close WRITE as not necessary here
                close(fd);
    
                exec_command(i, commands);
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-07-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多