目录

HW4: Shell & Pipe 实现记录

系列 - CIT5950

实现一个只含有基本命令和 Pipe(不含重定向符及其他符号)的 Shell。

  • pipe_shell.cc:在其中实现 shell 程序
  • sh.cc: 传入一个附带参数的程序,fork()子程序然后execvp()去执行它
  • stdin_echo.cc: 从stdin中读取,输出读取的内容,直到获取EOF,然后停止
  • example_tests/: 其中含有示例输入和对应输出
  • solution_binaries/: 官方答案执行码
  • 程序一次从标准输入读取一行命令
  • 一行命令包括命令本身和连接他们的 Pipe
  • 不停读入直到读入EOF/用户输入exit
  • 在当前命令完成之后才能运行下一条命令
  • 命令可以是绝对路径或者是程序名(用execvp执行)
  • 通读该指南和提供的源代码,搞清楚作业是在做什么
  • 执行一下./solution_binaries/pipe_shell,看看结果长什么样
  • 开始实现pipe_shell.cc,从循环提示用户输入开始,并且打印$提示符,直到无输入或者遇到EOF/输入exit
  • 实现fork()pipe连接和命令的执行(execvp或者直接运行命令路径)
  • 可以使用boost库中的split()trim()方法,
  • 使用execvp(), fork(), pipe(), waitpid()等函数
  • 注意不同情形:无管道,一个管道,多于一个管道
  • 两种方法
    • 使用一个 pipe 数组
    • 每次fork()之前创建一个pipe
  • 每个子进程只需要两个端口,从上一个进程送来的读端口,和给下一个子进程的写端口
  • 子进程用完端口之后关闭端口,否则程序无法正常退出
  • 普通测试: make && ./pipe_shell
  • 内存泄漏测试: valgrind --leak-check=full ./pipe_shell
  • 比较自己的程序和solution_binaries/pipe_shell的结果:

bash

cat ./tests/simple_input.txt | ./pipe_shell &> my_output.txt
diff my_output.txt ./tests/simple_output.txt

实现核心:pipe的内存在操作系统内核中。在每次循环前创建一个pipe,然后fork()一个子进程读取上一个pipe的内容,写入当前这个pipe。使用完毕后关闭这个pipe的写端,保留这个pipe的读端口。进而让下一次dup2导入时使用,使用完后可关闭该pipe的读端口。pipe不会像本地变量那样随着循环的进行而消失,只要保存一下读/写端口,在下一轮循环中就可以正常的进行读写。 此外,我们使用dup2来进行端口的重定向。

cpp

#include <unistd.h>    // for fork()
#include <sys/types.h> // for pid_t
#include <sys/wait.h>  // for wait(), waitpid(), etc.

#include <cstdlib> // for exit(), EXIT_SUCCESS, and EXIT_FAILURE
#include <iostream>
#include <vector>
#include <string.h>
#include <boost/algorithm/string.hpp>

using namespace std;

#define BUF_SIZ 1000

int main()
{
    string s;
    cout << "$ ";
    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 << "$ ";
    }
    return EXIT_SUCCESS;
}