0

我正在尝试实现我自己的一个非常小的外壳。我必须能够处理管道,比如

ls -l | wc -l

但一次只能用于两个程序。现在,我有这个:

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

现在,它可以很好地处理单个命令,但是当我输入类似上面的命令时,什么也没有发生!

谢谢!

4

3 回答 3

2

哦!我已经想通了。我也不得不关闭父程序中的管道:)

于 2012-09-13T14:54:32.630 回答
0

带有管道的命令中有 3 个主要部分。

  • 开始,采用标准输入并管道输出something |
  • 中间,可选或随意重复,有两个管道| something |
  • 最后,输出到标准输出| something

然后使用三个函数,每个函数一个:

#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(...);
  }
}
于 2012-09-13T15:04:46.140 回答
0

首先,只要找到管道字符,就应该循环。然后你需要为每个“管道”创建一个管道。

真正的 shell 通常会为管道中的每个命令分叉及其exec 自身。这样它应该能够处理内部命令。

于 2012-09-13T14:45:53.147 回答