在我的shell中实现管道

时间:2014-07-22 01:18:57

标签: c shell pipe

我已经阅读了很多相似的科目,而且我一直在寻找很长一段时间,但我没有找到问题所在,所以我一直在向你求助:

我试图实现一个迷你shell,它的工作非常好,但我在管道实现方面存在一个问题:

在管道完成工作之前,提示会回来:     /home/sim/t? ls | grep toto打印/home/sim/t? toto(在同一行),而不是

toto
/home/sim/t? 

如果我添加一个睡眠(1)(查看代码的评论)它运作良好,但我想找到如何等待好的过程,我尝试了很多东西,但它没有'工作......

命令ls | wc在应该的时候也不打印任何内容。

这是我的代码(对不起,不要犹豫要求澄清):

# include <assert.h> 
# include <stdio.h> 
# include <stdlib.h> 
# include <unistd.h> 
# include <sys/stat.h> 
# include <sys/types.h> 
# include <fcntl.h> 
# include <sys/wait.h>
# include <string.h>
enum { 
    MaxLigne = 1024,              
    MaxMot = MaxLigne / 2,       
    MaxDirs = 100,          
    MaxPathLength = 512,      
}; 
void decouper(char *, char *, char **, int); 
void affiche_prompt();
int in_array(char *, char **);
void executer_PATH(char **);
void usage(char *);

int 
main(int argc, char * argv[]){ 
    char ligne[MaxLigne]; 
    char * mot[MaxMot];
    char * mot2[MaxMot];
    int fd[2];
    int pipe_flag, i; 
    pid_t tmp, tmp_pipe;  

    for(affiche_prompt();fgets(ligne, sizeof ligne, stdin) != 0;affiche_prompt()) { 
        decouper(ligne, " \t\n", mot, MaxMot); 

        if (mot[0] == 0)  
            continue; 

        /* Is there a pipe? */
        pipe_flag = in_array("|", mot);
        if (pipe_flag > 0){
            if (pipe(fd) != 0)
               usage("Problème dans la création du pipe");

            if(mot[pipe_flag] == 0) {
                fprintf(stderr, 
                    "Vous devez entrer une commande après le pipe (|)\n");
            }
            //Copy second instruction in an array
            i = 0;
            while(pipe_flag <= MaxMot) {        
                mot2[i] = mot[pipe_flag];
                pipe_flag++;
                i++;
            }   
        }     

        tmp = fork();   

        if (tmp < 0){ 
            perror("fork"); 
            continue; 
        }  

        if (tmp != 0){  

            if (pipe_flag > 0){ //ther is a pipe            
                tmp_pipe = fork(); //for the shell not to be closed after a pipe

                if (tmp_pipe < 0){ 
                    perror("fork"); 
                    continue; 
                }  

                if (tmp_pipe != 0){ 
                    while(wait(0) != tmp_pipe); 
                    // here is the problem because a 
                    // sleep(1); 
                    // makes the shell waits as it should
                    continue;
                }  

                close(fd[0]); 
                dup2(fd[1], STDOUT_FILENO); 
                close(fd[1]); 
                executer_PATH(mot);   
            }    
            while(wait(0) != tmp) ; 
            continue;   
        } 

        if (pipe_flag == 0){ //There is no pipe                      
            executer_PATH(mot); 
        }
        //there is one pipe
        close(fd[1]);
        dup2(fd[0], STDIN_FILENO);
        close(fd[0]);
        executer_PATH(mot2);                  
    } 
    printf("Bye\n"); 
    return 0; 
}


/* decouper  --  decouper une chaine en mots */ 
void 
decouper(char * ligne, char * separ, char * mot[], int maxmot){ 
  int i; 

  mot[0] = strtok(ligne, separ); 
  for(i = 1; mot[i - 1] != 0; i++){ 
    if (i == maxmot){ 
      usage("Erreur dans la fonction decouper: trop de mots"); 
      mot[i - 1] = 0; 
      break; 
    } 
    mot[i] = strtok(NULL, separ); 
  } 
} 

/* affiche_prompt 
    affect à la variable globale PROMPT "?" + le répertoire courant*/
void 
affiche_prompt(){
    char buffer[MaxPathLength];
    if (getcwd (buffer, MaxPathLength) == NULL) 
        usage("impossible de connaître le répertoire courant");
    printf("%s", strcat(buffer, "? ")); 
}

/* in_array --
    renvoie 0 si le mot n'est pas trouvé, la case suivante sinon */
int
in_array(char * mot_cherche, char ** mot)
{
    int i;
    for(i = 1; i <= MaxMot; i++){ 
        if (mot[i] == 0) 
            break; 

        if (strcmp(mot[i], mot_cherche) == 0){  
            mot[i] = 0; 
            return i+1;
        } 
    }   
    return 0; 
}

