Pipe Fail (141) when piping output into tee -- why?
I think I’ve figured out how to tweak your experience to turn it into something other people will be able to reproduce:
$ (echo hello; sleep 1; echo world) | tee >(cat) hello hello … and, after a brief delay, world world $ echo "$?" 0 $ (echo hello; sleep 1; echo world) | tee >(echo yo) yo hello $ echo "$?" 141
As you hopefully understand,
>(command)
creates a pipe
to a process running command
.
The standard input of command
is connected to a pathname
that other commands on the command line (in this case, tee
)
can open and write to. When command
is cat
,
the process sits there and reads from stdin until it gets an EOF.
In this case, tee
has no problem
writing all the data it reads from its stdin to the pipe.
But, when command
is echo yo
,
the process writes yo
to the stdout and immediately exits.
This causes a problem for tee
;
when it writes to a pipe with no process at the other end,
it gets a SIGPIPE signal.
Apparently OS X’s version of tee
writes to the file(s) on the command line first, and then its stdout.
So, in your example (echo hi | tee >(echo yo)
),
tee
gets the pipe failure on its very first write.
Whereas, the version of tee
on Linux and Cygwin
writes to the stdout first,
so it manages to write hi
to the screen before it dies.
In my enhanced example, tee
dies when it writes hello
to the pipe,
so it doesn’t get a chance to read and write world
.
To visualize what's going on, compare the following two variations:
bash -c 'echo hi | tee >(sleep 1; echo yo); echo $?'
bash -c 'wait_and_tee () { sleep 1; tee "$@"; };
echo hi | wait_and_tee >(echo yo); echo $?'
Notice what's happening in the first variation?
$ bash -c 'echo hi | tee >(sleep 1; echo yo); echo $?'
hi
0
$ yo
The command in the process substitution sleep 1; echo yo
is executed in parallel with the commands outside, and bash doesn't wait for it to finish. So the sequence of events is:
- The three commands
echo hi
,sleep 1; echo yo
andtee
are started in parallel. echo hi
writeshi
to its output (the|
pipe).tee
reads from the pipe and writes to both its standard output and its command line argument, which is another pipe created by>(…)
. This results in one copy ofhi
printed to the terminal, and one copy in the buffer of the>(…)
pipe.- In parallel with the previous bullet point,
echo hi
exits, which closes the write end of the|
pipe. tee
notices that it has reached he end of its input file. It has written all of its data out, so it exits.- From bash's perspective, both sides of the pipe have exited, so the command is over. Since
tee
returned 0, the status of the pipeline is 0. - One second later,
sleep 1
finishes, andecho yo
is executed.
In the second variation, I force echo yo
to terminate before tee
, by forcing it to terminate before tee
starts. This time, the sequence of events is:
- The three commands
echo hi
,echo yo
andsleep 1
are started in parallel. echo hi
writeshi
to its output (the|
pipe).- In parallel with the previous bullet point,
echo yo
printsyo
and exits. - One second later,
sleep 1
exits andtee
starts. tee
readshi
from its input, and attempts to write it both to the terminal (its standard output) and the pipe resulting from>(…)
passed as an argument. Since the process that had this pipe open for reading (echo yo
) exited a second ago, the attempt to write to the pipe fails with SIGPIPE (signal 13, which the shell reports as 128+signal_number).
As G-Man explains, whether hi
is displayed in the second case depends on whether tee
tries to write to its standard output or its file argument first.
Without the sleep
calls, the timing could go either way (I get about half/half under Linux, a different kernel might make one timing a lot more likely than the other).