"ps -e"、"tee" 和 "head" 的管道表现异常

pipe of "ps -e", "tee" and "head" behaves strange

我打算使用以下命令来 grep header 的“zsh”进程。但是这个命令只显示了 header 然后就退出了。 grep 没有 运行。我可以通过其他方式完成这项任务,但我想了解这里发生了什么。有趣的是,如果 ls 等其他命令替换 ps -e,管道会按预期工作。事实上,管道甚至可以使用 ps(没有 -e)。如果 head 被另一个 grep 替换,管道也可以工作。看起来,这个管道中的ps -eheadtee之间有一些有趣的相互作用。有什么解释吗?我 运行 MacOS-iterm2 和 zsh 中的命令。谢谢。

 ps -e|tee >(head -n 1) >(grep "zsh") >/dev/null

上面的命令产生了以下不正确的输出:只显示了 header,grep 没有显示 运行。

PID TTY           TIME CMD

可以通过以下命令生成正确的输出:

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

更新:问题确实出在 MacOS 附带的 tee 中。作为@OndrejK。在下面的评论中指出,GNU 的 tee 按预期工作。

我认为 Ondrej K. 在评论中指出:head 正在读取它从 tee 收到的第一行,然后退出,所以当 tee 试图写该管道的第二行,它收到一个错误(可能实际上是一个 SIGPIPE 信号)并退出而不写入 ps's 输出的其余部分。

我能够在 macOS 和 Raspbian 下重现这个问题,同时使用 zsh 和 bash 作为我的 shell。通过将 head 替换为读取(但不输出)来自 tee 的其余输入的内容,我能够获得完整的输出。这是一个例子:

% 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

在上面的第二个版本中,在 head 之后添加 cat>/dev/null 让它在 head 退出后消耗(并丢弃)剩余的输入。在第三个中,sed -n '1p' 读取其整个输入,但只打印第一行。

请注意,此处的结果将取决于缓冲区大小和各种事件的时间等因素,因此它可能在不同环境之间不一致,甚至在明显一致的条件下随机变化。我怀疑它与 ps (没有 -e 选项)一起工作的原因是它没有产生足够的输出来触发问题: tee 可能能够转发整个输出pshead 绕过退出之前。

编辑:至少在我的环境中,在 head 退出后添加延迟可以防止出现问题:

% 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

这意味着它是 timing-limited,而不是 buffer-limited。但是,如果 ps 的输出足够大,它会 运行 在某个时候进入缓冲区限制,并且无论插入什么延迟,输出都会被 t运行 处理。