【问题标题】:How do I read the results of a system() call in C++?如何在 C++ 中读取 system() 调用的结果?
【发布时间】:2010-09-23 11:53:32
【问题描述】:

我正在使用以下代码尝试在 Linux 中使用 popen 读取 df 命令的结果。

#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++ 中的变量的示例吗?

【问题讨论】:

  • '系统调用'有一个非常具体的含义 - 参见 en.wikipedia.org/wiki/System_call>。你试图做什么来捕获另一个程序的输出(我不确定这个的技术术语是什么)。
  • 谢谢。我编辑了问题以尝试澄清。

标签: c++ linux operating-system system-calls


【解决方案1】:

你让这一切变得太难了。 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
}

那里有更多的错误处理,但这就是想法。关键是您将 popen 中的 FILE * 视为 any FILE *,并逐行阅读。

【讨论】:

  • 这一行 ins(fileno(fp)) 在 gcc 4.6.3 下似乎不起作用。
【解决方案2】:

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

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

根据the documentationstd::ftell() 在失败时返回 -1。它失败的一个明显原因是你不能在管道或 FIFO 中寻找

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

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

【讨论】:

  • 谢谢,这是一个很好的答案。现在我明白我错在哪里了。不过,我只是以 df 为例。我真的需要读取和解析几个不同系统调用的结果。
  • 接受了,因为我最终放弃了我原来的解决方案,而是使用 statvfs() 来解决我的大部分问题。谢谢。 :)
【解决方案3】:

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

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

【讨论】:

  • 你完全正确,我刚刚意识到他不是在打开的文件上,而是在管道上,这是不允许的。
  • 倒带管似乎没有多大意义!
  • 这就是我尝试合并两段外国代码的结果。 :(
【解决方案4】:

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

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

  • 您可以使用pipe()fork()exec() 启动该进程。这为您提供了一个文件描述符,然后您可以使用循环将文件描述符中的read() 放入缓冲区,并在完成后使用close() 文件描述符。这是最低级别的选项,可为您提供最大的控制权。
  • 您可以使用popen() 启动该过程,就像您正在做的那样。这为您提供了文件流。在循环中,您可以使用 fread()fgets()fgetc() 从流中读取 using 到临时变量或缓冲区,正如 Zarawesome 的回答所演示的那样,然后处理该缓冲区或将其附加到 C++ 字符串。李>
  • 您可以使用popen() 启动该过程,然后使用非标准的__gnu_cxx::stdio_filebuf 包装它,然后从stdio_filebuf 创建一个std::istream 并将其视为任何其他C++ 流。这是最类似于 C++ 的方法。下面是这种方法的示例part 1part 2

【讨论】:

    【解决方案5】:

    感谢所有花时间回答的人。一位同事将我指向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);
    }
    

    【讨论】:

      【解决方案6】:

      回答更新中的问题:

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

      这就够了吗?

      【讨论】:

      • 这和我开始的很接近。我更改了它以尝试将缓冲区的大小设置为我需要的大小。这可能是我为了自己的利益而完全“聪明”的一个例子。
      • "buffer[1024]" 成语被称为“C 程序员疾病”,是无数安全漏洞的原因。不过在简单的情况下还是很方便的。
      【解决方案7】:

      首先要检查的是 bufSize 的值 - 如果它恰好

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

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

      【讨论】:

      • 谢谢。我知道大部分代码都是 C,但我只是想解决一个小问题,它将成为更大的 C++ 应用程序的一部分。我不限于纯 C,所以我有兴趣学习用 C++ 执行此操作的任何方法。
      【解决方案8】:

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

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

      【讨论】:

      • 你是对的,bufSize 是-1。 (对我来说)令人惊奇的是,您可以通过快速查看代码来判断这一点。
      【解决方案9】:

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

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

      // your file opening code
      
      while (!feof(fp))
      {
      char c = getc(fp);
      std::cout << c;
      }
      

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

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

      【讨论】:

      • 我需要解析 df 命令和其他一些命令的输出。我认为将它输出到 C++ 字符串是我需要做的。
      猜你喜欢
      • 1970-01-01
      • 2011-12-10
      • 2015-08-16
      • 1970-01-01
      • 1970-01-01
      • 2021-08-08
      • 2021-09-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多