本篇文章简单讲述了c语言接口popen/pclose的用法

1.函数作用

函数定义如下

1
2
3
4
#include <stdio.h>

FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);

popen函数会创建一个管道,fork一个子进程,关闭管道的不使用端,通过exec函数执行一个shell命令,等待命令终止。并将命令的输出结果通过管道返回给当前的进程;

1.1 popen

popen函数会创建一个管道,fork后调用shell来打开进程。由于管道的定义是单向的,第二个参数type只能指定读或写,不能同时指定读和写;

所得到的管道相应地是只读或只写的

   The  popen()  function opens a process by creating a pipe, forking, and invoking the shell.  Since a pipe is by definition unidirectional, the type argument may specify only reading or writing, not both; the resulting stream is correspondingly read-only or write-only.

简单说来,popen会自动帮我们fork创建子进程和一个管道,将我们传入的参数command在shell中执行后,将返回值以管道文件的形式发送给调用方。

如果调用fork或pipe失败,或者不能分配内存,将返回NULL;否则返回标准I/O流。popen()没有为内存分配失败设置errno值。如果调用fork()或pipe()时出现错误,errno被设为相应的错误类型。如果type参数不合法,errno将返回EINVAL

1.2 pclose

这个函数没有什么好说的,是用来关掉popen打开的文件的(即popen的返回值)

但是,它的返回值可有说到了,其返回值就是我们终端操作的退出状态,以标识命令是否成功执行

但是这个返回是通过wait4得到的,如果wait4出现错误,就会返回-1并设置errno

1
The pclose() function returns -1 if wait4(2) returns an error, or some other error is detected.  In the event of an error, these functions set errnro to indicate the cause of the error.

这里还出现了一个新的错误码errnro,但是经过我的一番百度,没有发现任何地方有对errnro的说明,man手册下方又变成了errno

1
If pclose() cannot obtain the child status, errno is set to ECHILD.

而且编译执行的时候,errnro是会报错的,所以姑且认为这里是man手册出错了!

image-20230209081025661

errno才是正确的

1
2
int ret = pclose(f);
printf("pclose: %d | %s\n",ret,strerror(errno));

image-20230209081056203

2.代码示例

2.1 popen读

以下方最简单的ls命令为例,我们以读方式打开,popen就会将ls的执行结果写到文件里面返回给我们

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

int main()
{
FILE *f;
char readBuf[1024] = {0};

f = popen("ls","r");
fread(readBuf,1024,1,f);

printf("%s\n",readBuf);
pclose(f);
return 0;
}

编译后执行结果如下

1
2
3
4
$ ./test
test
test.cpp

2.2 popen写

和读不同,如果用写方式执行popen,命令的输出结果会直接打印到屏幕上

1
2
3
4
5
6
void test2(FILE* f)
{
f = popen("ls","w");
int ret = pclose(f);
printf("pclose: %d | %s\n",ret,strerror(errno));
}
1
2
makefile  test  test.cpp
pclose: 0 | Success

这里我还以为后续可以接着往管道里面写数据,让他继续执行命令

1
2
3
4
5
6
7
8
9
10
11
12
void test2(FILE* f)
{
f = popen("ls","w");

// 缓冲区中写入数据
strcpy(readBuf,"ls -l");
printf("cmd: %s\n",readBuf);
// 写道管道文件中
fwrite(readBuf,1024,1,f);
int ret = pclose(f);
printf("pclose: %d | %s\n",ret,strerror(errno));
}

可测试的结果告诉我,并不行

1
2
3
cmd: ls -l
makefile test test.cpp
pclose: 0 | Success

网上的资料大多数都是只演示了r方法,我不确定这里是不是我写的代码有问题,还是说本来就是这样的。暂且停留在这里吧!

3.优缺点

优点是:由于所有类Unix系统中参数扩展都是由shell完成的,所有它运行我们通过popen完成非常复杂的shell命令。而其他一些创建进程的函数(如execl)调用起来就复杂的多,因为调用进程必须自己完成shell扩展。

缺点是:针对每个popen调用,不仅要启动一个被请求的程序,还要启动一个shell,即每个popen调用将启动两个进程。从节省系统资源的角度来看,popen函数的调用成本略高,并且对目标命令的调用比正常方式慢一些(通过pipe改进)。

end

关于popen和pclose的简单介绍到这里就结束了,欢迎评论区交流