简体   繁体   中英

I'm building a small shell. How do I set the standard in- and output of two processes to a pipe, so they can communicate?

I'm trying to implement a very small shell of my own. I have to be able to handle pipes, like

ls -l | wc -l

but only for two programs at a time. Right now, I have this:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>

#define BUFFER_SIZE 256
#define NO_PARAMS 32

void split_string(char **params, char *string){ 
  char *arg;
  int i;

  arg = strtok(string, " ");
  params[0] = arg;
  i = 1;
  while(arg != NULL){
    arg = strtok(NULL, " ");
    params[i] = arg;
    i++;
  }
}

int main(int argc, char **argv){
  char string[BUFFER_SIZE];
  char *prog1, *prog2;
  int i, err; 
  int fd[2];
  pid_t pid1, pid2;
  size_t buffer = BUFFER_SIZE;
  char *params1[NO_PARAMS], *params2[NO_PARAMS];
  int pipe_exists = 0;

  memset(string,0,buffer); 

  while(1){
    /*Read command*/ 
    fgets(string, BUFFER_SIZE-1, stdin);
    if(string == NULL){
      perror("Error reading input:\n");
      exit(1);
    }


    /*replace linefeed character with end of line character*/
    for(i=0;i<BUFFER_SIZE;i++){
      if(string[i] == 10){
        string[i] = 0;
      }
    }       

    /*check if command is "exit"*/
    if(strcmp(string,"exit") == 0){
      return 0;
    }

    /*split command into different program calls*/
    prog1 = strtok(string, "|");
    prog2 = strtok(NULL,"\0");
    if(prog2 != NULL){
      pipe_exists = 1;
      printf("PIPE!\n");
      err =  pipe(fd);
      if(err<0){
        perror("Error creating pipe:\n");
        exit(1);
      }

    }


    /*split string into arguments*/
    split_string(params1, prog1);
    if(pipe_exists){
      split_string(params2, prog2);
    }



    /*fork child process*/
    pid1 = fork(); 

    if(pid1==0){ /*child 1*/
      if(pipe_exists){
        close(fd[0]); /*close read-end*/
        err = dup2(fd[1], 1);
        if(err<0){
          perror("Error with dup in child 1!\n");
          exit(1);
        }
      }
      execvp(params1[0],params1);
      perror("Error calling exec()!\n");
      exit(1);

    }else{ /*parent*/
      if(pipe_exists){

        pid2 = fork();

        if(pid2==0){ /*child 2*/
          close(fd[1]); /*close pipe write-end*/
          err = dup2(fd[0], 0);

          if(err<0){
            perror("Error with dup in child 2!\n");
            exit(1);
          }
          execvp(params2[0],params2);
          perror("Error calling exec()!\n");
          exit(1);

        }else{ /*parent with 2 children*/
          waitpid(pid1,0,0);
          waitpid(pid2,0,0);
        }
      }else{ /*parent with 1 child*/
       waitpid(pid1,0,0); 
      }
    }
  }
}

Right now, it'll handle single commands fine, but when I input something like the command above, nothing happens!

Thanks!

Oh! I've already figured it out. I had to close the pipe in the parent program as well :)

To start with, you should loop as long as you find the pipe character. Then you need to create a pipe for each "piping".

Real shells usually forks and exec itself for each command in the pipeline. This is so it should be able to handle internal commands.

There are 3 main parts in a command with pipes.

  • The begining, that takes stdin and pipes its output something |
  • The middle, optionnal or repeated at will with two pipes | something | | something |
  • The end, that outputs to stdout | something | something

Then use three functions, one for each of those:

#define PIPE_INPUT 0
#define PIPE_OUTPUT 1

execute_pipe_start(t_cmdlist *commands)
{
  int pid;
  int fd[2];

  if (!commands)
    return;
  if (commands->next)
  {
    if (pipe(fd) < 0)
    {
      perror("pipe failed");
      exit(1);
    }
    pid = fork();
    if (!pid)
    {
      close(fd[PIPE_INPUT]);
      if (dup2(fd[PIPE_OUTPUT, 1) < 0)
      {
        perror("dup2 failed");
        exit(1);
      }
      parse_and_exec_cmd(commands->cmd);
    }
    else
    {
      waitpid(...); //what you put here is a bit tricky because
                   //some shells like tcsh will execute all
                   //commands at the same time (try cat | cat | cat | cat)
    }
    if (commands->next->next != null) //If you have 2 commands in line there is a middle
      execute_pipe_middle(commands->next, fd);
    else // no middle
      execute_pipe_end(commands->next, fd);
  }
  else
    parse_and_exec_cmd(commands->cmd);
}

execute_pipe_middle(t_cmdlist *commands, int fd_before[2])
{
  int pid;
  int fd_after[2];

  if (pipe(fd_after) < 0)
    {
      perror("pipe failed");
      exit(1);
    }
  pid = fork();
  if (!pid)
  {
    close(fd_before[PIPE_OUTPUT]);
    close(fd_after[PIPE_INPUT]);
    if (dup2(fd_after[PIPE_OUTPUT, 1) < 0)
    {
      perror("dup2 failed");
      exit(1);
    }
    if (dup2(fd_before[PIPE_INPUT, 0) < 0)
    {
      perror("dup2 failed");
      exit(1);
    }
    parse_and_exec_cmd(commands->cmd);
  }
  else
    waitpid(...);
  if (commands->next->next != null) //More than two following commands : a middle again
    execute_pipe_middle(commands->next, fd_after);
  else // No more repetition
    execute_pipe_end(commands->next, fd_after);
}

execute_pipe_end(t_cmdlist *commands, int fd_before[2])
{
  int pid;

  if (!commands)
    return;
  if (commands->next)
  {
    pid = fork();
    if (!pid)
    {
      close(fd_before[PIPE_OUTPUT]);
      if (dup2(fd_before[PIPE_INPUT, 0) < 0)
      {
        perror("dup2 failed");
        exit(1);
      }
      parse_and_exec_cmd(commands->cmd);
    }
    else
      waitpid(...);
  }
}

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