33

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 !!

vicpermir
  • 3,544
  • 3
  • 22
  • 34
  • Is this homework? If not - just run `/bin/sh` with the appropriate arguments. Why reinvent the wheel? – Ed Heal Nov 10 '11 at 16:36
  • 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 Nov 10 '11 at 16:46
  • there are numerous good posts here on S.O. that cover the background material you'll need to master this topic. Good luck. – shellter Nov 10 '11 at 17:09
  • @user1031296 can you post the full code – ss321c Aug 27 '18 at 13:12

2 Answers2

68

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. :)

Ivan
  • 37
  • 8
chill
  • 16,470
  • 2
  • 40
  • 44
  • Very interesting, seems to work. Time to mess with the code a bit :P – vicpermir Nov 11 '11 at 13:08
  • This is working perfectly but in one case, the I try to redirect the output of a pipe-chain it throws a weird error. I mean like in "ls -l | head > file". – vicpermir Nov 12 '11 at 13:48
  • @user1031296, works for me, const char *ls[] = { "ls", "-l", 0 }; const char *head[] = { "head", 0 }; struct command cmd [] = { {ls}, {head} }; return fork_pipes (2, cmd); – chill Nov 12 '11 at 15:05
  • I know it works, have been testing it etc. but cannot redirect the output to a file "cmd1 | cmd2 > file". Thanks for the help btw. – vicpermir Nov 12 '11 at 17:42
  • 2
    unused `fd[0]` is not closed in child processes; both pipes ends should be closed in the parent except for the last pipe. – jfs Nov 13 '13 at 21:33
  • @user1031296: to redirect to a file, add to the last stage: `if (file_fd != 1) { dup2(file_fd, 1); close(file_fd); }` – jfs Nov 13 '13 at 21:35
  • so you don't need to wait() or waitpid() at all? – kanitw Jan 20 '14 at 04:54
  • @chill: I've created a code example based on your answer that takes into account my comments above: [`pipeline-three-processes.c`](https://gist.github.com/zed/7540510). – jfs Feb 20 '14 at 17:21
  • 4
    the parent should wait for the child output right ? – bawejakunal Sep 15 '16 at 03:19
  • @chill Can you modify your example that works file file redirection also? i.e `cat < file1 | grep Hello > file2` – alhelal Aug 25 '17 at 13:54
  • @chill shouldn't you `wait` for those commands? Otherwise you'll have zombie processes. – Emil Terman Jan 02 '18 at 15:07
4

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.

thiton
  • 35,651
  • 4
  • 70
  • 100