我想写一个小包装器,ps
这样无论选项是什么,或者如果我 grep 结果,我都可以得到输出的第一行,ps
告诉我列是什么。例如, 的输出ps_wrapper -aux | grep thing
将类似于:
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
name 33925 1.0 0.0 9972 5528 pts/0 Ss 19:34 0:00 /bin/thing
我试着看看是否tee
有用,并尝试了类似的东西
function ps_wrapper {
tmpfile=$(mktemp)
ps $@ > $tmpfile
head -n 1 $tmpfile >> /dev/stdout
cat $tmpfile
}
但我很快意识到,当然,即使我“手动”写入/dev/stdout
,它仍然会通过管道传输到grep
。
我想打印我正在运行的实际命令的第一行,因为打印的列ps
可能会根据使用的选项而变化。
我正在使用 zsh,但如果有更合适的工具,我不介意使用其他工具。
感谢您的阅读。
答案1
一般来说,“绕过管道”是不可能的,例如,将输出发送到没有管道时输出将去往的任何地方。但是,可以绕过管道,将输出重定向到您选择的另一个位置,例如终端。特殊文件/dev/tty
始终代表当前终端。
function ps_wrapper {
tmpfile=$(mktemp)
ps "$@" > $tmpfile
head -n 1 $tmpfile >/dev/tty
cat $tmpfile
rm $tmpfile
}
如果您“保存”原始位置并将其向下传递,也可以绕过管道通过文件描述符。但你不能从ps_wrapper
函数中做到这一点。
function ps_wrapper {
tmpfile=$(mktemp)
ps "$@" > $tmpfile
head -n 1 $tmpfile >&3
cat $tmpfile
rm $tmpfile
}
{ ps_wrapper … | grep …; } 3>&1
有很多方法可以避免创建临时文件。我会提到一些。除非另有说明,否则此答案中的解决方案可以在 bash 中工作,如果您在变量和命令替换周围添加双引号。
如果您愿意更改调用该函数的方式,可以在管道的右侧连续调用head
和。将读取并打印第一行,并将其余部分留给后继者使用。grep
head
ps … | { head -n 1; grep …; }
您可以流程替代与tee
或zsh 的内置tee
类似功能 ( multios
)要复制输出,请将一个流发送到head -n 1
您选择的命令,将另一个流发送到您选择的命令。但是,如果您只是将每个流通过管道传输到一个命令,则两者之间将会发生竞争,如果head
速度不够快,第一行可能不会出现在顶部。它可能经常工作,因为head
速度相当快,但不能保证,例如,如果grep
在磁盘缓存中但head
不在磁盘缓存中。
ps | tee >(head -n 1 >/dev/tty) | grep …
ps >(head -n 1 >/dev/tty) | grep … # zsh only, only if multios is not disabled
您可以使用 awk 显示第一行,然后传递其余行。
function ps_wrapper {
ps "$@" | awk 'NR == 1 {print >"/dev/tty"} NR != 1 {print}'
}
说明:
- awk 逐行处理输入。
- 健康)状况{代码} 执行代码在满足的线上健康)状况。
NR
是行号。- awk 中的重定向工作方式与 shell 不完全一样,但已经足够接近了。
print
不带参数打印输入行。
另一种方法是将过滤器功能构建到单个命令中。选择一个不会出现在参数中的字符串用作ps
分隔符,例如|
。
function pipe_preserving_first_line {
local lhs
lhs=()
while [[ $1 != '|' ]]; do
lhs+=($1)
shift
done
shift
"${lhs[@]}" | {
head -n 1;
"$@"
}
}
pipe_preserving_first_line ps u \| grep foo
您可以使用ps
的匹配功能来代替 grep,例如-C
通过命令名称来匹配进程。
ps uww -C mycommand
除了使用 grep 之外,您还可以使用pgrep
的匹配工具。
ps -p $(pgrep -d, mycommand)