如何使用 grep 输出标题?

我们知道 ps 命令是一个方便的实用程序,用于列出系统上当前正在运行的进程。此外,grep 命令擅长过滤文本。

在本教程中,我们将探索如何结合 head、grep、sed、awk 命令来查找所需的进程并保留ps输出的标题行,以及为什么我们不应该使用 ps -ef | {head -1; grep 'patter';} 方法。

问题简介

带有 -ef 选项的 ps 命令可以列出系统上所有正在运行的进程。实际上,我们通常不需要整个列表。相反,我们想要检查特定进程的信息。因此,通常我们会将 ps 的输出通过管道传递给 grep 命令以进行一些过滤。

接下来,让我们看一个例子。假设我们要使用关键字“ wechat ”检查进程:

1
2
3
4
5
$ ps -ef | grep -i 'wechat'  
501 18069 1 0 3:12下午 ?? 0:00.87 /Applications/WeChat.app/Contents/MacOS/Mini Program.app/Contents/MacOS/Mini Program
501 79145 1 0 6:12下午 ?? 9:23.76 /Applications/WeChat.app/Contents/MacOS/WeChat
501 79198 1 0 6:12下午 ?? 0:00.03 5A4RE8SF68.com.tencent.xinWeChat.IPCHelper
501 34552 29961 0 3:40下午 ttys018 0:00.00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox -i wechat

如上面的输出所示,当前,微信分别启动了微信、进程间通信管理、小程序这三个进程。除此之外,我们还可以看到“ grep -i wechat ”命令也出现在输出中。这是因为grep进程在我们启动“ ps | grep ”命令。

要从输出中抑制 grep 命令,我们可以使用 Regex 技巧:

1
2
3
4
$ ps -ef | grep -i '[w]echat'
501 18069 1 0 3:12下午 ?? 0:00.89 /Applications/WeChat.app/Contents/MacOS/Mini Program.app/Contents/MacOS/Mini Program
501 79145 1 0 6:12下午 ?? 9:24.41 /Applications/WeChat.app/Contents/MacOS/WeChat
501 79198 1 0 6:12下午 ?? 0:00.03 5A4RE8SF68.com.tencent.xinWeChat.IPCHelper

该正则匹配的是 wechat,因此不会出现 grep 自身。

然而,由于ps输出的头部与模式“ wechat ” 不匹配, grep过滤掉了头部。例如,默认的 ps -ef标头如下所示:

1
UID          PID    PPID  C STIME TTY          TIME CMD

没有标头,当我们想要检查进程的详细信息时并不简单。

此外,我们知道ps 命令可以让我们 灵活地控制输出。如果我们要求 ps显示一些自定义的列,标题行肯定会使输出更容易理解。

因此,接下来,我们将介绍几种使用 grep 过滤 ps 输出的方法,同时保留标题行。

另外,我们可能已经看到了这个解决方案:ps -ef | {head -1; grep -i wechat} 在本教程的最后,我们将讨论这种方法存在的问题以及为什么我们不应该使用该命令。

管道到 sed 命令

让我们首先回顾一下我们的要求。我们想对ps -ef输出进行基于正则表达式的搜索。此外,必须保留标题行,它也是输出中的第一行。附注 | grep方法无法解决问题,因为grep 命令只能通过 Regex 过滤输入。

sed 命令是用于处理文本的出色命令行实用程序。它支持“ [地址]动作”动作模式。此外,行号和正则表达式模式都可以是 sed 的地址

接下来,让我们将ps -ef的输出通过管道传递给 sed

1
2
3
4
5
$ ps -ef | sed -n '1p; /[w]echat/Ip'
UID PID PPID C STIME TTY TIME CMD
501 18069 1 0 3:12下午 ?? 0:01.88 /Applications/WeChat.app/Contents/MacOS/Mini Program.app/Contents/MacOS/Mini Program
501 79145 1 0 6:12下午 ?? 10:14.25 /Applications/WeChat.app/Contents/MacOS/WeChat
501 79198 1 0 6:12下午 ?? 0:00.03 5A4RE8SF68.com.tencent.xinWeChat.IPCHelper

如上面的输出所示,我们得到了所需的输出。

接下来,让我们快速浏览一下sed命令以了解其工作原理:

  • sed -n 禁用sed的自动打印;我们将手动控制输出
  • 1p 打印第一行
  • /[w]echat/p 打印匹配给定正则表达式*/[w]echat/的行*,I表示忽略大小写。

管道到 awk 命令

awk 命令是另一个强大的文本处理工具。同样,它也可以通过 Regex 和行号来控制输出:

1
2
3
4
5
$ ps -ef | awk 'NR==1 || /[W]eChat/'                       
UID PID PPID C STIME TTY TIME CMD
501 18069 1 0 3:12下午 ?? 0:02.14 /Applications/WeChat.app/Contents/MacOS/Mini Program.app/Contents/MacOS/Mini Program
501 79145 1 0 6:12下午 ?? 10:28.04 /Applications/WeChat.app/Contents/MacOS/WeChat
501 79198 1 0 6:12下午 ?? 0:00.03 5A4RE8SF68.com.tencent.xinWeChat.IPCHelper

正如我们所见, awk命令也产生了预期的输出。这里,“ NR == 1 || /[W]eChat/ ”是一个布尔表达式。awk在每个输入行上计算这个表达式。

当输入行的行号为1或其内容与定义的正则表达式匹配时,\ awk\ 将执行默认操作: \打印\。因此,我们得到了想要的输出。

不要将 ps 输出通过管道传输到 { head -1; grep ‘pattern’;}

