假设一个简单的 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'
怎么运行的:
Sed 实用程序单独作用于每一行,在每一行上运行指定的命令。您可以有多个命令,指定多个
-e
选项。我们可以在每个命令前面加上一个范围参数,指定该命令是否应用于特定行。“1p”是第一命令。它使用
p
通常打印所有行的命令。但我们在它前面加上一个数值,指定它应该应用的范围。在这里,我们使用1
which表示第一行。如果要打印更多行,可以使用x,yp
wherex
is first line to print,y
is last line to print。例如要打印前 3 行,您可以使用1,3p
下一个命令
d
通常会删除缓冲区中的所有行。在此命令之前,我们yourpattern
在两个/
字符之间放置。这是p
寻址命令应在其上运行的行的另一种方式(首先是像我们对命令所做的那样指定哪些行)。这意味着该命令仅适用于匹配的行yourpattern
。除此之外,我们在命令之前使用!
字符d
,这会颠倒其逻辑。所以现在它将删除所有行不要匹配指定的模式。最后,sed 将打印缓冲区中剩余的所有行。但是我们从缓冲区中删除了不匹配的行,因此只会打印匹配的行。
总结一下:我们打印第一行,然后从输入中删除与我们的模式不匹配的所有行。其余的行被打印(所以只有那些行做匹配模式)。
第一行问题
正如评论中提到的,这种方法存在问题。如果指定的模式也匹配第一行,它将被打印两次(一次通过p
命令,一次因为匹配)。我们可以通过两种方式避免这种情况:
在 后添加
1d
命令1p
。正如我已经提到的,d
命令从缓冲区中删除行,我们用数字 1 指定它的范围,这意味着它只会删除第一行。所以命令是sed -e '1p' -e '1d' -e '/youpattern/!d'
使用
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'
怎么运行的
首先,我们使用
-n
选项在每行之前添加行号。我们想要计算我们正在匹配的所有行.*
- 任何内容,甚至是空行。正如评论中建议的,我们也可以匹配'^',结果是一样的。然后我们使用扩展正则表达式,这样我们就可以使用
\|
充当 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
实现,例如内置head
的ksh93
(使用 启用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
-e
e
read
答案4
我倾向于将标题发送到标准错误:
ps | (IFS= read -r HEADER; echo "$HEADER" >&2; cat) | grep ps
这通常足以满足人类阅读的目的。例如:
PID TTY TIME CMD
4738 pts/0 00:00:00 ps
括号内的部分可以放入其自己的脚本中以供一般使用。
还有一个额外的便利之处在于,输出可以进一步通过管道传输(到sort
等),并且标头将保留在顶部。