Firstly, behavior of pipes on the command line (ie: the effect of the character '|' as opposed to the pipe(2) call) is the behavior of the shell feature rather than the OS.
Secondly, if I'm not mistaken the behavior doesn't have anything to do with the pipe. It is the scheduling of the subshell (created by () ) which might explain what you're seeing. IOW, when you create the pipeline you are executing "echo blue" in the same shell and "(echo red; echo green)" in a different (child) shell. Execution of these are independent. Since "echo blue" doesn't have to wait for I/O on the pipeline, it runs and exits.
Thirdly, your 1>&2 is perhaps misplaced, ie: if you were expecting red also to be printed you probably want (echo red; echo green) 1>&2 ...
Fourtly, by 1>&2 you are redirecting stdout to stderrr, which in a pipeline implies whatever is received first goes to the parent shell's stderr not to the pipeline (if that was your intention).
IOW, the entire this is the same as executing
/bin/bash -c "echo red; echo green >& 2" >/dev/null ; echo blue
I'd expect the result to be no different
[1] I am aware that bash has echo as a builtin, as opposed to /bin/echo -- which would results in an exec(3), possibly other shells do as well.
I did not expect that. I expected the output to always be "blue green".
The surprising thing here is that about 1% of the time, the command outputs something else. It seems to be some race condition that happens in the handling of the pipeline.
I have written out my theories on the expected and observed behaviour on the page now. I also added a link to a long discussion this spawned on lobste.rs which is pretty interesting.
* There is no blocking on buffers involved. The 2 ends of the pipe are simultaneously run. One in a subshell and one in the current shell.
* If the one in the current shell (right side of the pipe) is executed and finished first, blue is sent to the stderr (ie: the console), a SIGPIPE is sent to the subshell process (thus ensuring red is never printed) but if SIGPIPE is ack'd after echo green is sent to the stderr, you also see green, else you see only blue
* If the one in the subshell is executed first then red is sent to the stdout of the subshell (which is the pipe, but nobody is reading the pipe, so it just lies in there and eventually is lost, since the other end of the pipe will eventually be closed without any read()), green is sent to the stderr and then blue.
I feel you believe there subshell process is blocked because nothing is reading from the other side. This is incorrect. The pipe buffer will be written to until the "pipe size" (try ulimit -a or ulimit -p). The write end will not be blocked until then.
Some reading:
https://www.gnu.org/software/bash/manual/html_node/Command-Grouping.html#Command-Grouping
https://www.gnu.org/software/bash/manual/html_node/Command-Execution-Environment.html#Command-Execution-Environment
http://man7.org/linux/man-pages/man7/pipe.7.html (see I/O on pipes)
I see you've now updated the post and got the idea. Small nitpick tho'.
> When the right side of the pipe ends, the shell kills the left side.
That's technically incorrect. The shell sends SIGPIPE to the subshell. SIGPIPE can be trapped/ignored (man trap) and although the default disposition is to kill the process that receives the signal, this might not be always true.
For instance, what do you suppose would happen with this:
6 comments
[ 4.0 ms ] story [ 22.2 ms ] threadSecondly, if I'm not mistaken the behavior doesn't have anything to do with the pipe. It is the scheduling of the subshell (created by () ) which might explain what you're seeing. IOW, when you create the pipeline you are executing "echo blue" in the same shell and "(echo red; echo green)" in a different (child) shell. Execution of these are independent. Since "echo blue" doesn't have to wait for I/O on the pipeline, it runs and exits.
Thirdly, your 1>&2 is perhaps misplaced, ie: if you were expecting red also to be printed you probably want (echo red; echo green) 1>&2 ...
Fourtly, by 1>&2 you are redirecting stdout to stderrr, which in a pipeline implies whatever is received first goes to the parent shell's stderr not to the pipeline (if that was your intention).
IOW, the entire this is the same as executing
/bin/bash -c "echo red; echo green >& 2" >/dev/null ; echo blue
I'd expect the result to be no different
[1] I am aware that bash has echo as a builtin, as opposed to /bin/echo -- which would results in an exec(3), possibly other shells do as well.
The surprising thing here is that about 1% of the time, the command outputs something else. It seems to be some race condition that happens in the handling of the pipeline.
Why do you expect that ? What ought to prevent the execution of the echo in the subshell in front of the pipe ?
> It seems to be some race condition that happens in the handling of the pipeline.
What exactly is the pipeline achieving in your opinion ?
Peter's post on lobste.rs (https://lobste.rs/s/mb14bg/what_should_be_output_this_comman... ) is far more comprehensive and clear imo, than any of the other explanations (including mine), but the idea is essentially the same.
* There is no blocking on buffers involved. The 2 ends of the pipe are simultaneously run. One in a subshell and one in the current shell.
* If the one in the current shell (right side of the pipe) is executed and finished first, blue is sent to the stderr (ie: the console), a SIGPIPE is sent to the subshell process (thus ensuring red is never printed) but if SIGPIPE is ack'd after echo green is sent to the stderr, you also see green, else you see only blue
* If the one in the subshell is executed first then red is sent to the stdout of the subshell (which is the pipe, but nobody is reading the pipe, so it just lies in there and eventually is lost, since the other end of the pipe will eventually be closed without any read()), green is sent to the stderr and then blue.
I feel you believe there subshell process is blocked because nothing is reading from the other side. This is incorrect. The pipe buffer will be written to until the "pipe size" (try ulimit -a or ulimit -p). The write end will not be blocked until then.
Some reading:
> When the right side of the pipe ends, the shell kills the left side.
That's technically incorrect. The shell sends SIGPIPE to the subshell. SIGPIPE can be trapped/ignored (man trap) and although the default disposition is to kill the process that receives the signal, this might not be always true.
For instance, what do you suppose would happen with this:
versus: in a loop ?