有没有办法在 shell 脚本或函数中绕过管道?

有没有办法在 shell 脚本或函数中绕过管道?

我想写一个小包装器,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和。将读取并打印第一行,并将其余部分留给后继者使用。grephead

ps … | { head -n 1; grep …; }

您可以流程替代teezsh 的内置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)

相关内容