31
votes

I am trying to implement a shell in C. I can execute simple commands just fine with a simple execvp() but one of the requirements is to manage commands like this: "ls -l | head | tail -4" with a 'for' loop and only one 'pipe()' statement redirecting stdin and stdout. Now after days I'm a bit lost.

N = Number of simple commands (3 in the example: ls, head, tail) commands = a list of structs with the commands, like this:

commands[0].argv[0]: ls
commands[0].argv[1]: -l
commands[1].argv[0]: head
commands[2].argv[0]: tail
commands[2].argv[1]: -4

So, I made the for loop, and started to redirect stdin and stdout in order to connect all the commands with pipes, but...I'm just clueless why it doesn't work.

for (i=0; i < n; i++){

pipe(pipe);
if(fork()==0){  // CHILD

    close(pipe[0]);
    close(1);
    dup(pipe[1]);
    close(pipe[1]);

    execvp(commands[i].argv[0], &commands[i].argv[0]);
    perror("ERROR: ");
    exit(-1);

}else{      // FATHER

    close(pipe[1]);
    close(0);
    dup(pipe[0]);
    close(pipe[0]);

}
}

What I want to create is a 'line' of childed processes:

[ls -l] ----pipe----> [head] ----pipe----> [tail -4]

All this processes have a root (the process runing my shell) so, the first father is also a child of the shell process, I'm a bit exhausted already, can anyone help me here please?

I'm not even sure if the childs should be the ones executing the commands.

Thanks guys !!

2
Is this homework? If not - just run /bin/sh with the appropriate arguments. Why reinvent the wheel?Ed Heal
This is only one of the requirements from a 3 pages long voluntary practice. Not exactly homework, but I'd like to know how to do this, or atleast get some clues.vicpermir
there are numerous good posts here on S.O. that cover the background material you'll need to master this topic. Good luck.shellter
@user1031296 can you post the full codess321c

2 Answers

64
votes

Nothing complex here, just have in mind that the last command should output to the original process' file descriptor 1 and the first should read from original process file descriptor 0. You just spawn the processes in order, carrying along the input side of the previous pipe call.

So, here's are the types:

#include <unistd.h>

struct command
{
  const char **argv;
};

Make a helper function with a simple well defined semantics:

int
spawn_proc (int in, int out, struct command *cmd)
{
  pid_t pid;

  if ((pid = fork ()) == 0)
    {
      if (in != 0)
        {
          dup2 (in, 0);
          close (in);
        }

      if (out != 1)
        {
          dup2 (out, 1);
          close (out);
        }

      return execvp (cmd->argv [0], (char * const *)cmd->argv);
    }

  return pid;
}

And here's the main fork routine:

int
fork_pipes (int n, struct command *cmd)
{
  int i;
  pid_t pid;
  int in, fd [2];

  /* The first process should get its input from the original file descriptor 0.  */
  in = 0;

  /* Note the loop bound, we spawn here all, but the last stage of the pipeline.  */
  for (i = 0; i < n - 1; ++i)
    {
      pipe (fd);

      /* f [1] is the write end of the pipe, we carry `in` from the prev iteration.  */
      spawn_proc (in, fd [1], cmd + i);

      /* No need for the write end of the pipe, the child will write here.  */
      close (fd [1]);

      /* Keep the read end of the pipe, the next child will read from there.  */
      in = fd [0];
    }

  /* Last stage of the pipeline - set stdin be the read end of the previous pipe
     and output to the original file descriptor 1. */  
  if (in != 0)
    dup2 (in, 0);

  /* Execute the last stage with the current process. */
  return execvp (cmd [i].argv [0], (char * const *)cmd [i].argv);
}

And a small test:

int
main ()
{
  const char *ls[] = { "ls", "-l", 0 };
  const char *awk[] = { "awk", "{print $1}", 0 };
  const char *sort[] = { "sort", 0 };
  const char *uniq[] = { "uniq", 0 };

  struct command cmd [] = { {ls}, {awk}, {sort}, {uniq} };

  return fork_pipes (4, cmd);
}

Appears to work. :)

2
votes

First, you are prematurely closing the pipes. Close only the end that you don't need in the current process, and remember to close stdin/stdout in the child.

Secondly, you need to remember the fd from the previous command. So, for two processes, this looks like:

int pipe[2];
pipe(pipe);
if ( fork() == 0 ) {
     /* Redirect output of process into pipe */
     close(stdout);
     close(pipe[0]);
     dup2( pipe[1], stdout );
     execvp(commands[0].argv[0], &commands[0].argv[0]);
} 
if ( fork() == 0 ) {
     /* Redirect input of process out of pipe */
     close(stdin);
     close(pipe[1]);
     dup2( pipe[0], stdin );
     execvp(commands[1].argv[0], &commands[1].argv[0]);
}
/* Main process */
close( pipe[0] );
close( pipe[1] );
waitpid();

Now your job is to add error handling to this and generate n-1 pipes for n processes to start. The code in the first fork() block needs to be run for the appropriate pipe for processes 1..n-1, and the code in the second fork() block for the processes 2..n.