距离上次更新本文已经过去了 731 天,文章部分内容可能已经过时,请注意甄别

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

1. 函数作用

函数定义如下

c
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

plaintext
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

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

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

image-20230209081025661

errno 才是正确的

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

image-20230209081056203

2. 代码示例

2.1 popen 读

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

c
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;
}

编译后执行结果如下

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

2.2 popen 写

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

c
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));
}
plaintext
1
2
makefile  test  test.cpp
pclose: 0 | Success

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

c
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));
}

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

plaintext
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 的简单介绍到这里就结束了,欢迎评论区交流