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

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

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

#include  #include  #include  #include  #include  #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 ^_^)> ",8); } printf(" v(^o^)^ BYE BYE!\n"); } 

谢谢

各种评论:

  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 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() ),即使您在调用dup2()也使用它。

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

    您的代码不会这样做,也不会接近任何地方。

  6. 当您使用pipe()然后dup()dup2()将任何描述符映射到标准I / O描述符时,您需要关闭管道的两端。 你根本没有足够的close()调用。

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

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

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

     case 0: // child if (i != pnum) { dup2(fdright[1], 1); } if (i != 0); // Unwanted semi-colon! { dup2(fdright[0], 0); } 
  10. 关于SO的迷你弹壳管道有很多问题,包括:

    • SO 13636252
    • SO 13693446
    • SO 13905948
    • SO 14312939
    • SO 13213864

    13636252的答案几乎是通用的。 唯一的障碍是它使用char *** ,这很容易让人感到困惑,并且它写得非常紧密,它具有相互递归的函数,重复次数最少。 OTOH,它工作正常,你的makeargv()函数也使用char ***参数。


重写代码

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

 #include  #include  #include  #include  #include  #include  #include  #include  extern int makeargv(char *line, char *seps, char ***args); extern void err_sys(const char *msg); static void dump_argv(char **argv); static void dump_fds(void); int main(void) { char buf[80]; printf(" <(^_^)> \nHello \n I am your console and I am here to help you\n"); printf(" If you don't need me anymore just say \"bye\"\n"); fflush(stdout); dump_fds(); printf("(>^_^)> "); while (fgets(buf, sizeof(buf), stdin) != 0 && strcmp(buf, "bye\n") != 0) { pid_t pid; char **argp; int fdleft[2] = { -1, -1 }; int fdright[2] = { -1, -1 }; int pnum = 0; int ploc[16]; int j = makeargv(buf, " \n", &argp); ploc[0] = 0; if (j > 16) j = 16; for (int i = 0; i < j; i++) { if (strcmp(argp[i], "|") == 0) { argp[i] = NULL; ploc[++pnum] = i+1; } } printf("pnum = %d\n", pnum); for (int k = 0; k < pnum+1; k++) printf("ploc[%d] = %d\n", k, ploc[k]); for (int i = 0; i < pnum+1; i++) { if (i != pnum) { if (pnum > 0) { if (pipe(fdright) != 0) err_sys("pipe"); //printf("%d: fdright = { %d, %d }\n", i, fdright[0], fdright[1]); //dump_fds(); } } if ((pid = fork()) < 0) err_sys("fork failed"); else if (pid == 0) { /* Child */ int targ; //dump_fds(); if (i != pnum) { dup2(fdright[1], 1); close(fdright[0]); close(fdright[1]); } if (i != 0) { dup2(fdleft[0], 0); close(fdleft[0]); close(fdleft[1]); } targ = ploc[i]; dump_argv(&argp[targ]); dump_fds(); execvp(argp[targ], &argp[targ]); fprintf(stderr, "(-_-) I'm sorry the exec failed\n"); exit(1); } if (i != 0) { //dump_fds(); //printf("%d: fdleft = { %d, %d }\n", i, fdleft[0], fdleft[1]); assert(fdleft[0] != -1 && fdleft[1] != -1); close(fdleft[0]); close(fdleft[1]); //dump_fds(); } printf("PID %d launched\n", pid); fdleft[0] = fdright[0]; fdleft[1] = fdright[1]; } //dump_fds(); //printf("%d: fdleft = { %d, %d }\n", -1, fdleft[0], fdleft[1]); close(fdleft[0]); close(fdleft[1]); free(argp); //dump_fds(); int corpse; int status; while ((corpse = waitpid(0, &status, 0)) > 0) printf(":-( PID %d status 0x%.4X\n", corpse, status); printf("\n(>^_^)> "); } printf(" v(^o^)^ BYE BYE!\n"); } static void dump_argv(char **argv) { int n = 0; char **args; args = argv; while (*args++ != 0) n++; fprintf(stderr, "%d: %d args\n", getpid(), n); args = argv; while (*args != 0) fprintf(stderr, "[%s]\n", *args++); fprintf(stderr, "EOA\n"); } /* Report on open file descriptors (0..19) in process */ static void dump_fds(void) { struct stat b; char buffer[32]; sprintf(buffer, "%d: ", getpid()); char *str = buffer + strlen(buffer); for (int i = 0; i < 20; i++) *str++ = (fstat(i, &b) == 0) ? 'o' : '-'; *str++ = '\n'; *str = '\0'; fputs(buffer, stderr); } int makeargv(char *line, char *seps, char ***args) { enum { MAX_ARGS = 32 }; char **argv = malloc(32 * sizeof(char *)); // Lazy! if (argv == 0) err_sys("out of memory in makeargv()"); int n; char **argp = argv; char *str = line; for (n = 0; n < MAX_ARGS - 1; n++) { str += strspn(str, seps); if (*str == '\0') break; *argp++ = str; int len = strcspn(str, seps); if (len == 0) break; str[len] = '\0'; str += len + 1; } *argp = 0; dump_argv(argv); *args = argv; return(n); } void err_sys(const char *msg) { int errnum = errno; char *errmsg = strerror(errnum); fprintf(stderr, "%s (%d: %s)\n", msg, errnum, errmsg); exit(1); } 

