c - 在我的 shell 中实现管道

标签 c shell pipe

我读了很多类似的主题,并且已经搜索了很长时间,但我没有找到我的问题出在哪里,所以我向你寻求帮助:

我正在尝试实现一个迷你 shell,它工作得很好,但我在管道实现方面遇到一个问题:

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

toto
/home/sim/t? 

如果我添加一个 sleep(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);
}

欢迎对我的代码提出任何评论,提前谢谢!

最佳答案

你的主要问题是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();具有完整的原型(prototype)(没有参数,它只是说“该函数存在,但你对参数列表一无所知”)。
  2. 定义main()没有参数,因为它们没有被使用。
  3. 打印 executer_PATH() 中有关 PID 和命令的诊断信息.
  4. 捕获并打印有关 PID 的诊断信息以及等待循环中的退出状态。
  5. 修改内部逻辑fork()处理代码有 if (pid < 0)else if (pid != 0)和一个else 。我更喜欢这个,或者switch (pid) { case -1: ...; case 0: ...; default: ...; } ,到您所在的组织。我经常创建一个函数来作为 if 的每个部分中的操作。陈述。我没有修复外部代码。

结果输出如下:

$ ./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);
}

关于c - 在我的 shell 中实现管道,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24877423/

相关文章:

c - 搜索数组成员之间差异的函数

Ruby: 'bundle exec' 为所有 shell 命令抛出错误

c - 为什么在没有人阅读后继续写入命名管道?

python - Visual Studio CL.exe 找不到 python.h

linux - Manage ctrl+z and bg or & signal in shell script - (shell脚本中的后台进程)

java - 重新格式化java代码

bash - 如何识别Linux屏幕上打印的内容?

c - 这段 C 代码有什么问题? child 不回来了?

c - 无法在C中的父进程和子进程之间发送信号

c - 为什么 strlen 函数在此 for 循环条件下不起作用?