/* executer_PATH -- essaye de lancer la commande donnée en argument avec les 
chemins enregistrés dans PATH */ 
void 
executer_PATH(char ** commande){ 
    int i; 
    char * dirs[MaxDirs]; 
    char pathname[MaxPathLength]; 

    /* Decouper PATH en repertoires */ 
    decouper(strdup(getenv("PATH")), ":", dirs, MaxDirs); 

    for(i = 0; dirs[i] != 0; i++){           
        snprintf(pathname, sizeof pathname, "%s/%s", dirs[i], commande[0]); 
        execv(pathname, commande); 
    } 
    /* Aucun exec n’a fonctionné */       
    fprintf(stderr, "%s: not found\n", commande[0]); 
    exit(1); 
} 

/* usage -- afficher un message d'erreur et sortir */
void 
usage(char * message)
{
    fprintf(stderr, "%s\n", message);
    exit(1);
}

欢迎任何关于我的代码的评论,谢谢你提前!

1 个答案:

答案 0 :(得分:1)

你的主要问题是executer_PATH()在执行程序失败时可以返回,但是对它的5次调用中只有3次跟着错误处理代码。让函数报告错误并退出会更好。当进行更改时,shell可以合理地执行。

executer_PATH()修改为:

void
executer_PATH(char **commande)
{
    int i;
    char *dirs[MaxDirs];
    char pathname[MaxPathLength];

    decouper(strdup(getenv("PATH")), ":", dirs, MaxDirs);

    for (i = 0; dirs[i] != 0; i++)
    {
        snprintf(pathname, sizeof pathname, "%s/%s", dirs[i], commande[0]);
        execv(pathname, commande);
    }
    fprintf(stderr, "Failed to find a command for %s\n", commande[0]);
    exit(1);
}

导致输出如下:

/Users/jleffler/soq? ls | wkj
Failed to find a command for wkj
/Users/jleffler/soq? wkj | cat
Failed to find a command for wkj
/Users/jleffler/soq? wkj | lkq
Failed to find a command for lkq
Failed to find a command for wkj
/Users/jleffler/soq? Bye

我在调用executer_PATH()之后删除了现在多余的错误检查代码,但显示的代码更改足以使事情合理地运行。问题是没有错误代码的executer_PATH()最终会有一个孩子仍然在运行(等待输入?),而父母仍然在等待孩子退出,这是不会的。


