Writing my own shell… stuck on pipes?

2020-02-25 07:04发布

For the past few days I have been attempting to write my own shell implementation but I seem to have gotten stuck on getting pipes to work properly. I am able to parse a line and fork off the commands between the pipes (ex: ls | sort) individually but can't seem to get them to pipe input from one into the other.

I think I just don't understand how to use dup2() and pipes properly.

I've now included my code which is still failing... :( So stuck...

void forkAndExecute( char* arrayOfWords[] , vector<pid_t> *vectorOfPIDs , bool hasNextCmd , bool hasPrevCmd) {

int fd[ 2 ];
pid_t pid;

if( hasNextCmd ){
    pipe(fd);
}

pid = fork();

//error if PID < 0
if( pid < 0 ) {
    cerr << ">>> fork failed >>>" << endl;
    exit(-1);
}
//child process if PID == 0
else if( pid == 0 ) {
    if ( hasPrevCmd ){
        dup2(fd[0] , 0);
        close(fd[0]);
        close(fd[1]);

    }
    if ( hasNextCmd ){
        dup2(fd[1],1);
        close(fd[0]);
        close(fd[1]);
    }
    execvp( arrayOfWords[0] , arrayOfWords );
    cout << ">>> command not found >>>" << endl;
    //if logic reaches here, exec failed
    exit(0);
} 
//parent process
else{
    close(fd[0]);
    close(fd[1]);
    //if( ! isLastCmd ){

    //}
    vectorOfPIDs->push_back(pid);
}

}

9条回答
老娘就宠你
2楼-- · 2020-02-25 07:24

Here's a tutorial on UNIX pipes, specifically about how to construct piplines in a shell-like architecture:

http://www.cse.ohio-state.edu/~mamrak/CIS762/pipes_lab_notes.html

Not much fully-written code, but it describes the concepts pretty well.

You could also download source code for virtually any shell, such as bash, tcsh, zsh, etc.

查看更多
Juvenile、少年°
3楼-- · 2020-02-25 07:28

First suggestion: Symbolic constants are better than magic numbers.

const int PIPE_READ = 0;
const int PIPE_WRITE = 1;
int fd[2];
pipe(fd);
// Now you can refer to fd[PIPE_READ] and fd[PIPE_WRITE].

Second suggestion: Take a step back and think about what you're trying to accomplish.

You want to spawn two processes, with the first process's stdout connected to the second process's stdin. Right?

So, in C, this means that you need to take call pipe, pass fd[PIPE_WRITE] to the first child process, which will dup2 it to 1, and pass fd[PIPE_READ] to the second child process, which will dup2 it to 0.

Simply looking at forkAndExecute's prototype shows that it can't do that:

void forkAndExecute( char* arrayOfWords[] , vector *vectorOfPIDs , 
    bool hasNextCmd , bool hasPrevCmd);

It only handles a single command, and from looking at that argument list, unless it resorts to evil global variables, there's no way for it to receive a file descriptor from its PrevCmd or receive a file descriptor from its NextCmd.

Think about how to manage the file descriptors that you need, and redesign forkAndExecute to be able to use these.

查看更多
太酷不给撩
4楼-- · 2020-02-25 07:32

ok This is working for me. Hope this helps you:

/************************
function: void pipeCommand(char** cmd1, char** cmd2)
comment: This pipes the output of cmd1 into cmd2.
**************************/
void pipeCommand(char** cmd1, char** cmd2) {
  int fds[2]; // file descriptors
  pipe(fds);
  // child process #1
  if (fork() == 0) {
    // Reassign stdin to fds[0] end of pipe.
    dup2(fds[0], STDIN_FILENO);
    close(fds[1]);
    close(fds[0]);
    // Execute the second command.
    // child process #2
    if (fork() == 0) {
        // Reassign stdout to fds[1] end of pipe.
        dup2(fds[1], STDOUT_FILENO);
        close(fds[0]);
        close(fds[1]);
        // Execute the first command.
        execvp(cmd1[0], cmd1);
    }
    wait(NULL);
    execvp(cmd2[0], cmd2);
    }
    close(fds[1]);
    close(fds[0]);
    wait(NULL);
}
查看更多
地球回转人心会变
5楼-- · 2020-02-25 07:37

When I needed to do a similar shell some years ago, I used the book Practical Unix Programming.

It is really useful for examples on many IPC topics. I still have a copy on my desk that I reference from time to time. For $2 - $9 used, it's a pretty good value for what you get.

For what it's worth, just thought I'd mention it.

查看更多
地球回转人心会变
6楼-- · 2020-02-25 07:39

Try reading the source code of Bash to see how they did it.

查看更多
Juvenile、少年°
7楼-- · 2020-02-25 07:41

The general process would add error handling to this base process (pseudocode):

pipe(fds)
if (fork() is child) {
  dup2(fds[1], 1)
  close(fds[0])
  close(fds[1])
  exec("ls")
}
if (fork() is child) {
  dup2(fds[0], 0)
  close(fds[0])
  close(fds[1])
  exec("sort")
}
close(fds[0])
close(fds[1])
wait()

Create the pipe first. Then fork the child processes so they inherit it. Remap the file descriptors to 0 (stdin) and 1 (stdout) so the processes read and write the appropriate places. Close any remaining file descriptor you don't want the child processes to see or block on when the work is finished. Exec the actual child processes. Wait for them to finish, and you're done!

查看更多
登录 后发表回答