文章

使用 popen 代替 system 函数执行系统命令

使用 popen 代替 system 函数执行系统命令

在 c/c++ 编程中常常需要执行一些系统命令,可以使用 system 函数,也可以采用 popen 的方式来实现,本文介绍 popen 的使用方法及优势。

system vs popen

特性systempopen
获取输出❌ 无法获取✅ 可实时读取
灵活性❌ 简单粗暴✅ 逐行处理
错误处理❌ 复杂✅ 精确状态码
效率❌ 低(启动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 进行授权