Pipe only STDERR through a filter

Is there any way, in bash, to pipe STDERR through a filter before unifying it with STDOUT? That is, I want

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

rather than

STDOUT ────┐
├────[ filter ]───> terminal/file/whatever
STDERR ────┘
37646 次浏览

高级 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.

谢谢 S.C. 。

这可能是你想要的。如果没有,其他部分的 ABSG 应该能够帮助你,这是非常好的。

让我们来看看命名管道:

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

下面是一个模仿 如何在 bash 中交换文件描述符的示例。

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. Send stderr to &3 (stdout) (&3 is duped into 2)
  4. 关闭 & 3(&-被欺骗成3)

我发现 bash 进程替换的使用更容易记住和使用,因为它几乎一字不差地反映了最初的意图。例如:

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

uses the first sed command as a filter on stderr only and the second sed command to modify the joined output.

请注意,为了正确解析命令,2 > 后面的空白是强制性的。

A naive use of process substitution seems to allow filtering of stderr separately from stdout:

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

请注意,stderr出现在 stderr上,stdout出现在 stdout上,我们可以通过在另一个子 shell 中包装整个事件并重定向到文件 oe来看到这一点

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

译者:

$ 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的解决方案(实际上非常复杂) ,那么可以使用 see here


解释

到目前为止,最简单的方法是通过 工序替代工序替代重定向 STDERR:

进程替换允许使用文件名引用进程的输入或输出

>(list)

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

所以进程替换得到的是一个文件名。

就像你一样:

$ cmd 2> filename

你可以的

$ cmd 2> >(filter >&2)

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

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不支持命名文件描述符。

The other issue with the above is that the command cannot be piped to further commands without again swapping STDOUT and STDERR back to their original values.

为了允许 POSIX sh中的向前管道,(仍然假设 FD3不使用)它是 变得很复杂:

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

因此,考虑到这个问题的假设和复杂的语法,您可能最好使用上面的 TL; DR 中显示的更简单的 bash/zsh语法,并解释 给你


实际演示,只抓取 stderr:

$ 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