使用 popen 代替 system 函数执行系统命令
使用 popen 代替 system 函数执行系统命令
在 c/c++ 编程中常常需要执行一些系统命令,可以使用 system 函数,也可以采用 popen 的方式来实现,本文介绍 popen 的使用方法及优势。
system vs popen
| 特性 | system | popen |
|---|---|---|
| 获取输出 | ❌ 无法获取 | ✅ 可实时读取 |
| 灵活性 | ❌ 简单粗暴 | ✅ 逐行处理 |
| 错误处理 | ❌ 复杂 | ✅ 精确状态码 |
| 效率 | ❌ 低(启动shell) | ✅ 高(直接通信) |
实现详解
commander.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#ifndef COMMANDER_H
#define COMMANDER_H
#include <stdio.h>
#include <sys/wait.h>
#include <string>
#include <sstream>
#include <functional>
class Commander {
public:
using Callback = std::function<void(const std::string &output)>;
public:
int execute(const std::string &command, const Callback &callback = nullptr) {
FILE *fp = nullptr;
/// 使用 popen 执行命令并获取输出
fp = popen(command.c_str(), "r");
if (!fp) {
return -1;
}
char buffer[4096];
std::stringstream output;
/// 读取命令输出,直到结束
while (nullptr != fgets(buffer, sizeof(buffer), fp)) {
output << buffer;
}
/// 调用回调函数处理输出
if (callback && !output.str().empty()) {
callback(output.str());
}
/// 关闭管道并获取命令执行状态
int status = pclose(fp);
fp = nullptr;
/// pclose 失败
if (-1 == status) {
return -2;
}
/// 命令未正常退出
if (!WIFEXITED(status)) {
return -3;
}
/// 返回命令退出状态码
return WEXITSTATUS(status);
}
};
#endif // COMMANDER_H
main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "commander.h"
#include <iostream>
int main() {
Commander commander;
int result = commander.execute("ls -l", [](const std::string &output) {
std::cout << output;
});
if (result < 0) {
std::cerr << "Command execution failed with code: " << result << std::endl;
}
return 0;
}
代码说明
核心步骤
| 步骤 | 说明 |
|---|---|
| popen | 打开命令的标准输出管道 |
| fgets | 逐行读取输出 |
| Callback | 处理输出结果 |
| pclose | 关闭管道获取状态码 |
| WIFEXITED/WEXITSTATUS | 校验和提取退出状态 |
注意事项
- 命令注入:避免直接拼接用户输入,使用白名单验证
- 缓冲区大小:根据命令输出调整
buffer大小,过小会影响性能 - 资源管理:
pclose必须被调用,建议使用 RAII 封装进一步完善
实战案例:获取 deb 包版本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "commander.h"
#include <iostream>
#include <regex>
int main() {
Commander commander;
std::string version;
// 查询 libssl3 包的版本
int result = commander.execute("dpkg -l | grep libssl3", [&](const std::string &output) {
// 解析输出格式: ii libssl3:amd64 3.0.8-1 amd64 Secure...
std::regex pattern(R"(libssl3[^\s]*\s+([^\s]+))");
std::smatch match;
if (std::regex_search(output, match, pattern)) {
version = match[1].str();
}
});
if (result == 0) {
std::cout << "libssl3 version: " << version << std::endl;
} else {
std::cerr << "Failed to get package version" << std::endl;
}
return 0;
}
或者使用更直接的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include "commander.h"
#include <iostream>
int main() {
Commander commander;
std::string version;
// 使用 dpkg-query 直接查询
int result = commander.execute(
"dpkg-query -W -f='${Version}' libssl3",
[&](const std::string &output) {
// output 就是版本号,如 "3.0.8-1"
version = output;
// 移除可能的换行符
if (!version.empty() && version.back() == '\n') {
version.pop_back();
}
}
);
if (result == 0 && !version.empty()) {
std::cout << "libssl3 version: " << version << std::endl;
} else {
std::cerr << "Package not found" << std::endl;
}
return 0;
}
常用的包查询命令
| 命令 | 说明 | 输出 |
|---|---|---|
dpkg -l \| grep <package> | 列出所有包信息 | 完整信息行 |
dpkg-query -W -f='${Version}' <package> | 仅查询版本 | 版本号 |
apt-cache policy <package> | 显示包的版本和源 | 可用/已安装版本 |
apt show <package> | 显示包详情 | 格式化信息 |
本文由作者按照 CC BY 4.0 进行授权