3

我正在使用以下代码尝试df在 Linux 中使用popen.

#include <iostream> // file and std I/O functions

int main(int argc, char** argv) {
    FILE* fp;
    char * buffer;
    long bufSize;
    size_t ret_code;

    fp = popen("df", "r");
    if(fp == NULL) { // head off errors reading the results
        std::cerr << "Could not execute command: df" << std::endl;
        exit(1);
    }

    // get the size of the results
    fseek(fp, 0, SEEK_END);
    bufSize = ftell(fp);
    rewind(fp);

    // allocate the memory to contain the results
    buffer = (char*)malloc( sizeof(char) * bufSize );
    if(buffer == NULL) {
        std::cerr << "Memory error." << std::endl;
        exit(2);
    }

    // read the results into the buffer
    ret_code = fread(buffer, 1, sizeof(buffer), fp);
    if(ret_code != bufSize) {
        std::cerr << "Error reading output." << std::endl;
        exit(3);
    }

    // print the results
    std::cout << buffer << std::endl;

    // clean up
    pclose(fp);
    free(buffer);
    return (EXIT_SUCCESS);
}

这段代码给了我一个退出状态为“2”的“内存错误”,所以我可以看到失败的地方,我只是不明白为什么

我从Ubuntu ForumsC++ Reference上找到的示例代码把它放在一起,所以我没有嫁给它。如果有人可以提出更好的方法来读取 system() 调用的结果,我愿意接受新的想法。

编辑原文:好的,bufSize结果是否定的,现在我明白为什么了。您不能像我天真地尝试那样随机访问管道。

我不能成为第一个尝试这样做的人。有人可以给(或指向我)一个示例,说明如何将 system() 调用的结果读取到 C++ 中的变量中吗?

4

9 回答 9

7

你让这一切变得太难了。 popen(3)返回标准管道文件的常规旧FILE *文件,也就是说,换行符终止的记录。在 C 语言中使用fgets(3)可以非常高效地阅读它:

#include <stdio.h>
char bfr[BUFSIZ] ;
FILE * fp;
// ...
if((fp=popen("/bin/df", "r")) ==NULL) {
   // error processing and return
}
// ...
while(fgets(bfr,BUFSIZ,fp) != NULL){
   // process a line
}

在 C++ 中它更容易——

#include <cstdio>
#include <iostream>
#include <string>

FILE * fp ;

if((fp= popen("/bin/df","r")) == NULL) {
    // error processing and exit
}

ifstream ins(fileno(fp)); // ifstream ctor using a file descriptor

string s;
while (! ins.eof()){
    getline(ins,s);
    // do something
}

那里还有一些错误处理,但这就是想法。关键是您将FILE *from popen视为any FILE *,并逐行阅读。

于 2008-11-24T04:03:22.760 回答
5

为什么会std::malloc()失败?

显而易见的原因是“因为std::ftell()返回了一个负符号数,然后将其视为一个巨大的无符号数”。

根据文档std::ftell()失败时返回 -1。它失败的一个明显原因是您无法在管道或 FIFO 中查找

跑不了的; 不读就无法知道命令输出的长度,只能读一次。您必须分块读取它,根据需要增加缓冲区或动态解析。

df但是,当然,您可以通过直接使用系统调用可能用于获取其信息来简单地避免整个问题: statvfs().

于 2008-11-21T17:17:55.843 回答
4

(关于术语的注释:Unix和Linux中的“系统调用”通常是指从用户空间代码调用内核函数。将其称为“system()调用结果”或“system(3)调用结果”会更清楚,但最好只说“捕获进程的输出”。)

无论如何,您可以像读取任何其他文件一样读取进程的输出。具体来说:

  • 您可以使用pipe()fork()和开始该过程exec()。这为您提供了一个文件描述符,然后您可以使用循环read()从文件描述符到缓冲区和close()完成后的文件描述符。这是最低级别的选项,为您提供最大的控制权。
  • 您可以使用 开始该过程popen(),就像您正在做的那样。这为您提供了文件流。在循环中,您可以使用 、或将流中的 using 读取到临时变量或缓冲区中fread(),正如 Zarawesome 的回答所演示的那样,然后处理该缓冲区或将其附加到 C++ 字符串。fgets()fgetc()
  • 您可以使用 开始该过程popen(),然后使用非标准__gnu_cxx::stdio_filebufstd::istream来包装它,然后从 中创建一个stdio_filebuf并将其视为任何其他 C++ 流。这是最类似于 C++ 的方法。这是此方法示例的第 1 部分第 2 部分。
于 2008-11-21T19:20:53.380 回答
3

我不确定您是否可以像这样 fseek/ftell 管道流。

你检查过 bufSize 的值吗?malloc 失败的原因之一是缓冲区大小异常。

于 2008-11-21T17:14:34.943 回答
2

感谢所有花时间回答的人。一位同事向我介绍了ostringstream课程。这是一些示例代码,它们基本上完成了我在原始问题中尝试做的事情。

#include <iostream> // cout
#include <sstream> // ostringstream

int main(int argc, char** argv) {
    FILE* stream = popen( "df", "r" );
    std::ostringstream output;

    while( !feof( stream ) && !ferror( stream ))
    {
        char buf[128];
        int bytesRead = fread( buf, 1, 128, stream );
        output.write( buf, bytesRead );
    }
    std::string result = output.str();
    std::cout << "<RESULT>" << std::endl << result << "</RESULT>" << std::endl;
    return (0);
}
于 2008-11-24T01:43:33.620 回答
1

要回答更新中的问题:

char buffer[1024];
char * line = NULL;
while ((line = fgets(buffer, sizeof buffer, fp)) != NULL) {
    // parse one line of df's output here.
}

这够了吗?

于 2008-11-21T19:35:51.133 回答
0

检查你的 bufSize。ftell可以在错误时返回 -1,这可能导致 malloc 不分配缓冲区具有 NULL 值。

ftell失败的原因是因为popen。你不能搜索管道。

于 2008-11-21T17:13:03.450 回答
0

首先要检查的是 bufSize 的值 - 如果它恰好 <= 0,那么当您尝试分配大小为 0 的缓冲区时,malloc 可能会返回 NULL。

另一种解决方法是要求 malloc 为您提供大小为 (bufSize + n) 且 n >= 1 的缓冲区,这应该可以解决这个特定问题。

除此之外,您发布的代码是纯 C,而不是 C++,所以包含有点过头了。

于 2008-11-21T17:14:30.653 回答
0

管道不是随机访问的。它们是连续的,这意味着一旦您读取了一个字节,管道就不会再次将其发送给您。这意味着,显然,你不能倒带。

如果您只想将数据输出回用户,您可以执行以下操作:

// your file opening code

while (!feof(fp))
{
char c = getc(fp);
std::cout << c;
}

这将一个接一个地从 df 管道中提取字节,并将它们直接泵入输出。

现在,如果您想作为一个整体访问 df 输出,您可以将其通过管道传输到一个文件中并读取该文件,或者将输出连接到一个构造中,例如 C++ 字符串。

于 2008-11-21T17:43:43.403 回答