如何使用 grep 输出标题?
我们知道 ps 命令是一个方便的实用程序,用于列出系统上当前正在运行的进程。此外,grep 命令擅长过滤文本。
在本教程中,我们将探索如何结合 head、grep、sed、awk 命令来查找所需的进程并保留ps输出的标题行,以及为什么我们不应该使用 ps -ef | {head -1; grep 'patter';}
方法。
问题简介
带有 -ef 选项的 ps 命令可以列出系统上所有正在运行的进程。实际上,我们通常不需要整个列表。相反,我们想要检查特定进程的信息。因此,通常我们会将 ps 的输出通过管道传递给 grep 命令以进行一些过滤。
接下来,让我们看一个例子。假设我们要使用关键字“ wechat ”检查进程:
1 | $ ps -ef | grep -i 'wechat' |
如上面的输出所示,当前,微信分别启动了微信、进程间通信管理、小程序这三个进程。除此之外,我们还可以看到“ grep -i wechat ”命令也出现在输出中。这是因为grep进程在我们启动“ ps | grep ”命令。
要从输出中抑制 grep 命令,我们可以使用 Regex 技巧:
1 | $ ps -ef | grep -i '[w]echat' |
该正则匹配的是 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 | $ ps -ef | sed -n '1p; /[w]echat/Ip' |
如上面的输出所示,我们得到了所需的输出。
接下来,让我们快速浏览一下sed命令以了解其工作原理:
sed -n
禁用sed的自动打印;我们将手动控制输出1p
打印第一行/[w]echat/p
打印匹配给定正则表达式*/[w]echat/的行*,I
表示忽略大小写。
管道到 awk 命令
awk 命令是另一个强大的文本处理工具。同样,它也可以通过 Regex 和行号来控制输出:
1 | $ ps -ef | awk 'NR==1 || /[W]eChat/' |
正如我们所见, awk命令也产生了预期的输出。这里,“ NR == 1 || /[W]eChat/ ”是一个布尔表达式。awk在每个输入行上计算这个表达式。
当输入行的行号为1或其内容与定义的正则表达式匹配时,\ awk\ 将执行默认操作: \打印\。因此,我们得到了想要的输出。
不要将 ps 输出通过管道传输到 { head -1; grep ‘pattern’;}
到目前为止,我们已经看到了 sed 和 awk 解决方案。我们知道 ps -ef | grep 不会工作只是因为 grep不能输出第一行。此外,head命令可以轻松打印前n行。我们可以简单地添加head -1来打印第一行并将其余行留给grep吗?
ps -ef | { head -1; grep ‘pattern’} 方法
让我们用我们的例子来测试它:
1 | $ ps -ef | {head -1; grep -i wechat} |
如我们所见,这种方式适用于我们的示例。然而,这个解决方案并不可靠。我们不应该使用这种方法。
接下来,我们来了解一下这种做法有什么问题。
管道到组命令
首先,让我们看一下“ { head -1; grep -i wechat; } “。在这里,我们将这两个命令分组在 {…}中。
当我们通过管道连接到 Bash 命令组时,例如Cmd | { 命令 1; 命令2;},自然地,我们认为Cmd的输出将变成命令组中每个命令的 Stdin。
但是,以这种方式无法通过管道传输到命令组。如果我们将一些数据通过管道传输到命令组,则该组中的所有命令共享相同的 Stdin。
在将 ps 的输出通过管道传输到命令组之后,首先,head -1* 将使用来自 Stdin 的一些数据。然后,*grep 命令将使用来自 Stdin 的其余数据。**因此,如果前面的命令已经使用了来自 Stdin 的所有数据,后面的命令将使用空输入。一个例子可以快速解释它:
1 | $ seq 10 | { wc -l; grep '2'} |
如上例所示,命令组中的第一个命令是带有 -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 | $ seq 10 | { head -1; grep '2'; } |
正如我们所见, 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 | $ seq 2048 | wc -c |
如我们所见, seq 2048 将产生 9133 字节的输出。接下来,让我们看看 head -1从 seq 2048读取后 Stdin 中还剩下多少字节:
1 | $ seq 2048 | { head -1 > /dev/null; wc -c;} |
由于我们不需要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 输出标题?