只将标准错误输出流通过过滤器传输

128

在bash中,是否有一种方法可以在将标准错误流与标准输出流合并之前通过过滤器传递标准错误流?也就是说,我想要

STDOUT ────────────────┐
                       ├─────> terminal/file/whatever
STDERR ── [ filter ] ──┘

与其

STDOUT ────┐
           ├────[ filter ]───> terminal/file/whatever
STDERR ────┘

9
因为其中包含一个精美的ASCII艺术图,所以这篇内容值得点赞。 - Leif Wickland
7个回答

72

以下是示例,模仿 如何在bash中交换文件描述符。 a.out的输出如下,没有前缀'STDXXX:'。

STDERR: stderr output
STDOUT: more regular

./a.out 3>&1 1>&2 2>&3 3>&- | sed 's/e/E/g'
more regular
stdErr output

引用上面的链接:

  1. 首先将 stdout 保存为 &3(将 &1 复制到 3)
  2. 接下来将 stdout 发送到 stderr(将 &2 复制到 1)
  3. 将 stderr 发送到 &3(即 stdout)(将 &3 复制到 2)
  4. 关闭 &3(将 -& 复制到 3)

2
它对我不起作用。最终,我使用3>&2 2>&1 1>&3-使其工作。 - aleung
7
注意:这假定FD 3没有在使用,并且不会撤销文件描述符1和2的交换,因此您不能继续将其传输到另一个命令。有关更多详细信息和解决方法,请参见此答案。对于{ba,z}sh的更清晰的语法,请参见此答案 - Tom Hale

71

简而言之:

$ cmd 2> >(stderr-filter >&2)

例子:

% cat /non-existant 2> >(tr o X >&2)
cat: /nXn-existant: NX such file Xr directXry
%

这个方法在bash和zsh中都可以使用。如今,Bash几乎无处不在,但是如果您真的需要一个(非常复杂的)解决方案来适用于POSIX sh,那么请点击此处


说明

目前最简单的解决方案是通过进程替换将标准错误输出重定向:

进程替换允许使用文件名引用进程的输入或输出。它采用以下形式:

>(list)

进程列表是异步运行的,其输入或输出显示为文件名。

因此,通过进程替换得到的是一个文件名。

就像您可以执行以下操作一样:

$ cmd 2> filename

你可以做

$ cmd 2> >(filter >&2)

>&2 重定向将 filter 的 STDOUT 返回到原始的 STDERR。


6
这个答案有一个警告:bash不会等到被替换的进程完成,而FD交换1和2则会。这可能很重要。在作为systemd服务运行的脚本中执行 exec 2> >(while .. read .. echo) 时,我曾受到过此类问题的困扰。journald捕获了服务的fd2,并从“<N>”前缀推断出日志级别:在输出行之前添加 <2>,你可以分配ERROR级别给journal记录。但有时,systemd在主要进程退出之前就更快地杀死整个进程组,导致它无法记录最后、最重要的消息! - kkm
2
不错的简单解决方案。注意:在作为cron作业的一部分使用时要小心;某些由cron使用的shell中不支持进程替换。 - Quinn Comendant

28

简而言之:(bash和zsh)

$ cmd 2> >(stderr-filter >&2)

示例:

% cat /non-existant 2> >(tr o X >&2)
cat: /nXn-existant: NX such file Xr directXry
%

StackExchange 网络上的许多答案都具有以下形式:

cat /non-existant 3>&1 1>&2 2>&3 3>&- | sed 's/e/E/g'

这里有一个内置的假设:文件描述符3没有被用于其他用途。

相反,使用一个命名的文件描述符,然后{ba,z}sh将分配下一个可用的文件描述符>= 10:

cat /non-existant {tmp}>&1 1>&2 2>&$tmp {tmp}>&- | sed 's/e/E/g'

请注意,POSIX sh不支持命名文件描述符。

上述方法的另一个问题是,如果不将STDOUT和STDERR再次交换回其原始值,则无法将命令管道传递给其他命令。

为了在POSIX sh中允许进一步的管道操作(并仍然假设FD 3未被使用),情况变得复杂起来

(cmd 2>&1 >&3 3>&- | stderr-filter >&2 3>&-) 3>&1

所以,考虑到这个假设和复杂的语法,你最好使用TL;DR中简单的bash/zsh语法,并在此处解释here