样本输出:

 $ ./pipes-15673333 <(^_^)> Hello I am your console and I am here to help you If you don't need me anymore just say "bye" 29191: ooo----------------- (>^_^)> who | grep jl | sort 29191: 6 args [who] [|] [grep] [jl] [|] [sort] EOA pnum = 2 ploc[0] = 0 ploc[1] = 2 ploc[2] = 5 PID 29194 launched PID 29195 launched 29194: 1 args [who] EOA PID 29196 launched 29194: ooo----------------- 29195: 2 args [grep] [jl] EOA 29195: ooo----------------- 29196: 1 args [sort] EOA 29196: ooo----------------- :-( PID 29194 status 0x0000 jleffler console Mar 27 15:11 jleffler ttys000 Mar 27 16:26 jleffler ttys001 Mar 27 16:26 jleffler ttys002 Mar 27 16:26 jleffler ttys003 Mar 27 16:26 jleffler ttys004 Mar 27 16:26 jleffler ttys005 Mar 27 16:26 :-( PID 29195 status 0x0000 :-( PID 29196 status 0x0000 (>^_^)> ls 29191: 1 args [ls] EOA pnum = 0 ploc[0] = 0 PID 29197 launched 29197: 1 args [ls] EOA 29197: ooo----------------- bash.getopts.update makefile pipeline.c pthread-1.c shuntzeroes.c timezeromoves.c cmpfltint.c mda.c pipeline.dSYM pthread-2.c so.14304827 uint128.c const-stuff.c mq-saurabh pipes-13905948.c pthread-3.c so.367309 uname.c dupdata.sql mqp-saurabh pipes-14312939.c quine.c so.6964747 unwrap.c fifocircle.c multi-pipe-sort.c pipes-15673333 ranges.sql so.6965001 xxx.sql idsdb00246324.ec multiopts.sh pipes-15673333.c recv.c so.8854855.sql yyy.sql incunabulum.c nextpipe.c pipes-15673333.dSYM regress.c strandsort.c madump.c pipeline powa.c send.c streplace.c :-( PID 29197 status 0x0000 (>^_^)> ls -C | wc 29191: 4 args [ls] [-C] [|] [wc] EOA pnum = 1 ploc[0] = 0 ploc[1] = 3 PID 29200 launched PID 29201 launched 29200: 2 args 29201: 1 args [ls] [wc] [-C] EOA EOA 29201: ooo----------------- 29200: ooo----------------- :-( PID 29200 status 0x0000 16 46 581 :-( PID 29201 status 0x0000 (>^_^)> bye v(^o^)^ BYE BYE! $