web-dev-qa-db-fra.com

Connecter n commandes avec des pipes dans un shell?

J'essaie d'implémenter un shell en C. Je peux exécuter des commandes simples sans problème avec un simple execvp (), mais l'une des conditions à remplir est de gérer les commandes comme ceci: "ls -l | head | tail -4" avec un 'for 'loop et une seule instruction' pipe () 'redirigeant stdin et stdout. Maintenant, après des jours, je suis un peu perdu.

N = Nombre de commandes simples (3 dans l'exemple: ls, head, tail) Orders = une liste de structures avec les commandes, comme ceci:

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

Alors, j'ai fait la boucle for, et j'ai commencé à rediriger stdin et stdout afin de connecter toutes les commandes à des pipes, mais ... je ne comprends pas pourquoi cela ne fonctionne pas.

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]);

}
}

Ce que je veux créer, c'est une "ligne" de processus enfants:

[ls -l] ---- pipe ----> [tête] ---- pipe ----> [queue -4]

Tous ces processus ont une racine (le processus exécutant mon shell), donc, le premier père est également un enfant du processus shell, je suis déjà un peu fatigué, quelqu'un peut-il m'aider ici s'il vous plaît?

Je ne suis même pas sûr que ce soient les enfants qui exécutent les commandes.

Merci les gars !!

25
user1031296

Rien de complexe ici, gardez simplement à l’esprit que la dernière commande doit sortir dans le descripteur de fichier du processus original 1 et que la première doit lire le descripteur de fichier de processus original 0. previois pipe call.

Alors, voici les types:

#include <unistd.h>

struct command
{
  const char **argv;
};

Faire une fonction d'assistance avec une sémantique simple et bien définie:

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;
}

Et voici la routine principale:

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);
}

Et un petit 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);
}

Semble fonctionner. :)

49
chill

Tout d’abord, vous fermez prématurément les tuyaux. Fermez uniquement la fin dont vous n'avez pas besoin dans le processus en cours, et n'oubliez pas de fermer stdin/stdout dans l'enfant.

Deuxièmement, vous devez vous rappeler le fd de la commande précédente. Donc, pour deux processus, cela ressemble à:

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();

À présent, votre travail consiste à y ajouter le traitement des erreurs et à générer n-1 canaux pour le démarrage de n processus. Le code du premier bloc fork () doit être exécuté pour le canal approprié pour les processus 1..n-1, et le code du deuxième bloc fork () pour les processus 2..n. 

0
thiton