C语言popen如何获取stdout输出?

99ANYc3cd6
预计阅读时长 14 分钟
位置: 首页 C语言 正文

popen 是什么?

popen (pipe open) 是 C 标准库中的一个函数,它用于创建一个管道,从而调用一个 shell 命令,并可以与该命令进行通信。

c语言 popen stdout
(图片来源网络,侵删)

system() 函数不同,system() 只能执行命令并等待其结束,而无法获取命令的输出或输入。popen 则提供了一个单向的数据流:要么从子进程读取("r"模式),要么向子进程写入("w"模式)

函数原型

#include <stdio.h>
FILE *popen(const char *command, const char *type);
  • command: 一个字符串,代表你想要在 shell 中执行的命令。"ls -l""grep 'error' logfile.txt"

  • type: 一个字符串,指定了管道的打开模式:

    • "r" (read): 以只读模式打开管道。popen 会返回一个指向子进程标准输出 的文件指针,你可以用 fread, fgets, fscanf 等标准 I/O 函数来读取子命令的输出。
    • "w" (write): 以只写模式打开管道。popen 会返回一个指向子进程标准输入 的文件指针,你可以用 fwrite, fputs, fprintf 等标准 I/O 函数来向子命令输入数据。
  • 返回值:

    c语言 popen stdout
    (图片来源网络,侵删)
    • 成功时,返回一个与 FILE 关联的流指针,就像 fopen 一样。
    • 失败时,返回 NULL

如何获取子进程的标准输出(stdout

这是你最关心的问题,要获取子进程的标准输出,你需要使用 "r" 模式。

工作流程如下:

  1. 调用 popen: 使用 popen(command, "r") 启动一个子进程来执行你的命令,这个子进程的 stdout 会被重定向到一个管道。
  2. 获取文件指针: popen 返回一个 FILE* 指针,这个指针指向管道的“读”端。
  3. 读取数据: 使用标准 I/O 函数(如 fgets)从这个 FILE* 指针读取数据,这些数据实际上就是子进程命令的输出。
  4. 关闭管道: 使用 pclose() 函数关闭管道。pclose 会等待子进程执行完毕,并返回子进程的退出状态。这一点非常重要,popen 必须用 pclose 来关闭,而不是 fclose

代码示例:读取 ls -l 的输出

这是一个非常经典的例子,它演示了如何执行 ls -l 命令并逐行打印其输出。

#include <stdio.h>
#include <stdlib.h> // for exit()
int main() {
    FILE *fp;
    char path[1035]; // 用于存储命令输出的缓冲区
    // 1. 使用 "r" 模式执行命令,获取其 stdout
    // "ls -l" 是要执行的命令
    fp = popen("ls -l", "r");
    if (fp == NULL) {
        printf("Failed to run command\n");
        exit(1);
    }
    // 2. 从文件指针 fp 中逐行读取输出
    // fgets 会从管道中读取一行数据到 path 缓冲区
    printf("--- Output from 'ls -l' command ---\n");
    while (fgets(path, sizeof(path), fp) != NULL) {
        printf("%s", path); // 打印读取到的一行
    }
    // 3. 关闭管道,并获取子进程的退出状态
    int status = pclose(fp);
    if (status == -1) {
        // pclose 调用失败
        perror("pclose failed");
    } else {
        // 检查子进程的退出状态
        // WIFEXITED: 如果子进程正常终止,返回非零值
        // WEXITSTATUS: WIFEXITED 为真,返回子进程的退出码
        if (WIFEXITED(status)) {
            printf("\nCommand exited with status: %d\n", WEXITSTATUS(status));
        } else {
            printf("\nCommand did not exit normally.\n");
        }
    }
    return 0;
}

代码解释:

  1. fp = popen("ls -l", "r");

    • 系统会启动一个 shell(/bin/sh),执行 ls -l 命令。
    • ls -l 的标准输出不再显示在终端上,而是被重定向到一个管道。
    • popen 返回一个指向该管道读端的 FILE* 指针 fp
  2. while (fgets(path, sizeof(path), fp) != NULL)

    • fgets 是标准库函数,通常用于从文件读取一行,它被用来从管道 fp 中读取数据。
    • ls -l 命令产生输出时,fgets 就会从管道中读取这些数据。
    • ls -l 命令执行完毕并关闭其标准输出时,fgets 会返回 NULL,循环结束。
  3. pclose(fp);

    • 这个调用会做两件事:
      1. 关闭管道的文件指针 fp
      2. 等待 ls -l 这个子进程完全结束。
    • pclose 的返回值包含了子进程的退出状态,我们需要使用 WIFEXITEDWEXITSTATUS 这两个宏(来自 <sys/wait.h>)来解析它。

安全注意事项:popensystem 的安全漏洞

popensystem 一样,都存在一个严重的安全漏洞:命令注入

如果你将用户输入直接拼接到 command 字符串中,恶意用户可以插入自己的命令。

不安全的例子:

#include <stdio.h>
#include <string.h>
void list_files(const char *user_input) {
    char command[256];
    // 拼接命令,非常危险!
    sprintf(command, "ls -l %s", user_input);
    FILE *fp = popen(command, "r");
    // ... 处理输出 ...
    pclose(fp);
}
int main() {
    // 如果用户输入 "; rm -rf /",会发生什么?
    list_files("my_dir; rm -rf /");
    return 0;
}

在上面的例子中,user_input"; rm -rf /"popen 执行的命令变成了: ls -l my_dir; rm -rf /

这会先列出 my_dir 的内容,然后执行 rm -rf /,这是一个灾难性的命令。

如何避免?

最佳实践是永远不要将用户提供的、未经处理的字符串作为 popencommand 参数,如果必须执行动态命令,请使用 exec 系列函数(如 execlp, execvp)并手动处理参数列表,这样可以避免 shell 的解析和注入。

特性 popen(command, "r")
目的 执行 shell 命令并读取其标准输出。
工作方式 创建一个管道,子进程的 stdout 连接到管道的写端,父进程通过返回的 FILE* 指针从管道的读端读取数据。
核心函数 popen(), fgets(), pclose()
优点 简单易用,能无缝集成到标准 I/O 流中。
缺点 存在命令注入风险,需谨慎处理用户输入。
是一个同步操作,pclose 会阻塞父进程,直到子进程结束。
关闭方式 必须使用 pclose(fp),它会等待子进程结束并释放资源。

当你需要在 C 程序中捕获另一个命令的输出时,popen 是一个非常方便的工具,但请务必牢记其安全陷阱。

-- 展开阅读全文 --
头像
dede cfg_basehost是什么?如何配置使用?
« 上一篇 昨天
C语言printf输出aaa为何不换行?
下一篇 » 今天

相关文章

取消
微信二维码
支付宝二维码

目录[+]