shell管道重定向实现

本文转自:http://edsionte.com/techblog/archives/1194

如果你实现过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);
}