I meant to use the following command to grep the "zsh" process with header. But this command only showed the header and then exited. The grep
didn't run. I can do this task by other ways but I want to understand what happened here. The interesting thing is that the pipe works as expected if other command like ls
replaces ps -e
. In fact, the pipe works even with ps
(without -e
). The pipe also works if head
is replaced by another grep
. Seemingly, there are some interesting interactions between ps -e
, head
and tee
in this pipe. Any explanation? I ran the commands in MacOS-iterm2 and zsh. Thanks.
ps -e|tee >(head -n 1) >(grep "zsh") >/dev/null
the command above produces the following incorrect output: only header is shown, grep didn't run.
PID TTY TIME CMD
The correct output can be generated by the following command:
ps -e|awk 'FNR==1{print};/zsh/{print}'
PID TTY TIME CMD
1658 ttys000 0:00.30 -zsh
2817 ttys001 0:00.49 /bin/zsh -i
12890 ttys002 0:00.26 -zsh
13332 ttys003 0:00.23 -zsh
13469 ttys004 0:00.19 /bin/zsh -i
Update: The problem is indeed in the tee
shipped with MacOS. As @OndrejK. pointed out in the comments below, tee
from GNU works as expected.
CodePudding user response:
I think Ondrej K. nailed it in a comment: head
is reading just the first line it receives from tee
, then exiting, so when tee
tries to write the second line to that pipe, it gets an error (probably actually a SIGPIPE signal) and exits without writing the rest of ps's
output.
I was able to reproduce this problem under both macOS and Raspbian, using both zsh and bash as my shell. I was able to get the full output by replacing head
with something that reads (but doesn't output) the rest of the input from tee
. Here's an example:
% ps -e|tee >(head -n 1) >(grep "zsh") >/dev/null
PID TTY TIME CMD
% ps -e|tee >(head -n 1; cat>/dev/null) >(grep "zsh") >/dev/null
PID TTY TIME CMD
13127 pts/0 00:00:00 zsh
13353 pts/0 00:00:00 zsh
% ps -e|tee >(sed -n '1p') >(grep "zsh") >/dev/null
PID TTY TIME CMD
13127 pts/0 00:00:00 zsh
In the second version above, adding cat>/dev/null
after head
lets it consume (and discard) the rest of the input after head
exits. In the third, sed -n '1p'
reads its entire input, but only prints the first line.
Note that the result here will depend on things like buffer sizes and the timing of various events, so it may be inconsistent between different environments or even vary randomly under apparently consistent conditions. I suspect the reason it works with ps
(without the -e
option) is that it doesn't produce enough output to trigger the problem: tee
may be able to forward the entire output from ps
before head
gets around to exiting.
EDIT: At least in my environments, adding a delay after head
exits prevents the problem:
% ps -e|tee >(head -n 1; sleep 1) >(grep "zsh") >/dev/null
PID TTY TIME CMD
8002 pts/0 00:00:00 zsh
8164 pts/0 00:00:00 zsh
This means it's timing-limited, rather than buffer-limited. But if the output from ps
were big enough, it'd run into buffer limits at some point, and the output would be truncated no matter what delays were inserted.