popen
在linux下,不懂得可以查看帮助手册,我们先看看man手册怎么说。
可以看到,手册给出了两个函数原型,popen与pclose,其实我们很容易就能联想到另外一组函数,fopen,fclose。这两个函数是用来打开关闭文件的。所以其实我们已经可以猜到popen,pclose这两个函数的作用是什么了,p在linux下我们见到的地方大多数都与“管道”与关,所以可想而知,这两个函数实现的功能一定与管道有关。
下面给出该组函数的原型及解释:
FILE * popen( const char * command,const char * type);
函数功能:popen()会调用fork()产生子进程,然后从子进程中调用/bin/sh -c来执行参数command的指令。
参数type可使用“r”代表读取,“w”代表写入。
依照此type值,popen()会建立管道连到子进程的标准输出设备或标准输入设备,然后返回一个文件指针。
随后进程便可利用此文件指针来读取子进程的输出设备或是写入到子进程的标准输入设备中
返回值:若成功则返回文件指针,否则返回NULL,错误原因存于errno中
int pclose(FILE * stream);
函数功能:pclose()用来关闭由popen所建立的管道及文件指针。参数stream为先前由popen()所返回的文件指针
返回值:若成功返回shell的终止状态(也即子进程的终止状态),若出错返回-1,错误原因存于errno中
要注意的是:如果调用popen函数却不用pclose函数关闭它,子进程会变成僵尸进程。
举例说明
写一个简单的程序说明,首先创建一个txt文件往里面写入hello world。
读:
1 #include <stdio.h>
2 #include <stdlib.h>
3 int main()
4 {
5 char buf[1024]={0};
6 FILE* fp=popen("cat txt","r");
7 if(fp==NULL)
8 {
9 exit(1);
10 }
11 while(fgets(buf, 1024, fp) != NULL)
12 {
13 printf("%s\n",buf);
14 }
15 pclose(fp);
16
17 return 0;
18 }
执行结果:
写:
1 #include <stdio.h>
2 #include <stdlib.h>
3 int main()
4 {
5 char buf[1024]={0};
6 FILE* fp=popen("cat > txt1","w");
7 if(fp==NULL)
8 {
9 exit(1);
10 }
11 fwrite("hello world!", 1, 12, fp);
12 pclose(fp);
13
14 return 0;
15 }
ls多出一个txt1的文件,cat可以看到里面的内容就是我们要写入的内容。
system
同样我们先来查看以下帮助手册
函数说明
system()会调用fork()产生子进程,由子进程来调用/bin/sh-c string来执行参数string字符串所代表的命令,此命令执行完后随即返回原调用的进程。在调用system()期间SIGCHLD 信号会被暂时搁置,SIGINT和SIGQUIT 信号则会被忽略。
返回值
如果fork()失败 返回-1:出现错误
如果exec()失败,表示不能执行Shell,返回值相当于Shell执行了exit(127)
如果执行成功则返回子Shell的终止状态
如果system()在调用/bin/sh时失败则返回127,其他失败原因返回-1。若参数string为空指针(NULL),仅当命令处理程序可用时,返回非零值,可以通过这一特征判断在一个给定的操作系统上是否支持system函数(当system函数返回值为0时,表明system函数无效,在UNIX系统中,system函数总是可用的);。如果system()调用成功则最后会返回执行shell命令后的返回值,但是此返回值也有可能为 system()调用/bin/sh失败所返回的127,因此最好能再检查errno 来确认执行成功。
附加说明
在编写具有SUID/SGID权限的程序时请勿使用system(),system()会继承环境变量,通过环境变量可能会造成系统安全的问题。
为了更好的理解system()函数返回值,需要了解其执行过程,实际上system()函数执行了三步操作:
1.fork一个子进程;
2.在子进程中调用exec函数去执行command;
3.在父进程中调用wait去等待子进程结束。
7 int system(const char * command)
8 {
9 pid_t pid;
10 int status;
11 if(command == NULL)
12 {
13 return (1); //如果command为空,返回非零值,一般为1
14 }
15 if((pid = fork())<0)
16 {
17 status = -1; //fork失败,返回-1
18 }
19 else if(pid == 0)
20 {
21 execl("/bin/sh", "sh", "-c", command, (char *)0);
22 _exit(127); // exec执行失败返回127,注意exec只在失败时才返回现在的进程,成功的话现在的进程就不存在啦~~
23 }
24 else //父进程
25 {
26 while(waitpid(pid, &status, 0) < 0)
27 {
28 if(errno != EINTR)
29 {
30 status = -1; //如果waitpid被信号中断,则返回-1
31 break;
32 }
33 }
34 }
35 return status; //如果waitpid成功,则返回子进程的返回状态
36 }
37 int main()
38 {
39 system("pwd");
40 }
但是system函数返回值太多,而且返回值很容易和command的返回值混淆,所以,不推荐使用。
popen()函数较于system()函数的优势在于使用简单,popen()函数只返回两个值: 成功返回子进程的status,使用WIFEXITED相关宏就可以取得command的返回结果; 失败返回-1,我们可以使用perro()函数或strerror()函数得到有用的错误信息。
总结:
fork用来创建一个子进程。
vfork用来创建一个子进程,但必须等子进程退出时才执行自己。
popen也可以创建子进程,并实现进程shell与管道之间的通信。
system也可以创建子进程,system 可以看做是fork + execl + waitpid。