Home > Software engineering >  Why `getline` could acquire the output of the pipe as soon as possible, whereas `setvbuf` & `fread`
Why `getline` could acquire the output of the pipe as soon as possible, whereas `setvbuf` & `fread`

Time:06-01

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. I did a simple test on Ubuntu16.4, it does not make any difference indeed.

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

#include <stdio.h>
#include <string.h>

int main(void)
{
    char buffer[1024];
    memset(buffer, 0, 1024);
    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(NULL != file)
    {
        int size;
        printf("setvbuf returns %d\n", setvbuf(file, NULL, _IOLBF, 0)); 
        while((size = fread(buffer, 1, 1024, file))>0)
        {
            printf("size=%d\n", size);
            memset(buffer, 0, 1024);
        }
    }
    
    return 0;
}

Here is the output of the code snippet which runs on Ubuntu16.04:

setvbuf returns 0
//about five seconds later
size=65

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.

But when I tried getline, it could achieve the goal, which is really out of my expectation.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(void)
{
    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");
 
    char* line=NULL;
    size_t len;
    if(NULL != file)
    {
//      std::cout << setvbuf(file, NULL, _IOLBF, 0) << std::endl; //setvbuf has not been called
        while(getline(&line, &len, file)>0)
        {
            printf("strlen(line)=%lu\n", strlen(line));
            free(line);
            line = NULL;
        }
    }
    
    free(line);
    return 0;
}

Here is the output:

//one second later
strlen(line)=13
//one second later
strlen(line)=13
//one second later
strlen(line)=13
//one second later
strlen(line)=13
//one second later
strlen(line)=13

I am really conscious about why getline could acquire the output of the pipe as soon as possible, whereas setvbuf & fread does not work.

CodePudding user response:

getline stops reading once it gets to a newline. fread keeps reading until it reads as much data as you specified (in your case, 1024 bytes) or it encounters EOF or an error. This has nothing to do with buffering. You might want to look at read to see if it's closer to what you want.

CodePudding user response:

@Jeremy Friesner'answer:

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.

  • Related