好办法

好办法

假设一个简单的 grep 例如:

$ psa aux | grep someApp
1000     11634 51.2  0.1  32824  9112 pts/1    SN+  13:24   7:49 someApp

这提供了很多信息,但由于缺少 ps 命令的第一行,因此没有该信息的上下文。我希望也显示 ps 的第一行:

$ psa aux | someMagic someApp
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
1000     11634 51.2  0.1  32824  9112 pts/1    SN+  13:24   7:49 someApp

当然,我可以为 grep 添加一个正则表达式,专门用于 ps:

$ ps aux | grep -E "COMMAND|someApp"

但是,我更喜欢更通用的解决方案,因为在其他情况下我也希望有第一行。

看起来这将是一个很好的用例“stdmeta”文件描述符

答案1

好办法

通常您不能使用 grep 执行此操作,但可以使用其他工具。 AWK 已经提到过,但您也可以使用sed,如下所示:

sed -e '1p' -e '/youpattern/!d'

怎么运行的:

  1. Sed 实用程序单独作用于每一行,在每一行上运行指定的命令。您可以有多个命令,指定多个-e选项。我们可以在每个命令前面加上一个范围参数,指定该命令是否应用于特定行。

  2. “1p”是第一命令。它使用p通常打印所有行的命令。但我们在它前面加上一个数值,指定它应该应用的范围。在这里,我们使用1which表示第一行。如果要打印更多行,可以使用x,ypwhere xis first line to print, yis last line to print。例如要打印前 3 行,您可以使用1,3p

  3. 下一个命令d通常会删除缓冲区中的所有行。在此命令之前,我们yourpattern在两个/字符之间放置。这是p寻址命令应在其上运行的行的另一种方式(首先是像我们对命令所做的那样指定哪些行)。这意味着该命令仅适用于匹配的行yourpattern。除此之外,我们在命令之前使用!字符d,这会颠倒其逻辑。所以现在它将删除所有行不要匹配指定的模式。

  4. 最后,sed 将打印缓冲区中剩余的所有行。但是我们从缓冲区中删除了不匹配的行,因此只会打印匹配的行。

总结一下:我们打印第一行,然后从输入中删除与我们的模式不匹配的所有行。其余的行被打印(所以只有那些行匹配模式)。

第一行问题

正如评论中提到的,这种方法存在问题。如果指定的模式也匹配第一行,它将被打印两次(一次通过p命令,一次因为匹配)。我们可以通过两种方式避免这种情况:

  1. 在 后添加1d命令1p。正如我已经提到的,d命令从缓冲区中删除行,我们用数字 1 指定它的范围,这意味着它只会删除第一行。所以命令是sed -e '1p' -e '1d' -e '/youpattern/!d'

  2. 使用1b命令,而不是1p.这是一个诡计。b命令允许我们跳转到由标签指定的其他命令(这样可以省略一些命令)。但是,如果未指定此标签(如我们的示例中所示),它只会跳转到命令末尾,忽略我们行的其余命令。所以在我们的例子中,最后一个d命令不会从缓冲区中删除这一行。

完整示例:

ps aux | sed -e '1b' -e '/syslog/!d'

使用分号

某些sed实现可以通过使用分号分隔命令而不是使用多个-e选项来节省您的打字时间。因此,如果您不关心可移植性,则命令将是ps aux | sed '1b;/syslog/!d'.它至少在GNU sed实现中起作用busybox

疯狂的方式

然而,这是使用 grep 来完成此操作的相当疯狂的方法。这绝对不是最佳的,我发布这个只是为了学习目的,但是如果您的系统中没有任何其他工具,您可以使用它:

ps aux | grep -n '.*' | grep -e '\(^1:\)\|syslog'

怎么运行的

  1. 首先,我们使用-n选项在每行之前添加行号。我们想要计算我们正在匹配的所有行.*- 任何内容,甚至是空行。正如评论中建议的,我们也可以匹配'^',结果是一样的。

  2. 然后我们使用扩展正则表达式,这样我们就可以使用\|充当 OR 的特殊字符。因此,如果该行以1:(第一行)开头或包含我们的模式(在本例中为syslog),我们就会匹配。

行号问题

现在的问题是,我们在输出中得到了这个丑陋的行号。如果这是一个问题,我们可以使用 删除它们cut,如下所示:

ps aux | grep -n '.*' | grep -e '\(^1:\)\|syslog' | cut -d ':' -f2-

-d选项指定分隔符,-f指定我们要打印的字段(或列)。因此,我们希望剪切每个:字符上的每一行,并仅打印第二列和所有后续列。这有效地删除了第一列及其分隔符,这正是我们所需要的。

答案2

awk您对使用代替感觉如何grep

chopper:~> ps aux | awk 'NR == 1 || /syslogd/'
USER              PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
root               19   0.0  0.0  2518684   1160   ??  Ss   26Aug12   1:00.22 /usr/sbin/syslogd
mrb               574   0.0  0.0  2432852    696 s006  R+    8:04am   0:00.00 awk NR == 1 || /syslogd/
  • NR == 1:记录数==1; IE。第一行
  • ||: 或者:
  • /syslogd/:要搜索的模式

它也可能值得一看pgrep,尽管这更多的是针对脚本而不是面向用户的输出。不过,它确实避免了grep命令本身出现在输出中。

chopper:~> pgrep -l syslogd
19 syslogd

答案3

ps aux | { IFS= read -r line; printf '%s\n' "$line";grep someApp;}

对于一些head实现,例如内置headksh93(使用 启用builtin head,但要注意并非所有构建都ksh93包含它):

ps aux | { head -n1;grep someApp;}

然而,对于大多数head实现来说,当输入不可查找时(例如这里的管道),当它们按块读取输入时,这不起作用。

和:

{ head -1;grep ok;} <<END
this is a test
this line should be ok
not this one
END

对于大多数head实现,您只能得到:

this is a test
this line should be ok

使用使用临时文件而不是管道实现此处文档的 shell。

line命令在可用的情况下(它曾经是一个标准命令,但由于可以通过 获得该功能而从标准中删除IFS= read -r)将适用于该命令,因为它保证不会读取多于一行的输入。

对于zsh,您还可以使用IFS= read -re(-e表示cho,不要与表示's表示dite混淆)。它也是唯一不会因 NUL 字节而阻塞的 shell。bash-eeread

答案4

我倾向于将标题发送到标准错误:

ps | (IFS= read -r HEADER; echo "$HEADER" >&2; cat) | grep ps

这通常足以满足人类阅读的目的。例如:

  PID TTY          TIME CMD
 4738 pts/0    00:00:00 ps

括号内的部分可以放入其自己的脚本中以供一般使用。

还有一个额外的便利之处在于,输出可以进一步通过管道传输(到sort等),并且标头将保留在顶部。

相关内容