Как мне прочитать результаты системного вызова () в C ++? - PullRequest
1 голос
/ 21 ноября 2008

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

РЕДАКТИРОВАТЬ в оригинал: Хорошо, bufSize подходит к отрицательному значению, и теперь я понимаю, почему. Вы не можете получить произвольный доступ к каналу, как я наивно пытался сделать.

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

Ответы [ 9 ]

6 голосов
/ 24 ноября 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 точно так же, как any FILE *, и читаете его построчно.

5 голосов
/ 21 ноября 2008

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

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

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

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

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

3 голосов
/ 21 ноября 2008

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

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

  • Вы можете запустить процесс, используя pipe(), fork() и exec(). Это дает вам файловый дескриптор, затем вы можете использовать цикл для read() из файлового дескриптора в буфер и close() файловый дескриптор, как только вы закончите. Это опция самого низкого уровня и дает вам максимальный контроль.
  • Вы можете запустить процесс, используя popen(), как и вы. Это дает вам поток файлов. В цикле вы можете прочитать, используя поток из временной переменной или буфера, используя fread(), fgets() или fgetc(), как показывает ответ Zarawesome, затем обработать этот буфер или добавить его в строку C ++.
  • Вы можете запустить процесс, используя popen(), затем использовать нестандартный __ gnu_cxx :: stdio_filebuf , чтобы обернуть это, затем создать std::istream из stdio_filebuf и обработать его как любой другой поток C ++ , Это наиболее C ++ -подобный подход. Вот часть 1 и часть 2 примера такого подхода.
3 голосов
/ 21 ноября 2008

Я не уверен, что вы можете подобрать такие потоковые потоки, как этот.

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

1 голос
/ 24 ноября 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);
}
1 голос
/ 21 ноября 2008

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

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

Этого будет достаточно?

0 голосов
/ 21 ноября 2008

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

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

// your file opening code

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

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

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

0 голосов
/ 21 ноября 2008

Первое, что нужно проверить, это значение bufSize - если это <= 0, есть вероятность, что malloc возвращает NULL, когда вы пытаетесь выделить буфер размера 0 в этой точке. </p>

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

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

0 голосов
/ 21 ноября 2008

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...