Today I stumbled across an interesting indeterminism when using Linux pipes:
(echo red; echo green 1>&2) | echo blueMost of the time, this command outputs "blue green". But something like 1% of the time it outputs "green blue". And sometimes it outputs just "blue".
I am not sure why. Here is my working hypothesis why the output is usually "blue green":
1: A process 1 is started for the left side of the pipe. The "echo red" is executed. It is blocked and hangs, waiting for something on the other side to read from stdin.
2: A second process 2 is started for the right side of the pipe. The "echo blue" is executed and blue is written to the terminal.
3: Process 2 ends as "echo blue" finished. The shell lifts the write block for "echo red" and the next command "echo green 1>&2" is executed.
Here is a theory, why sometimes the output is just "blue":4: After process 2 ends, the shell sends a KILL signal to process 1. Sometimes this signal arrives before "green" is printed.
And why do we sometimes see "green blue"?
I have no good theory for that. Maybe there is some buffering involved. And "blue" gets written to the buffer of process 2. Then the write lock for process 1 is lifted and it writes "green" to the terminal. And after that the buffer of process 2 is written to the terminal.
But these are all just wild speculations.
Update 1: This spawned interesting discussions on Lobste.rs and Reddit.
Update 2: In fact, there seem to be buffers at play. And the result is that "echo red" does not block! echo seems to only block after some buffer has been filled. Here is a line to test this:
(i=1; while true; do echo red; echo $i 1>&2; ((i+=1)); done) | (while true; do echo blue; done)
It outputs the numbers from the left part of the pipe. And blue from the right part of the pipe. But only as long as the buffer is not full with all those "red" strings written to it. Then only the "blue" from the right part of the pipe keeps being printed.
This makes the random behaviour explainable. Both sides of the pipe are executed in their own processes that try to run the respective commands in full. So green and blue appear in random order. Green might never appear though. The reason is that after process 2 ends, the shell kills process 1. So if process 2 ends before process 1 managed to print green, process 1 will be killed and there will only be blue written to the terminal.
By the way, the pipe buffer seems to be 64k. This prints 16384 lines for me and then hangs:
(i=1; while true; do echo red; echo $i 1>&2; ((i+=1)); done) | sleep 10
I would assume that "red\n" has 4 chars. And 16384*4 equals 65536.
So in the end, it comes down to three basic aspects of how pipes work:
- Both sides run simultaneously in their own process.
- Due to buffering, writing to the pipe does not instantly block.
- When the right side of the pipe ends, the shell kills the left side.
Update 3: Chris Siebenmann wrote a blog article about this and Hacker News now also picked this up.