Contents

只过滤Linux中的标准错误

1. 概述

有时,我们可能希望在不修改标准输出的情况下过滤命令的标准错误。例如,我们可能想使用grep 来处理另一个命令的标准错误。

当我们使用管道 过滤进程的输出时,我们可以单独过滤标准输出,也可以过滤标准输出和错误。但是,我们不能使用管道仅过滤标准错误。

在本文中,我们将学习两种克服此限制的方法。

2. 使用流程替换

使用进程替换bash将文件名设置为命令输入或输出的别名。如果我们写*>(command),那么我们将得到一个代表命令输入的文件名。或者,我们可以写<(command)*来引用命令的输出。

让我们看看它是如何使用grep 工作的:

$ echo This parameter: >(grep blogdemo) is a filename to the command\'s input.
This parameter: /dev/fd/63 is a filename to the command's input.

正如我们所看到的,bash用文件*/dev/fd/63*替换了命令的输入。

让我们将其与重定向 结合起来以写入该“文件”:

$ echo -e "line 1: blogdemo
line 2: linux" > >(grep blogdemo)
line 1: blogdemo

在这种情况下,echo的标准输出被重定向到一个连接到grep标准输入的文件。

在 Linux 中,有 3 个标准文件描述符。标准输入 ( STDIN ) 是文件描述符0,标准输出 ( STDOUT ) 是文件描述符1,标准错误 ( STDERR ) 是文件描述符2。使用这个概念,我们还可以重定向文件描述符2来仅过滤标准错误。

当我们运行*curl -v http://www.example.com *时,HTML 响应打印到标准输出,HTTP 标头打印到标准错误。让我们通过grep重定向标准错误来过滤 Accept HTTP 标头:

$ curl -v http://www.example.com 2> >(grep Accept:)
> Accept: */*
<!doctype html>
<html>
<head>
    <title>Example Domain</title>
...

正如我们所见,grep只过滤了标准错误,而将 HTML 内容保持不变。

我们必须考虑到grep的输出是标准输出。但是,我们使用curl的标准错误来提供grep 。这可能是一个缺点,例如,如果我们附加更多过滤器。让我们在grep过滤器中添加>&2*以将标准错误与标准输出分开*:

$ curl -v http://www.example.com 2> >(grep Accept: >&2)

这有助于防止混合输出,并且grep会将Accept标头打印到标准错误。这样,我们可以使用两种不同的过滤器,一种用于标准输出,另一种用于标准错误

让我们添加*grep “title”*来过滤标准输出:

$ curl -v http://www.example.com > >(grep "<title>") 2> >(grep Accept: >&2)
> Accept: */*
    <title>Example Domain</title>

正如我们所见,我们设法通过一个过滤器*grep “title”传递标准输出,并通过另一个过滤器**grep Accept:*传递标准错误。*请注意,我们将grep Accept:*的输出重定向到标准错误。如果我们不这样做,*grep “title”也会过滤来自grep Accept:*的输出。

3. 交换文件描述符

仅过滤标准错误的另一种方法是交换文件描述符。**我们可以互换标准输出和标准错误,允许我们使用管道只过滤标准输出,这原本是标准错误。**此交换使用重定向分 3 步完成。

首先,我们将使用3>&1创建一个新的文件描述符 3,作为标准输出的副本。接下来,我们将使用*>&2使标准输出成为标准错误的副本。最后,我们将使用2>&3将标准错误设为文件描述符 3 的副本。请记住,文件描述符 3 是标准输出的副本。由于我们已经创建了一个新的文件描述符 3,然后我们将使用3>&-* 关闭它。

让我们重复前面的curl示例,但这次我们交换文件描述符

$ curl -v http://www.example.com 3>&1 >&2 2>&3 3>&- | grep Accept:
> Accept: */*
<!doctype html>
<html>
<head>
    <title>Example Domain</title>
...

这样,我们就收到了完整的 HTML 内容,并且我们只过滤了Accept HTTP 标头。

由于我们交换了文件文件描述符,curl将 HTML 内容打印到标准错误,并将 HTTP 标头打印到标准输出。这与原始行为相反。如果我们想保持原来的行为,我们必须再次交换文件描述符。

要将文件描述符返回到它们原来的含义,我们可以将命令和过滤器包围在一个子shell中,然后我们将再次交换文件描述符

$ (curl -v http://www.example.com 3>&1 >&2 2>&3 3>&- | grep Accept:) 3>&1 >&2 2>&3 3>&-

现在,我们可以添加另一个管道来过滤原始标准输出。 让我们添加grep来过滤title部分:

$ (curl -v http://www.example.com 3>&1 >&2 2>&3 3>&- | grep Accept:) 3>&1 >&2 2>&3 3>&- | grep "<title>"
> Accept: */*
    <title>Example Domain</title>

如我们所见,第一个grep只过滤了 HTTP 标头。然后,最后一个grep只过滤了 HTML 内容。