popen 函数用于创建一个管道来与一个进程通信。这个函数允许你在 C 程序中执行一个 shell 命令,并根据指定的模式读取命令的输出或向命令发送输入。
语法
FILE *popen(const char *command, const char *type);
- command: 要在 shell 中执行的命令。
- type: 模式,可以是:
"r": 读取命令的输出。
"w": 向命令写入输入。
返回值
返回一个 FILE * 指针,可以用标准文件 I/O 函数(如 fgets、fprintf 等)来操作。
如果操作失败,返回 NULL。
工作原理
内部实现: popen 的内部操作包括:
- 创建一个管道(单向通信通道)。
- 通过 fork 创建一个子进程。
- 子进程执行指定的 shell 命令。
- 父进程返回一个连接到管道的 FILE * 指针。
应用场景
1. 捕获命令输出
你可以使用 popen 执行一个 shell 命令,并直接在你的 C 程序中捕获其输出。
示例: 列出当前目录中的文件。
#include <stdio.h>
int main() {
FILE *pipe = popen("ls -l", "r");
char buffer[128];
if (pipe == NULL) {
perror("popen 失败");
return 1;
}
while (fgets(buffer, sizeof(buffer), pipe) != NULL) {
printf("%s", buffer);
}
pclose(pipe);
return 0;
}
使用注意事项
资源管理: 始终使用 pclose 关闭由 popen 打开的管道,以避免资源泄漏。
安全性: 对于 command 字符串,特别是包含用户输入的情况,要小心 shell 注入漏洞。
一个实例介绍(部分代码):
snprintf(cmd, sizeof(cmd), "sudo lsusb -d %x:%x", MCU_VENDOR_ID, MCU_PRODUCT_ID);
FILE *lsusb_pipe = popen(cmd, "r");
if (lsusb_pipe == NULL)
{
rc = -1;
goto Exit;
}
while (fgets(line, sizeof(line), lsusb_pipe) != NULL && index < MAX_DEVICES)
{
char bus[10], device[10];
if (sscanf(line, "Bus %s Device %s", bus, device) == 2)
{
sprintf(devices[index], "/dev/bus/usb/%s/%s", bus, device);
index++;
}
}
构建的命令是 sudo lsusb -d <MCU_VENDOR_ID>:<MCU_PRODUCT_ID>,用于根据指定的厂商和产品 ID 过滤 USB 设备。
- 代码使用 fgets 函数逐行读取 lsusb 命令的输出。
每行内容都会检查是否符合预期格式("Bus <bus> Device <device>"),如果符合,则使用 sscanf 提取出总线和设备编号。
- 提取出的总线和设备编号随后被用来构建 USB 设备文件的路径,并存储在 devices 数组中。