我们在执行shell命令比如cat /etc/group | grep root的时候,通过管道的机制将cat /etc/group的结果传递给grep root,然后将结果显示出来
linux中提供了popen和pclose函数来达到这个目的。
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
函数popen先执行fork,然后调用exec以执行command,并且返回一个要么指向子进程的stdout,要么指向stdin的文件指针:若type是”r”,则文件指针是连接到子进程执行command命令的标准输出,如果type是”w”,则文件指针连接到子进程执行command命令的标准输入。那么前面shell命令cat /etc/group | grep root就可以通过下面的程序来实现:
void chapter_15_5_3()
{
FILE *fpr=NULL,*fpw=NULL;
char buf[256];
int ret;
fpr=popen("cat /etc/group","r");
fpw=popen("grep root","w");
while((ret=fread(buf,1,sizeof(buf),fpr)) > 0)
{
fwrite(buf,1,ret,fpw);
}
pclose(fpr);
pclose(fpw);
}
执行结果:
那么popen函数是如何工作的呢:
(1) popen(comm, type)函数会创建一个管道,类似与pipe函数创建管道。再fork一个子进程,在子进程中执行execX函数来执行comm命令(因为execX执行新程序后新程序的进程空间会覆盖原进程的进程空间,所以开一个子进程来执行execX家族函数),然后想要返回stdout或者stdin的文件指针(取决于type);
(2) 因为comm命令是通过子进程的执行的,那么stdin或者stdout文件指针也是子进程的进程片空间的,要将其返回给父进程,这就需要通过管道了;
(3) stdin是供程序写数据的,stdout是供程序读数据的。这里设计的巧妙之处在于,管道的读端跟stdout绑定,管道的写端跟stdin绑定;
(4) 读写管道操作的无非就是管道(文件)的fd(文件描述符),这里将fd封装到文件流指针fp中;
在上面的代码中
(1)popen首先创建一个管道,假设为fd。fd[0]指向管道的读端,fd[1]指向管道的写端
(5) popen的type为”r”,返回的是stdout,那么关闭fd[0], 执行shell命令,并写入fd[1]在父进程中关闭fd[1], 将fd[0]转换为一个文件描述符返回。表示创建一个管道且该管道文件的读端赋给fpr。通过fopen可以从fpr中读取数据存入buffer中
(6) popen的type为”w”,返回的是stdin,那么关闭fd[1], 在父进程中关闭fd[0], 将fd[1]转换为一个文件描述符返回表示创建一个管道且该管道文件的写端赋给fpw。通过fwrite()可以从fpw中
(7) 这样子,读fpr(管道的读端)等于读子进程的stdout,写fpw(管道的写端)等于写子进程的stdin。