"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 -e
、head
和tee
之间有一些有趣的相互作用。有什么解释吗?我 运行 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
可能能够转发整个输出ps
在 head
绕过退出之前。
编辑:至少在我的环境中,在 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运行 处理。
我打算使用以下命令来 grep header 的“zsh”进程。但是这个命令只显示了 header 然后就退出了。 grep
没有 运行。我可以通过其他方式完成这项任务,但我想了解这里发生了什么。有趣的是,如果 ls
等其他命令替换 ps -e
,管道会按预期工作。事实上,管道甚至可以使用 ps
(没有 -e
)。如果 head
被另一个 grep
替换,管道也可以工作。看起来,这个管道中的ps -e
、head
和tee
之间有一些有趣的相互作用。有什么解释吗?我 运行 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
可能能够转发整个输出ps
在 head
绕过退出之前。
编辑:至少在我的环境中,在 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运行 处理。