到目前为止,我们已经看到了 sedawk 解决方案。我们知道 ps -ef | grep 不会工作只是因为 grep不能输出第一行。此外,head命令可以轻松打印前n行。我们可以简单地添加head -1来打印第一行并将其余行留给grep吗?

ps -ef | { head -1; grep ‘pattern’} 方法

让我们用我们的例子来测试它:

1
2
3
4
5
$ ps -ef | {head -1; grep -i wechat}
UID PID PPID C STIME TTY TIME CMD
501 18069 1 0 3:12下午 ?? 0:02.24 /Applications/WeChat.app/Contents/MacOS/Mini Program.app/Contents/MacOS/Mini Program
501 79145 1 0 6:12下午 ?? 10:33.68 /Applications/WeChat.app/Contents/MacOS/WeChat
501 79198 1 0 6:12下午 ?? 0:00.03 5A4RE8SF68.com.tencent.xinWeChat.IPCHelper

如我们所见,这种方式适用于我们的示例。然而,这个解决方案并不可靠。我们不应该使用这种方法。

接下来,我们来了解一下这种做法有什么问题。

管道到组命令

首先,让我们看一下“ { head -1; grep -i wechat; } “。在这里,我们将这两个命令分组在 {…}中。

当我们通过管道连接到 Bash 命令组时,例如Cmd | { 命令 1; 命令2;},自然地,我们认为Cmd的输出将变成命令组中每个命令的 Stdin。

但是,以这种方式无法通过管道传输到命令组。如果我们将一些数据通过管道传输到命令组,则该组中的所有命令共享相同的 Stdin

在将 ps 的输出通过管道传输到命令组之后,首先,head -1* 将使用来自 Stdin 的一些数据。然后,*grep 命令将使用来自 Stdin 的其余数据。**因此,如果前面的命令已经使用了来自 Stdin 的所有数据,后面的命令将使用空输入。一个例子可以快速解释它:

1
2
3
4
$ seq 10 | { wc -l; grep '2'}
10
$ echo ?
1

如上例所示,命令组中的第一个命令是带有 -l 选项的wc命令,用于报告总行数。它将使用来自 Stdin 的所有数据来计算我们有多少行。wc打印“10”后 Stdin 为空。当然,后面的grep ‘2’ 命令也找不到匹配的行。因此,如果我们检查整个命令的退出代码,它是1而不是0

现在我们了解了 pipe 如何与 Bash 的命令组一起工作,有些人可能会进一步问:如果head -1仅使用第一行,即ps输出的标题行,那么后面的grep*命令将处理所有其余部分——这确实是我们想要的——所以,为什么我们不应该这样使用?

要回答这个问题,我们需要了解 head 是如何读取输入的。

head 命令如何读取输入

尽管 head -1只打印第一行,但这并不意味着 head 只读取到第一个换行符。head 命令总是使用预定义的缓冲区大小读取字节。如果第一个缓冲区块中没有换行符——例如,如果第一行很长——head继续读取定义大小的后续块,直到找到第一个换行符。

另一方面,如果第一行很短,head -1 无论如何都会读取第一个块,这意味着它会在第一个换行符之后消耗第一行和其他数据。

接下来,让我们通过一个例子来理解这一点:

1
2
3
4
$ seq 10 | { head -1; grep '2'; }
1
$ echo $?
1

正如我们所见, head -1打印“ 1 ”。但是, grep ‘2’不匹配任何内容。此外,$? 报告 1。它还告诉我们 grep 命令没有找到任何匹配项。

实际上,我们可以将 *grep ‘2’更改为grep ‘3’grep ‘4’甚至grep ‘10’,我们都会得到相同的结果。这是因为**前面的*head -1已经消耗了*seq*命令的全部输出**。

因此,该方法 ps -ef | {head -1; grep 'pattern';} 不可靠。我们不应该使用它。

head 命令缓冲区大小

最后,让我们找出 head 的读取操作的缓冲区大小。此缓冲区大小 ( BUFSIZ ) 在 glibc 库的stdio.h中定义 。在最新版本中,缓冲区大小定义为 8192 字节

我们也可以很容易地验证这个值。

首先,让我们创建一些大于 8k 字节的数据。为简单起见,我们仍将使用seq命令生成数据:

1
2
$ seq 2048 | wc -c
9133

如我们所见, seq 2048 将产生 9133 字节的输出。接下来,让我们看看 head -1seq 2048读取后 Stdin 中还剩下多少字节:

1
2
$ seq 2048 | { head -1 > /dev/null; wc -c;}
941

由于我们不需要head -1的输出,我们将其重定向到*/dev/null。正如我们所看到的,在 head从 9133 字节读取之后,我们仍然有 941 字节。因此,head*命令的读取缓冲区大小为 9133 – 941 = 8192 字节。

当我们使用 ps -ef | {head -1; grep 'patter';}我们无法预测我们正在寻找的进程条目是否位于*ps*命令输出的前 8192 字节中,因此这种方法是不稳定的

结论

在本文中,我们探讨了如何使用 sed 和 *awk过滤 ps 的输出并保留标题行 。

此外,我们还了解了管道如何与 Bash 的命令组一起工作。此外,我们还讨论了为什么我们不应该使用 ps -ef | {head -1; grep 'patter';} 方法。

本文翻译自: How to grep the ps Output With Headers | Baeldung on Linux

如何使用 grep 输出标题?

https://ganzhixiong.com/p/b1f4a583/

Author

干志雄

Posted on

2023-04-01

Updated on

2023-04-01

Licensed under

Comments