在C中制作shell时管道的正确方法是什么

时间:2013-03-28 02:40:51

标签: c shell fork pipe

我正在尝试创建自己的shell我相信我已经正确完成了分叉,但我无法弄清楚如何正确管道。任何帮助或提示将不胜感激。

基本上我的管道不工作,我花了很多年时间试图弄清楚如何让它们在进程之间正确传输数据。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include "ourhdr.h" // from Steven's book Advanced programing in the UNIX Enviroment


extern int makeargv(char *, char * , char ***);
int main()  
{   char **argp;
    int i,j,vpret;
    char buf[80];
    pid_t pid;
    int pnum;   
        //pipe number
    int ploc[16];   
        //pipe location 
        //(this has a max of 12 possible pipes)
    int targ;
    int fdleft[2], fdright[2];




    printf("            <(^_^)> \nHello \n I am your console and I am here to help you \n");
    printf("    If you dont need me anymore just say \"bye\" ");
    fflush(stdout);
    write(1,"\n(>^_^)> ",8);
    while(strcmp(fgets(buf, 80, stdin), "bye\n")!=0)
    {        
        j=makeargv(buf," \n",&argp); //this breaks the line up and returns the number of commands 
        pnum = 0;
        ploc[0] = 0;
        if (j > 16) j = 16;
        for (i=0;i<j;i++)
        {
            if ( strcmp(argp[i], "|") == 0)
            {
                argp[i]= NULL;
                ploc[pnum+1] = (i+1);
                pnum++;
            }

        } 

        for (i = 0; i < (pnum+1); i++) 
        {   
            pipe(fdright);
            if (i != 0)
            {   
                dup2(fdright[1], fdleft[0]);            
            }
            pid = fork();


            switch (pid)
            {
                case -1:
                    err_sys("fork failed");
                    break;
                case 0: // child
                    if (i != pnum)
                    {
                        dup2(fdright[1],1);
                    }
                    if ( i != 0);
                    {
                        dup2(fdright[0],0); 
                    }
                    //printf("(^o^) running pipe[%i]\n" , i);
                    targ =(ploc[i]) ;
                    execvp(argp[targ],&argp[targ]);
                    write(1,"(-_-) I'm sorry the exec failed \n",33);
                    exit(1);
                default:

                    dup2(fdleft[1], fdright[1]);
                    waitpid(pid,NULL, 0);
                    close(fdright[0]);
                    close(fdright[1]);
                    //waitpid(pid,NULL, 0);
            }
        }   
        //waitpid(pid, NULL, 0);
        write(1,"\n(>^_^)> ",8);
    }
    printf("  v(^o^)^ BYE BYE!\n");

}

谢谢

1 个答案:

答案 0 :(得分:3)

各种评论:

  1. while (strcmp(fgets(buf, 80, stdin), "bye\n")!=0)

    如果shell被赋予EOF而不是bye,则会崩溃。不要像这样组合两个函数调用。如果您想在一个循环条件下完成所有操作,请使用:

    while (fgets(buf, sizeof(buf), stdin) != 0 && strcmp(buf, "bye\n") != 0)
    

    我们将再次讨论对命令行长度的限制。

  2. 由于您没有提供makeargv()来查看,我们必须假设它可以正常工作。

  3. 将事物拆分为命令和管道的循环是:

    pnum = 0;
    ploc[0] = 0;
    if (j > 16) j = 16;
    for (i = 0; i < j; i++)
    {
        if (strcmp(argp[i], "|") == 0)
        {
            argp[i] = NULL;
            ploc[pnum+1] = (i+1);
            pnum++;
        }
    }
    

    假设我们有一个命令行输入:ls -l | grep lemon。您的makeargv()似乎会返回5并设置argp,如下所示:

    argp[0] = "ls";
    argp[1] = "-l";
    argp[2] = "|";
    argp[3] = "grep";
    argp[4] = "lemon";
    argp[5] = 0;         // Inferred - things will crash sooner or later if wrong
    

    你的这个循环会给你:

    ploc[0] = 0;
    ploc[1] = 3;
    pnum = 1;
    
  4. 您的代码在fdleft数组中有一对文件描述符,但您永远不会初始化该数组(例如,调用pipe()),即使您在调用{ {1}}。

  5. 然后主dup2()循环必须运行两次,每个命令一次。对于管道中的三个或更多命令(例如for)的一般情况,您的第一个命令(who | grep me | sort)需要其标准输入不变,但其标准输出将连接到连接的管道{ {1}}和who。 'middle'命令(倒数第二个命令的第二个,或示例中的who)每个都需要它的标准输入来自前一个管道,并且需要为其标准输出创建一个新管道。最后一个命令(在本例中,第三个命令grep)需要其标准输入来自最后一个管道,其标准输出不变。

    您的代码不会这样做,也不会靠近。

  6. 当您使用grep me然后sortpipe()将任何描述符映射到标准I / O描述符时,您需要关闭 both 管道的末端。你根本没有足够的dup()电话。

  7. 您的父进程必须依次启动每个子进程,并且只有在它们全部启动后才等待它们退出。组织流程有不同的方法。父母可以分叉一次;孩子可以负责在管道中启动前导命令,最后执行最后一个命令。父母只有一个直接孩子(其他人是孙子女),所以只需要等待一个命令完成。另一种方法是父母知道管道中的每个流程,并等待所有流程完成。

  8. 如果您的父进程在启动剩余部分之前等待管道中的每个命令完成,则最终可能会出现死锁形式。一个孩子将大量数据写入其管道,它被内核阻止,直到某个进程从管道读取,但是从管道读取的进程尚未启动,并且父进程正在等待子进程退出。您也失去了多处理和并发的好处。

  9. 在你的'案例0'中,你有一个无关的分号。我的编译器警告过我。如果你没有警告过它,你需要使用更多编译警告或获得更好的编译器。

    dup2()
  10. 关于SO的迷你炮弹中的管道有很多问题,包括:

    13636252的答案几乎是通用的。唯一的障碍是它使用char ***,这很容易让人感到困惑,而且编写得非常紧密,它具有相互递归的函数,重复次数最少。 OTOH,它工作正常,您的close()函数也使用case 0: // child if (i != pnum) { dup2(fdright[1], 1); } if (i != 0); // Unwanted semi-colon! { dup2(fdright[0], 0); } 参数。


  11. 重写代码

    这是您的代码重新编写,以便它可以工作。它包括makeargv()char ***的实现。我的err_sys()只是假设命令行中的字数少于32个。在我的想象中,它不是一个强大的命令行解析器。它允许您输入makeargv()并给出正确的答案;它还允许makeargv()并给出正确的答案;它还允许ls | wc并给出正确的答案。管道符号周围的空格是至关重要的(在普通的shell中,它们是可选的,因此who | grep me | sort也应该可以工作,但它不会使用此代码。

    ls

    示例输出:

    who|grep me|sort