Как мне прочитать результаты вызова system() в C++?

Я использую следующий код, чтобы попытаться прочитать результаты команды 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 и Справочник по C++, поэтому я не женат на нем. Если кто-нибудь может предложить лучший способ чтения результатов вызова system(), я открыт для новых идей.

ИЗМЕНИТЬ оригинал: Хорошо, bufSize становится отрицательным, и теперь я понимаю, почему. Вы не можете получить доступ к трубе случайным образом, как я наивно пытался сделать.

Я не могу быть первым, кто попытается это сделать. Может ли кто-нибудь привести (или указать мне) пример того, как читать результаты вызова system() в переменную на С++?


person Bill the Lizard    schedule 21.11.2008    source источник


Ответы (9)


Почему std::malloc() потерпит неудачу?

Очевидная причина: «потому что std::ftell() вернул отрицательное число со знаком, которое затем было обработано как огромное число без знака».

Согласно документации, std::ftell() возвращает -1 в случае ошибки. Одной из очевидных причин неудачи является то, что вы не можете искать в конвейере или FIFO.

Спасения нет; вы не можете узнать длину вывода команды, не прочитав его, и вы можете прочитать его только один раз. Вы должны читать его кусками, либо увеличивая буфер по мере необходимости, либо анализируя на лету.

Но, конечно, вы можете просто избежать всей этой проблемы, напрямую используя системный вызов, который df, вероятно, использует для получения информации: statvfs().

person CesarB    schedule 21.11.2008
comment
Спасибо, это хороший ответ. Теперь я понимаю, где я ошибся. Однако я использовал df только в качестве примера. Мне действительно нужно прочитать и проанализировать результаты нескольких различных системных вызовов. - person Bill the Lizard; 21.11.2008
comment
Принято, потому что в итоге я отказался от своего исходного решения и вместо этого использовал statvfs() для значительной части моей проблемы. Спасибо. :) - person Bill the Lizard; 05.12.2008

Ты слишком все усложняешь. popen(3) возвращает обычный старый FILE * для стандартного файла канала, то есть записи, заканчивающиеся новой строкой. Вы можете прочитать его с очень высокой эффективностью, используя fgets(3), как в C:

#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 * из popen точно так же, как любой FILE *, и читаете его построчно.

person Charlie Martin    schedule 24.11.2008
comment
эта строка ins(fileno(fp)) не работает в gcc 4.6.3. - person ahala; 01.10.2013

Я не уверен, что вы можете fseek/ftell использовать такие потоки каналов.

Вы проверили значение bufSize? Одна из причин, по которой malloc не работает, — буферы безумного размера.

person arul    schedule 21.11.2008
comment
Вы совершенно правы, я только что понял, что фтелл он делал не на открытый файл, а на пайп, что не разрешено. - person flolo; 21.11.2008
comment
Перематывать трубу, похоже, не имеет особого смысла! - person Martin York; 21.11.2008
comment
Вот что я получаю за попытку объединить два чужих фрагмента кода. :( - person Bill the Lizard; 21.11.2008

(Примечание к терминологии: «системный вызов» в Unix и Linux обычно относится к вызову функции ядра из кода пользовательского пространства. Ссылаться на него как на «результаты вызова system()» или «результаты вызова system(3)» было бы понятнее, но, вероятно, было бы лучше просто сказать «захват вывода процесса».)

В любом случае, вы можете прочитать вывод процесса так же, как вы можете прочитать любой другой файл. Конкретно:

  • Вы можете запустить процесс, используя pipe(), fork() и exec(). Это дает вам дескриптор файла, затем вы можете использовать цикл для read() из дескриптора файла в буфер и close() дескриптора файла, как только вы закончите. Это вариант самого низкого уровня, который дает вам максимальный контроль.
  • Вы можете запустить процесс, используя popen(), как вы это делаете. Это дает вам файловый поток. В цикле вы можете читать using из потока во временную переменную или буфер, используя fread(), fgets() или fgetc(), как показывает ответ Zarawesome, а затем обрабатывать этот буфер или добавлять его в строку C++.
  • Вы можете запустить процесс с помощью popen(), затем использовать нестандартный __gnu_cxx::stdio_filebuf, чтобы обернуть это, затем создать std::istream из stdio_filebuf и обрабатывать его как любой другой поток C++. Это наиболее похожий на C++ подход. Вот часть 1 и часть 2 примера такого подхода.
person Josh Kelley    schedule 21.11.2008

Спасибо всем, кто нашел время ответить. Коллега указал мне на класс 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);
}
person Bill the Lizard    schedule 24.11.2008

Чтобы ответить на вопрос в обновлении:

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

Будет ли этого достаточно?

person Community    schedule 21.11.2008
comment
Это довольно близко к тому, с чего я начал. Я изменил его, чтобы попытаться установить размер буфера именно так, как мне нужно. Вероятно, это тот случай, когда я слишком умна для своего же блага. - person Bill the Lizard; 23.11.2008
comment
Идиома buffer[1024] называется болезнью программиста на C и является причиной бесчисленного количества дыр в безопасности. Однако в простых случаях это очень удобно. - person ; 25.11.2008

Первое, что нужно проверить, это значение bufSize: если оно ‹= 0, есть вероятность, что malloc вернет NULL, когда вы пытаетесь выделить буфер размера 0 в этот момент.

Другим обходным решением было бы попросить malloc предоставить вам буфер размера (bufSize + n) с n >= 1, который должен решить эту конкретную проблему.

Кроме того, код, который вы разместили, является чистым C, а не C++, поэтому включение немного переусердствует.

person Timo Geusch    schedule 21.11.2008
comment
Спасибо. Я знаю, что большая часть этого кода написана на C, но я просто пытаюсь решить одну небольшую проблему, которая станет частью гораздо более крупного приложения на C++. Я не ограничен чистым C, поэтому мне интересно изучить любой способ сделать это на C++. - person Bill the Lizard; 21.11.2008

проверьте свой bufSize. ftell может возвращать -1 при ошибке, и это может привести к тому, что malloc не будет выделять память буферу, имеющему значение NULL.

Причина сбоя ftell в том, что файл popen. Вы не можете искать трубы.

person flolo    schedule 21.11.2008
comment
Вы правы, bufSize был -1. Удивительно (для меня), что вы можете сказать это, взглянув на код. - person Bill the Lizard; 21.11.2008

Пайпы не случайный доступ. Они последовательные, что означает, что как только вы прочитаете байт, канал больше не отправит его вам. Что означает, очевидно, что вы не можете перемотать его назад.

Если вы просто хотите вывести данные обратно пользователю, вы можете просто сделать что-то вроде:

// your file opening code

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

Это будет вытягивать байты из канала df один за другим и закачивать их прямо в вывод.

Теперь, если вы хотите получить доступ к выходным данным df в целом, вы можете либо передать их в файл и прочитать этот файл, либо объединить выходные данные в конструкцию, такую ​​как C++ String.

person zaratustra    schedule 21.11.2008
comment
Мне нужно проанализировать вывод команды df и некоторых других. Я думаю, что вывод его в строку C++ - это то, что мне нужно сделать. - person Bill the Lizard; 21.11.2008