简体   繁体   中英

how to implement pipe command from linux shell in c++?

i am working in a project to implement a mini linux shell, i want to implement a pipe command where it basically works like this: command1 | command2 command1 | command2 : using the pipe character “ | ” will produce a pipe, redirects command1 stdout to its write channel and command2 stdin to its read channel.

or:

command1 |& command2 : using the pipe character “ |& ” will produce a pipe, redirects command1 stderr to the pipe's write channel and command2 stdin to the pipe's read channel.

now command 1 can be either an external command from linux that i run using execv or a built in command that i wrote, and command2 is always an external command

my code is not working correctly and i don't know where is the problem exactly, because i implemented many commands and they all worked perfect for example (cp, redirection... ), so the base is good in my code, but the pipe is just wrong: for example if the command is : showpid |./parser.exe 1 where parser.exe is a giving file that does parsing on the command, for example here if showpid prints: shell process pid is 12311 , then calling this command showpid |./parser.exe 1 the output should be "shell" , but in my code the output is shell process pid is 12311

this is my pipe command implementation:

this is the class of the pipe command:

class PipeCommand : public Command {
private:
    int pipeNum;
    int split;
    string cmd1;
    string cmd2;
public:
    PipeCommand(const char* cmd_line);
    virtual ~PipeCommand() {}
    void execute() override;
};

// the pipe constructor , here i want to extract each command from the right and left side of the pipe from the cmd_line , which  is the command line that i get
// fro example : " showpid | grep 1 "

PipeCommand::PipeCommand(const char* cmd_line):Command(cmd_line) {
    pipeNum = -1;
    isBackground = _isBackgroundComamnd(cmd_line);
    string cmd1 = "", cmd2 = "";
    int split = -1;
    for (int i = 0; i < this->num_args; i++) {
        if (strcmp(args[i], "|") == 0) {
            split = i;
            pipeNum = 1;

            break;
        }

        if (strcmp(args[i], "|&") == 0) {
            split = i;
            pipeNum = 2;
            break;
        }

    }


    for (int i = 0; i < split; i++) {
        cmd1 = cmd1 + args[i] + " ";
    }

    for (int i = split + 1; i < num_args; i++) {
        cmd2 = cmd2 + args[i] + " ";
    }


// the implementation of the pipe command
void PipeCommand::execute() {

    int pipeFd[2];
    int pid;
    pipe(pipeFd);

    pid = fork();
    if (pid == 0) { // child process .

    close(pipeFd[1]);
    dup2(pipeFd[1], pipeNum);


        if (isBuiltInCMD(args[0])) {   // if the command is built in which means i wrote it i run it like this ( this works fine i checked it)
            Command *newCmd = CreateBuiltInCommand(const_cast<char *>(cmd1.c_str()));
            newCmd->execute();
            exit(0);
        } else { // if the command is external than use execv
            const char **argv = new const char *[4];
            argv[0] = "/bin/bash";
            argv[1] = "-c";
            argv[2] = cmd1.c_str();
            argv[3] = nullptr;
            execv(argv[0], const_cast<char **>(argv));
            perror("execvp failed");

        } 
    } else {     // the parent process , basically runs the command2 , which it can be only an external command
        pid = fork();  // we fork again in the parent process

        if (pid == 0)  {      // the child process executes the secomd command using execv

            dup2(pipeFd[0], STDIN_FILENO);


        close(pipeFd[0]); 
        dup2(pipeFd[0], pipeNum); 

            // execute

                const char **argv = new const char *[4];
                argv[0] = "/bin/bash";
                argv[1] = "-c";
                argv[2] = cmd2.c_str();
                argv[3] = nullptr;
                execv(argv[0], const_cast<char **>(argv));
                perror("execvp failed");
                             
        } else {   // the parent process waits 

            waitpid(pid,NULL,0);
            close(pipeFd[1]);
            close(pipeFd[0]);
        }

    }
}

I think you should look at the order that you are closing / duping file descriptors. Specifically:

The first command needs to use existing stdin (fd 0). Don't close it. But you should close existing stdout (fd 1) and THEN do the fd dup so it becomes 1.

The second command does it the other way.

I would test with a MUCH simpler example. Get the piping thing to work and THEN do the exec thing.


This is edited information added later.

In a C/C++ world, you have 3 standard files when the program starts:

FD 0 is stdin -- Used for input FD 1 is stdout -- Used for normal output FD 2 is stderr -- Used for error output

When you do this:

grep foo < file.txt | grep bar

What the shell does is:

-Does the pipe call to get the input and output files -On the first grep for foo, close fd 0 (stdin) and open file.txt for input. It will land on 0, and thus is stdin to the grep command. -Close stdout and assign it to the out part of the pipe

On the second grep:

-Close 1 (stdin) -And move the pipe input portion to 1 so stdin is set.

Thus, in the end:

part 1 fd 0 (stdin) is the file part 1 fd 1 (stdout) is the output portion of the pipe part 2 fd 0 (stdin) is the input portion of the pipe

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM