Search code examples
csubprocessbufferingio-redirection

Why does program output redirection make the output of its sub-processes to be out of order?


In my program in C, running on Linux, which creates sub-processes using system() I noticed that when I redirected stdout to a pipe or to a file then the output of the sub-processes was sent before the output of buffered I/O functions like printf(). When stdout was left to go to the terminal then the output was in the expected order. I simplified the program to the following example:

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

int main(void)
{
    printf("1. output from printf()\n");
    system("echo '2. output from a command called using system()'");

    return EXIT_SUCCESS;
}

The expected output when stdout goes to the terminal:

$ ./iobuffer
1. output from printf()
2. output from a command called using system()

The output out of order when stdout is redirected to a pipe or a file:

$ ./iobuffer | cat
2. output from a command called using system()
1. output from printf()

Solution

  • A terminal generally uses line-buffering, while a pipe would use block buffering.

    This means that your printf call, which includes a newline, will fill the line buffer, triggering a flush. When redirecting, no flush takes place until the program completes.

    echo on the other hand, always flushes the buffer it is writing to when it completes.

    With line buffering (terminal output), the order is:

    • printf() prints a line with newline, buffer is flushed, you see 1. output from printf() being printed.
    • echo writes output, exits, flushes the buffer, you see 2. output from a command called using system() printed.

    With block buffering, the order is:

    • printf() prints a line with newline, not fully filling the buffer.
    • echo writes output, exits, flushes the buffer, you see 2. output from a command called using system() printed.
    • Your program exits, flushes its block buffer, you see 1. output from printf() being printed.

    Your options are to use to flush explicitly using fflush() or to set the buffering on stdout explicitly with setvbuf().