0

As per the document, which says that[emphasis mine]:

FILE *popen(const char *command, const char *type);

Note that output popen() streams are block buffered by default.

I hope to see the output of the command executed by popen as soon as possible.So I change the buffering type of the file stream returned by popen to line buffered. As per the document, setvbuf seems work for this goal. But after I did a simple test on Ubuntu16.4, setvbuf does not work indeed.

Here is the code snippet which I used to do the said test:

#include <stdio.h>
#include <thread>
#include <chrono>
#include <iostream>
#include <array>

int main(void)
{
    std::array<char, 1024> buffer;
    buffer.fill('\0');
    FILE* file = popen("bash -c \"for i in 1 2 3 4 5;do echo -e -n 'thanks a lot\n'; sleep 1; done\" ", "r");

    if(nullptr != file)
    {
        std::cout << setvbuf(file, NULL, _IOLBF, 0) << std::endl;
        while(fread(buffer.data(), 1, buffer.size(), file)>0)
        {
            std::cout << buffer.data() << std::endl;
            buffer.fill('\0');
        }
    }
    
    return 0;
}

Here is the output of the code snippet above:

0
//about five seconds later!
thanks a lot
thanks a lot
thanks a lot
thanks a lot
thanks a lot

As per the document, which says that:

The function setvbuf() returns 0 on success.

As per the output above, setvbuf(file, NULL, _IOLBF, 0) has successfully set the buffing type of file returned by popen to line buffered.But the output of the aforementioned code snippet indicates it still uses the default block buffered.

I am really conscious why it works this way.

I did an extra test about setvbuf to try to make it clear. As per the document, which says that:

If a stream refers to a terminal (as stdout normally does) it is line buffered. The standard error stream stderr is always unbuffered by default.

So I modify the buffing type of stdout by setvbuf(stdout, NULL, _IOFBF, 0);. Here is the full code snippet:

#include <stdio.h>
#include <thread>
#include <chrono>

int
main(void)
{
    char buf[BUFSIZ];
    setvbuf(stdout, NULL, _IOFBF, 0);
    for(int i=0; i<10; i++)
    {
        printf("Hello, world!\n");
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }
    return 0;
}

Here is the ouput:

//about ten seconds later
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!

The output is printed out to the terminal after about ten seconds, which is in the expectation.

John
  • 2,963
  • 11
  • 33
  • In my experience, the call to `setvbuf(stdout, NULL, _IONBF, 0)` needs to be executed inside the child process in order to get the behavior you want. (Of course that's much easier to achive if the child process's executable is one you control rather than /bin/bash). `popen()` in the parent process can't give you data that the child process hasn't supplied to it yet. – Jeremy Friesner May 30 '22 at 03:37
  • Since the subprocess is a shell using discrete `echo`s, that's not a concern. That said, `setvbuf` is useless here since `popen` is in read mode. The actual problem here (tested via `strace`) is that somehow `cout << endl` isn't flushing in the main process. – o11c May 30 '22 at 03:48
  • Try using `fgets` (or better `getline`) instead of `fread`. – o11c May 30 '22 at 03:57
  • @o11c Any reason for why `fgets` or `getline` is better? Why `getline` works other than `fread` & `setvbuf`? – John May 30 '22 at 04:52
  • @JeremyFriesner I thought and thought. I finally think you are right. How the commands(e.g. `top`) invoked by `/bin/bash` flushes their output to the pipe is out of my control. It seems there is no way to achieve this goal(i.e. see the output of the command as soon as possible). Am I right? – John Jun 01 '22 at 09:01
  • @John have a look here for some ideas: https://stackoverflow.com/questions/3465619/how-to-make-output-of-any-shell-command-unbuffered – Jeremy Friesner Jun 01 '22 at 10:57

0 Answers0