模拟Unix shell管道

3

教育任务:希望模仿管道符号(命令、方法)“|”的工作方式。程序从标准输入(STDIN)获取类似于Unix shell中的命令:

command1 | command2 | command3 | ....

我需要执行每个命令的STDIN | STDOUT重定向到一个管道,并将最终输出重定向到result.out文件中。只能使用execlp和fork。

第一种变体:对于1-2个命令可以正常工作,但是对于3个或更多个命令会冻结。我的错误在哪里:似乎我关闭了所有管道描述符?

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

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

第四种变体,修复了逻辑,几乎正确:能够正常处理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;
}
3个回答

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);

现在,我甚至不需要解释这个问题。您可以阅读自己的评论,然后尝试解释为什么要将同一管道的两端连接到同一个进程的标准输入和输出?这没有任何意义。该管道应将一个进程的标准输入连接到另一个进程的标准输出。在这种情况下,执行进程并将其标准输入连接到标准输出是毫无意义的。这让我感到困惑。
这只是其中一个问题,但在更仔细地查看后,可能还有其他问题。
整体方法对我来说似乎太复杂了。这个递归函数设置管道,实际上只需要一个决策点:这是管道中的最后一个命令吗?如果是,则执行一件事。如果不是,则进行涉及递归的其他操作,以设置管道的其余部分。

在我看来,这里可能有三个或四个决策点,所以即使整体逻辑稍微有些复杂,但并没有错误,也应该简化。根据您的注释,您不需要为管道的“中间”部分编写任何特殊代码。您要么处理管道中的最后一个命令,要么不处理。就是这样。尝试以这种方式重写您的函数。它应该更简单,也能更好地工作。


您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Aleksey Kontsevich
简化 execute_line 方法,请检查:现在没有锁,但输出中出现了混乱。如何正确地在命令之间传递管道? - Aleksey Kontsevich
1
就像我说的,这不是唯一的问题。整体逻辑是错误的。简要来说应该是:从第一个命令开始到最后一个命令,递归地启动命令,而不是反过来。递归函数参数:下一个要执行的命令和输入文件描述符。初始参数:0 和 0(stdin)。逻辑:这是最后一个命令吗?如果不是,则创建一个新的管道,递归调用自身增加下一个命令,并将pipefd[0]作为输入传递给下一个命令。现在,您要么有pipefd[1]传递给下一个命令,要么打开最后一个命令的stdout。完成逻辑。 - Sam Varshavchik
不要忘记额外的细节,比如关闭所有涉及进程中未使用的文件描述符。你需要自己找出如何做到这一点。递归调用只需将pipefd[0]附加到分叉的进程上,而pipefd[1]仍然保持打开状态。没有人说这是一项容易完成的工作。 - Sam Varshavchik
第四个变量,修正逻辑,几乎正确:现在可以使用1、3或更多命令正常工作,但开始在2上失败(先前正确工作)- 奇怪 :) - Aleksey Kontsevich

0

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

我首先将所有命令按照各自的描述符组合成一个链表,然后按顺序执行。

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);
    }
}

谢谢提供解决方案,不过我想修复我的代码——它已经非常接近正确的答案了 :) - Aleksey Kontsevich

0
在最终版本中,只有一个简单的if表达式(应该是i >= 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);
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接