c语言popen介绍
1、函数定义
#include <stdio.h>
FILE * popen(const char *command , const char *type );
int pclose(FILE *stream);
2、函数说明
popen()函数通过创建一个管道,调用fork()产生一个子进程,执行一个shell以运行命令来开启一个进程。这个管道必须由pclose()函数关闭,而不是fclose()函数。pclose()函数关闭标准I/O流,等待命令执行结束,然后返回shell的终止状态。如果shell不能被执行,则pclose()返回的终止状态与shell已执行exit一样。
type参数只能是读或者写中的一种,得到的返回值(标准I/O流)也具有和type相应的只读或只写类型。如果type是"r"则文件指针连接到command的标准输出;如果type是"w"则文件指针连接到command的标准输入。
command参数是一个指向以NULL结束的shell命令字符串的指针。这行命令将被传到bin/sh并使用-c标志,shell将执行这个命令。
popen()的返回值是个标准I/O流,必须由pclose来终止。前面提到这个流是单向的(只能用于读或写)。向这个流写内容相当于写入该命令的标准输入,命令的标准输出和调用popen()的进程相同;与之相反的,从流中读数据相当于读取命令的标准输出,命令的标准输入和调用popen()的进程相同。
3、返回值
如果调用fork()或pipe()失败,或者不能分配内存将返回NULL,否则返回标准I/O流。popen()没有为内存分配失败设置errno值。如果调用fork()或pipe()时出现错误,errno被设为相应的错误类型。如果type参数不合法,errno将返回EINVAL。
4、参数说明:
command: 是一个指向以 NULL 结束的 shell 命令字符串的指针。这行命令将被传到 bin/sh 并使用 -c 标志,shell 将执行这个命令。
mode: 只能是读或者写中的一种,得到的返回值(标准 I/O 流)也具有和 type 相应的只读或只写类型。如果 type 是 “r” 则文件指针连接到 command 的标准输出;如果 type 是 “w” 则文件指针连接到 command 的标准输入。
5、作用:
popen函数允许一个程序将另外一个程序作为新进程来启动,并可以传递数据或者通过它接受数据。
其内部实现为调用 fork 产生一个子进程,执行一个 shell, 以运行命令来开启一个进程,这个进程必须由 pclose() 函数关闭。
6、缺点:
使用popen的不好影响是,针对每个popen调用,不仅要启动一个被请求的程序,还要启动一个shell,即每个popen调用将多启动两个进程。
例子1:
#include<stdio.h> #include<unistd.h> #include<string.h> int main() { FILE *fp=NULL; FILE *fh=NULL; char buff[128]={0}; memset(buff,0,sizeof(buff)); fp=popen("ls -l","r");//将命令ls-l 同过管道读到fp fh=fopen("shell.c","w+");// 创建一个可写的文件 fread(buff,1,127,fp);//将fp的数据流读到buff中 fwrite(buff,1,127,fh);//将buff的数据写入fh指向的文件中 pclose(fp); fclose(fh); return 0; }
[lol@localhost practice]$ ls popen popen.c shell.c [lol@localhost practice]$ cat shell.c total 12 -rwxrwxr-x. 1 lol lol 5478 May 24 15:39 popen -rw-rw-r--. 1 lol lol 473 May 24 15:39 popen.c -rw-rw-r--. 1 lol lol [lol@localhost practice]$ vim popen.c [lol@localhost practice]$
例子2:
//execute shell command//执行一个shell命令,输出结果逐行存储在resvec中,并返回行数 int32_t myexec(const char *cmd, vector<string> &resvec) { resvec.clear(); FILE *pp = popen(cmd, "r"); //建立管道 if (!pp) { return -1; } char tmp[1024]; //设置一个合适的长度,以存储每一行输出 while (fgets(tmp, sizeof(tmp), pp) != NULL) { if (tmp[strlen(tmp) - 1] == '\n') { tmp[strlen(tmp) - 1] = '\0'; //去除换行符 } resvec.push_back(tmp); } pclose(pp); //关闭管道 return resvec.size(); }
例子3: int main() { FILE *fpr = NULL, *fpw = NULL; char buf[256]; int ret; fpr = popen("cat /etc/group", "r"); //执行完这行代码,标准输出就装满,这里这个标准输出标记为out1,管道指向out1,fpr指向管道的读端 fpw = popen("grep root", "w"); //执行这句代码,会一直去读取标准输出,若标准输出为空,则会阻塞,直到标准输出不为空,执行命令后又 //会去指读取标准输出继续执行。这里这个标准输入标记为in2。 //管道指向int2,fpw指向管道的写端 while ((ret = fread(buf, 1, sizeof(buf), fpr)) > 0) //从out1中读取256个字节数据,存放在buf { fwrite(buf, 1, ret, fpw); //将buf的数据写到int2(此时gerp命令一直在获取int2,直到进行退出) } pclose(fpr); pclose(fpw); }
6、文件描述符
当某个进程打开文件时,操作系统返回相应的文件描述符,进程为了处理该文件必须引用此描述符。
所谓的文件描述符是一个低级的正整数。最前面的三个文件描述符(0,1,2)分别与标准输入(stdin),标准输出(stdout)和标准错误(stderr)对应。
因此,函数 scanf() 使用 stdin,而函数 printf() 使用 stdout。你可以用不同的文件描述符改写默认的设置并重定向进程的 I/O 到不同的文件。
popen函数的理解
(1) popen(comm, type)函数会创建一个管道,再fork一个子进程,在子进程中执行execX函数来执行comm命令(因为execX执行新程序后新程序的进程空间会覆盖原进程的进程空间,所以开一个子进程来执行execX家族函数),然后想要返回stdout或者stdin的文件指针(取决于type);
(2) 因为comm命令是通过子进程的执行的,那么stdin或者stdout文件指针也是子进程的进程片空间的,要将其返回给父进程,这就需要管道了;
(3) stdin是供程序写数据的,stdout是供程序读数据的。这里设计的巧妙之处在于,管道的读端跟stdout绑定,管道的写端跟stdin绑定;
(4) 读写管道操作的无非就是管道(文件)的fd(文件描述符),这里将fd封装到文件流指针fp中;
(5) popen返回的是stdout,那么type为”r”,表示创建一个管道且该管道文件的读端赋给fpr;
(6) popen返回的是stdin,那么type为”w”,表示创建一个管道且该管道文件的写端赋给fpw;
(7) 这样子,读fpr(管道的读端)等于读子进程的stdout,写fpw(管道的写端)等于写子进程的stdin。
参考:
https://www.jb51.net/article/140783.htm
https://blog.csdn.net/lihfqq/article/details/84687669
https://blog.csdn.net/hhhlizhao/article/details/71552588
https://blog.csdn.net/yellowston/article/details/90727424
https://blog.csdn.net/qq_29344757/article/details/70925123