如果你实现过my_shell.c,那么对管道重定向应该有印象。但是本文中所述的管道重定向,将采用最近我们学习的管道相关系统调用函数来实现。随后,也将和大家一起再去回顾当初my_shell.c中是如何实现管道重定向的
对于管道符号,这里只做简单的说明:管道符前命令的输出作为管道符后命令的输入。对于一般命令而言,输入均来自标准输入,而输出则至标准输出。但是为了实现管道重定向,我们先创建管道,然后将管道写端重定向到标准输出,将管道读端重定向到标准输入。当然这两次重定向分别在不同的进程中完成。具体代码实现可参考下面的代码片段。
if(pipe(fd)==-1) { printf("my_pipe:can't create a pipe\n"); exit(1); } pid=fork(); if(pid==0) { close(fd[0]); dup2(fd[1],STDOUT_FILENO); close(fd[1]); if(execlp(argv[1],argv[1],NULL)==0) { printf("my_pipe:can't execute the first command in the child process\n"); exit(1); } } else if(pid>\0) { close(fd[1]); dup2(fd[0],STDIN_FILENO); close(fd[0]); if(execlp(argv[2],argv[2],NULL)==0) { printf("my_pipe:can't execute the second command in the parent process\n"); exit(1); } } else { printf("my_pipe:can't create a new process\n"); exit(1); }
上述代码所实现的管道重定向只支持没有参数的命令,比如./my_pipe ls wc(相当于命令ls | wc)。如果想要使用命令参数,可以在次基础上继续完善。
可以发现,管道是作为前后两个命令的数据缓冲区,各命令的相关输入或输出只是“私下”操作。既然理解了上述代码,那么my_shell.c中的管道的实现代码也就一目了然了。
首先,创建一个子进程,然后在子进程中创建子子进程(pid2=fork())。其实无非是让这两个子进程去分别执行管道符前后的命令。这里应该注意的是此段代码是如何“模拟”管道的。我们可以发现,其实并没有真正的pipe一个管道,而是创建了一个实实在在tempfile文件而已,最后使用完毕再悄悄删除次文件。
再看具体是如何重定向的,在子子进程中,以写方式打开”管道”,将tempfile定向到标准输出。在子进程中,以读方式打开”管道”,将tempfile定向到标准输入。
看到这里,你应该已经发现,这里所用到的原理其实和一开始我们所述的方法是一致的。
if(pid==0)//child_process { int pid2; int status2; int fd2; if((pid2=fork())<\0) { printf("fork2 error\n"); return; } else if(pid2==0)//child_child_process { if(!(find_command(arg[0]))) { printf("%s: command not found\n",arg[0]); exit(0); } fd2=open("/tmp/tempfile",O_WRONLY|O_CREAT|O_TRUNC,0644); dup2(fd2,1); execvp(arg[0],arg); exit(0); } if(waitpid(pid2,&status2,0)==-1)//waiting for child_child_process { printf("wait for child_child process error\n"); } if(!(find_command(argnext[0]))) { printf("%s:command not found\n",argnext[0]); exit(0); } fd2=open("/tmp/tempfile",O_RDONLY); dup2(fd2,0); execvp(argnext[0],argnext); if(remove("/tmp/tempfile")) { printf("remove error\n"); } exit(0); }