修订后代码的轻微修改后的变体如下所示:

  1. 使用完整原型声明affiche_prompt();(没有参数,只是说&#39;函数存在,但你对参数列表一无所知&#39;)。
  2. 定义main()没有参数,因为它们没有被使用。
  3. executer_PATH()
  4. 中打印有关PID和命令的诊断信息
  5. 在等待循环中捕获并打印有关PID和退出状态的诊断信息。
  6. 修改内部fork()处理代码中的逻辑,使其具有if (pid < 0)else if (pid != 0)以及else。我更喜欢这个,或者switch (pid) { case -1: ...; case 0: ...; default: ...; },对你所拥有的组织。我经常创建一个函数作为if语句的每个部分中的动作。我没有修复外码。
  7. 结果输出如下:

    $ ./pipe43
    /home/jleffler/soq? ls | sleep 4
    Child 19300: sleep
    Child 19301: ls
    End-1: PID 19301 exit status 0x0000
    End-2: PID 19300 exit status 0x0000
    /home/jleffler/soq? sleep 4 | ls
    Child 19318: ls
    Child 19319: sleep
    bash-assoc-arrays.sh  kwargs.py  pipe43    posixver.h  spc.py  tmn.c
    data              makefile   pipe43.c  select.c    tmn
    Got-1: PID 19318 exit status 0x0000
    End-1: PID 19319 exit status 0x0000
    End-2: PID -1 exit status 0x0000
    /home/jleffler/soq? Bye
    $
    

    到目前为止,这么好。然后我试了一下:

    $ ./pipe43
    /home/jleffler/soq? ls | cat
    Child 19807: ls
    Child 19806: cat
    End-1: PID 19807 exit status 0x0000
    bash-assoc-arrays.sh
    data
    kwargs.py
    makefile
    pipe43
    pipe43.c
    posixver.h
    select.c
    spc.py
    tmn
    tmn.c
    

    在我打断它之前它并没有终止。这表明您没有关闭父进程中的管道 - 正在进行等待的shell。我添加了一行:

    close(fd[0]);
    close(fd[1]);
    

    在每个wait()循环之前 - 现在在下面的代码中显示 - 尽管第二对是冗余的,除非你删除内部wait()循环。

    在额外关闭的情况下,输出变为:

    $ ./pipe43
    /home/jleffler/soq? ls | cat
    Child 20111: cat
    Child 20112: ls
    bash-assoc-arrays.sh
    data
    kwargs.py
    makefile
    pipe43
    pipe43.c
    posixver.h
    select.c
    spc.py
    tmn
    tmn.c
    End-1: PID 20112 exit status 0x0000
    End-2: PID 20111 exit status 0x0000
    /home/jleffler/soq? Bye
    $
    

    修订后的代码:

    #include <assert.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <fcntl.h>
    #include <sys/wait.h>
    #include <string.h>
    
    enum {
        MaxLigne = 1024,
        MaxMot = MaxLigne / 2,
        MaxDirs = 100,
        MaxPathLength = 512,
    };
    void decouper(char *, char *, char **, int);
    void affiche_prompt(void);
    int in_array(char *, char **);
    void executer_PATH(char **);
    void usage(char *);
    
    int
    main(void)
    {
        char ligne[MaxLigne];
        char * mot[MaxMot];
        char * mot2[MaxMot];
        int fd[2];
        int pipe_flag, i;
        pid_t tmp;
    
        for(affiche_prompt();fgets(ligne, sizeof ligne, stdin) != 0;affiche_prompt()) {
            decouper(ligne, " \t\n", mot, MaxMot);
    
            if (mot[0] == 0)
                continue;
    
            /* Is there a pipe? */
            pipe_flag = in_array("|", mot);
            if (pipe_flag > 0){
                if (pipe(fd) != 0)
                   usage("Problème dans la création du pipe");
    
                if(mot[pipe_flag] == 0) {
                    fprintf(stderr,
                        "Vous devez entrer une commande après le pipe (|)\n");
                }
                //Copy second instruction in an array
                i = 0;
                while(pipe_flag <= MaxMot) {
                    mot2[i] = mot[pipe_flag];
                    pipe_flag++;
                    i++;
                }
            }
    
            tmp = fork();
    
            if (tmp < 0){
                perror("fork");
                continue;
            }
    
            if (tmp != 0)
            {
                if (pipe_flag > 0){ //ther is a pipe
                    pid_t pid2 = fork(); //for the shell not to be closed after a pipe
    
                    if (pid2 < 0){
                        perror("fork");
                    }
                    else if (pid2 != 0){
                      close(fd[0]);
                      close(fd[1]);
                      int corpse;
                      int status;
                      while ((corpse = wait(&status)) != pid2 && corpse != -1)
                        fprintf(stderr, "Got-1: PID %d exit status 0x%.4X\n", corpse, status);
                      fprintf(stderr, "End-1: PID %d exit status 0x%.4X\n", corpse, status);
                    }
                    else
                    {
                      close(fd[0]);
                      dup2(fd[1], STDOUT_FILENO);
                      close(fd[1]);
                      executer_PATH(mot);
                    }
                }
                {
                  int corpse;
                  int status;
                  /* These closes are redundant until you remove the inner wait code */
                  close(fd[0]);
                  close(fd[1]);
                  while ((corpse = wait(&status)) != tmp && corpse != -1)
                    fprintf(stderr, "Got-2: PID %d exit status 0x%.4X\n", corpse, status);
                  fprintf(stderr, "End-2: PID %d exit status 0x%.4X\n", corpse, status);
                }
                continue;
            }
    
            if (pipe_flag == 0){ //There is no pipe
                executer_PATH(mot);
            }
            //there is one pipe
            close(fd[1]);
            dup2(fd[0], STDIN_FILENO);
            close(fd[0]);
            executer_PATH(mot2);
        }
        printf("Bye\n");
        return 0;
    }
    
    
    /* decouper  --  decouper une chaine en mots */
    void
    decouper(char * ligne, char * separ, char * mot[], int maxmot){
      int i;
    
      mot[0] = strtok(ligne, separ);
      for(i = 1; mot[i - 1] != 0; i++){
        if (i == maxmot){
          usage("Erreur dans la fonction decouper: trop de mots");
          mot[i - 1] = 0;
          break;
        }
        mot[i] = strtok(NULL, separ);
      }
    }
    
    /* affiche_prompt
        affect à la variable globale PROMPT "?" + le répertoire courant*/
    void
    affiche_prompt(void){
        char buffer[MaxPathLength];
        if (getcwd (buffer, MaxPathLength) == NULL)
            usage("impossible de connaître le répertoire courant");
        printf("%s", strcat(buffer, "? "));
    }
    
    /* in_array --
        renvoie 0 si le mot n'est pas trouvé, la case suivante sinon */
    int
    in_array(char * mot_cherche, char ** mot)
    {
        int i;
        for(i = 1; i <= MaxMot; i++){
            if (mot[i] == 0)
                break;
    
            if (strcmp(mot[i], mot_cherche) == 0){
                mot[i] = 0;
                return i+1;
            }
        }
        return 0;
    }
    
    /* executer_PATH -- essaye de lancer la commande donnée en argument avec les
    chemins enregistrés dans PATH */
    void
    executer_PATH(char ** commande){
        int i;
        char * dirs[MaxDirs];
        char pathname[MaxPathLength];
        fprintf(stderr, "Child %d: %s\n", (int)getpid(), commande[0]);
    
        /* Decouper PATH en repertoires */
        decouper(strdup(getenv("PATH")), ":", dirs, MaxDirs);
    
        for(i = 0; dirs[i] != 0; i++){
            snprintf(pathname, sizeof pathname, "%s/%s", dirs[i], commande[0]);
            execv(pathname, commande);
        }
        /* Aucun exec n’a fonctionné */
        fprintf(stderr, "%s: not found\n", commande[0]);
        exit(1);
    }
    
    /* usage -- afficher un message d'erreur et sortir */
    void
    usage(char * message)
    {
        fprintf(stderr, "%s\n", message);
        exit(1);
    }