实际演示,仅对stderr进行grepping:

$ ls -l . noexistABC noexistXYZ
ls: cannot access 'noexistABC': No such file or directory
ls: cannot access 'noexistXYZ': No such file or directory
.:
total 4
-rw-rw-r-- 1 frank frank    0 Aug 19 12:26 bar.txt
-rw-rw-r-- 1 frank frank    0 Aug 19 12:26 foo.txt
drwxrwxr-x 2 frank frank 4096 Aug 19 12:26 someFolder


$ ( ls -l . noexistABC noexistXYZ 2>&1 >&3 3>&- | grep ABC >&2 3>&-) 3>&1
.:
ls: cannot access 'noexistABC': No such file or directory
total 4
-rw-rw-r-- 1 frank frank    0 Aug 19 12:26 bar.txt
-rw-rw-r-- 1 frank frank    0 Aug 19 12:26 foo.txt
drwxrwxr-x 2 frank frank 4096 Aug 19 12:26 someFolder

为什么要发两次相同的答案? - Rucent88

25

一个天真的进程替换使用似乎允许单独过滤stderrstdout

:; ( echo out ; echo err >&2 ) 2> >( sed s/^/e:/ >&2 )
out
e:err

注意,stderr输出在标准错误流上,而stdout则输出在标准输出流上,我们可以通过将整个内容包装在另一个子shell中并重定向到文件oe来查看。

( ( echo out ; echo err >&2 ) 2> >( sed s/^/e:/ >&2 ) ) 1>o 2>e

为什么要在开头加上 :; ?我尝试了不加这个神奇的行,但好像没有任何区别。 - Koshmaar
2
有些人使用 $ 作为命令提示符。然后他们经常编写像这样的 shell 示例:$ cat /var/log/syslog | fgrep ...。但是,由于 $ 的存在,这行代码无法直接复制粘贴。:; 看起来像一个提示符,但实际上它只是一个 shell 中的 no-op;因此,您可以安全地选择并粘贴整行代码。 - solidsnack
27
好的,但是你可以省略冒号和分号,这样这行内容也可以复制粘贴。对我来说,冒号和分号看起来不像是提示符,它并没有使示例更清晰(将命令与输出分开),而是让人感到困惑。虽然我理解你的观点,但我们不要讨论语法/约定。 - Koshmaar

8

我发现使用bash进程替换更容易记忆和使用,因为它几乎逐字反映了原始意图。例如:

$ cat ./p
echo stdout
echo stderr >&2
$ ./p 2> >(sed -e 's/s/S/') | sed 's/t/T/'
sTdout
STderr

使用第一个sed命令作为stderr的过滤器,第二个sed命令用于修改已连接的输出。

请注意,2>后面的空格是必须的,以便正确解析该命令。


7

高级Bash脚本指南的此页面的最后一部分是"仅将stderr重定向到管道"。

# Redirecting only stderr to a pipe.

exec 3>&1                              # Save current "value" of stdout.
ls -l 2>&1 >&3 3>&- | grep bad 3>&-    # Close fd 3 for 'grep' (but not 'ls').
#              ^^^^   ^^^^
exec 3>&-                              # Now close it for the remainder of the script.

# Thanks, S.C.

这可能是你所需要的内容。如果不是,ABSG 的其他部分应该能够帮助你,它非常优秀。

我们有些犹豫是否推荐ABSG作为参考,因为它混合了文档、规定和观点,没有清楚地标明差异。一些部分也有可疑的内容,尽管你链接的那个部分看起来还不错。 - tripleee

2

看一下命名管道:

$ mkfifo err
$ cmd1 2>err |cat - err |cmd2

“cat - err” 不会打破标准输出和标准错误的交替吗? - Martin DeMello
@Martin - 这取决于情况。如果 cmd1、cat 或 cmd2 缓冲输出,那么你可能会看到输出的顺序不正确。 - mob
这段代码无法运行。Shell 无法完成打开 err 的写入操作,直到 cat 完成对其的读取,但是 cat 在完成读取标准输入之前不会打开它,而在 cmd1 死亡之前它无法完成读取标准输入,但是 cmd1 无法启动直到它完成打开 err。这是一个死锁。fifo(7) 记录了这一点:“通常,打开 FIFO 会阻塞,直到另一端也被打开。” - JoL

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接