Как получить небуферизованный вывод из popen

#linux #stream #popen #fgets #fcntl

#linux #поток #popen #fgets #fcntl

Вопрос:

Я использую popen для выполнения команды и чтения выходных данных. Я устанавливаю дескриптор файла в неблокирующий режим, чтобы я мог установить свой собственный тайм-аут следующим образом:

     auto stream = popen(cmd.c_str(), "r");

    int fd = fileno(stream);
    int flags = fcntl(fd, F_GETFL, 0);
    flags |= O_NONBLOCK;
    fcntl(fd, F_SETFL, flags);

    while(!feof(stream)) {
        if(fgets(buffer, MAX_BUF, stream) != NULL) {
            // do something with buffer...
        }
        sleep(10);
    }
    pclose(stream);

  

Это работает просто отлично, за исключением того, что fgets продолжает возвращать значение NULL, пока программа не завершит выполнение, после чего она возвращает весь вывод, как и ожидалось.

Другими словами, даже если программа немедленно выводит некоторый текст и новую строку в стандартный вывод, мой цикл не считывает его сразу; он видит его только позже.

В документации для popen я вижу:

Обратите внимание, что выходные потоки popen () по умолчанию буферизуются по блокам.

Я попробовал несколько способов отключить буферизацию (например setvbuf(stream, NULL, _IONBF, 0) ), но пока безуспешно.

Как мне отключить буферизацию, чтобы я мог читать вывод в режиме реального времени?

Спасибо!

Комментарии:

1. По сути, fgets() возвращает только тогда, когда встречает EOF или конец строки. Итак, если ваша программа отображает только одну строку (даже большую), fgets не будет возвращаться до EOF или первой новой СТРОКИ, даже если базовый дескриптор файла установлен НЕБЛОКИРУЮЩИМ.

2. Спасибо за ответ. Я только что отредактировал вопрос, чтобы прояснить, что первая строка, выводимая подпроцессом, включает новую строку. Тем не менее, fgets некоторое время не читает его…

3. Это условие гонки: программа вызывает fgets() перед тем, как команда отобразит свою первую строку. Итак, fgets() возвращает NULL в первый раз (если вы отобразите errno, когда fgets() возвращает NULL, вы получите EAGAIN). Итак, основные программы переходят в режим ожидания () перед чтением первой строки. Тем временем команда отображает весь свой вывод. и основные программы получают все после своего сна. Итак, когда я сказал выше, что fgets() не вернется, пока не встретит » n» или EOF, я был неправ. Он возвращается с EAGAIN.

4. Он просто спит в течение 10 мс… Он продолжает вызывать fgets до EOF…

5. Да, но перед сном отобразите сообщение об ошибке. Вы будете получать EAGAIN до тех пор, пока команда, наконец, не начнет отображать свой вывод. Кстати: sleep (10) = 10 секунд, а не 10 мс

Ответ №1:

Решение, основанное на чем-то вроде select(), было бы более точным и гибким. Попробуйте это :

     #include <stdio.h>
    #include <unistd.h>
    #include <errno.h>
    #include <sys/select.h>


    void read_cmd(const char *cmd)
    {
    FILE *stream;
    int fd;
    int flags;
    char buffer[1024];
    fd_set fdset;
    struct timeval timeout;
    int rc;
    int eof;

      stream = popen(cmd, "r");

      fd = fileno(stream);

      eof = 0;
      while(!eof) {

        timeout.tv_sec = 10; // 10 seconds
        timeout.tv_usec = 0;

        FD_ZERO(amp;fdset);
        FD_SET(fd, amp;fdset);

        rc = select(fd   1, amp;fdset, 0, 0, amp;timeout);

        switch(rc) {

          case -1: {
            // Error
            if (errno != EINTR) {
              fprintf(stderr, "select(): error '%m' (%d)n", errno);
            }
            return;
          }
          break;

          case 0: {
            // Timeout
            printf("Timeoutn");
          }
          break;

          case 1: {
            // Something to read
            rc = read(fd, buffer, sizeof(buffer) - 1);
            if (rc > 0) {
              buffer[rc] = '';
              printf("%s", buffer);
              fflush(stdout);
            }

            if (rc < 0) {
              fprintf(stderr, "read(): error '%m' (%d)n", errno);
              eof = 1;
            }

            if (0 == rc) {
              // End of file
              eof = 1;
            }

          }
          break;

        } // End switch

      } // End while

      pclose(stream);

    }

    int main(int ac, char *av[])
    {

      read_cmd(av[1]);

      return 0;

    } // main