仅将STDERR通过过滤器输送


82

在bash中,是否可以通过STDOUT统一将STDERR通过过滤器传递?那就是我想要

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

而不是

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

Answers:


64

这是一个示例,以如何在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. 首先将标准输出另存为&3(&1分为3)
  2. 接下来将stdout发送到stderr(&2被复制为1)
  3. 将stderr发送到&3(stdout)(&3被分成2个)
  4. 关闭&3(&-分为3)

1
它对我不起作用。最后,我使用它3>&2 2>&1 1>&3-
aleung

3
注意:假定未使用FD 3,并且不会撤消文件描述符1和2的交换,因此您不能继续将其传递给另一个命令。请参阅此答案以获取更多详细信息和解决方法。有关{ba,z} sh的更简洁的语法,请参见以下答案
汤姆·黑尔

24

天真地使用流程替换似乎可以与以下内容stderr分开过滤stdout

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

需要注意的是stderr出来上stderrstdoutstdout,我们可以在另一个子shell包裹了整个事情,并重定向到文件看到oe

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

为何 :; 一开始?我尝试了魔术线,但没有任何作用。
Koshmaar'2

1
有些人$用作命令提示符。然后,他们经常写信像贝壳的例子:$ cat /var/log/syslog | fgrep ...。但是,由于导致该行不可复制粘贴$:;看起来像是提示,但基本上是无操作的shell;因此您可以安全地选择并粘贴整行。
solidsnack

23
好的,但是您可以省略:; 并且该行也可以复制粘贴:)对我来说:; 看起来不像是提示,没有使示例更清晰(将命令与输出分离),但令人困惑。尽管我理解您的观点,但我们不讨论语法/约定。
Koshmaar '16

24

TL; DR:

$ 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请参见此处


说明

到目前为止,最简单的方法是通过流程替换重定向STDERR :

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

>(list)

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

因此,使用过程替换得到的是文件名。

就像您可以做的一样:

$ cmd 2> filename

你可以做

$ cmd 2> >(filter >&2)

>&2重定向的filter的STDOUT回到原来的STDERR。


1
有一个警告需要解决:bash不等待替换过程完成,而FD试图交换1和2则这样做。这可能很重要。exec 2> >(while .. read .. echo)在执行作为systemd服务运行的脚本时,我为此而烦恼。journald捕获服务的fd2,并从'<N>'前缀推断日志级别:<2> 在输出行之前添加,并且您将ERROR级别分配给日记记录。但是有时systemd会在退出主进程最后一个最重要的消息之前迅速杀死整个进程组!
公里

不错的简单解决方案。警告:将其作为Cron工作的一部分时要小心;在cron使用的某些shell中,进程替换不可用。
Quinn Comendant

15

TL; DR:(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语法并在此处进行说明,可能会更好。


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>之后的空格是必需的。


5

Advanced Bash脚本指南的该页的最后一部分是“仅将stderr重定向到管道”。

#仅将stderr重定向到管道。

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.

#谢谢,SC

这可能就是您想要的。如果没有,那么ABSG的其他部分应该可以为您提供帮助,这非常好。


我们有点犹豫,建议将ABSG作为参考,因为它混合了文档,处方和意见,但没有清楚地标出差异。尽管您链接到的内容看起来不错,但某些部分的内容也令人怀疑。
tripleee

3

看一下命名管道:

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

不会cat - err破坏stdout和stderr的穿插?
Martin DeMello 2010年

@马丁-这取决于。如果cmd1,catcmd2缓冲了输出,那么您可能会看到输出乱序。
暴徒
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.