关于linux:通过过滤器仅管道STDERR

Pipe only STDERR through a filter

在使用STDOUT统一它之前,有没有办法在bash中通过过滤器管道STDERR? 那就是我想要的

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

而不是

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


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

1
2
3
4
5
6
STDERR: stderr output
STDOUT: more regular

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

引用上面的链接:

  • First save stdout as &3 (&1 is duped into 3)
  • Next send stdout to stderr (&2 is duped into 1)
  • Send stderr to &3 (stdout) (&3 is duped into 2)
  • close &3 (&- is duped into 3)

  • 过程替换的天真使用似乎允许从stdout分别过滤stderr

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

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

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


    TL; DR:

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

    例:

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

    这将适用于bash和zsh。 Bash现在几乎无处不在,但是,如果你确实需要一个(非常粗糙)POSIX sh的解决方案,那么请看这里。

    说明

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

    Process substitution allows a process’s input or output to be referred to using a filename. It takes the form of

    1
    >(list)

    The process list is run asynchronously, and its input or output appears as a filename.

    所以你用过程替换得到的是一个文件名。

    就像你能做的那样:

    1
    $ cmd 2> filename

    你可以做

    1
    $ cmd 2> >(filter >&2)

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


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

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

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

    请注意,2>之后的空格对于正确解析命令是必需的。


    TL; DR:(bash和zsh)

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

    例:

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

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

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

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

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

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

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

    上面的另一个问题是,如果不再将STDOUT和STDERR交换回其原始值,则无法将命令传送到其他命令。

    为了允许在POSIX sh中继续管道,(并且仍然假设FD 3不是它使用的)它变得复杂:

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

    所以,考虑到这个假设和粗略的语法,你可能会更好地使用TL中显示的更简单的bash / zsh语法;上面的DR,并在此解释。


    Advanced Bash Scripting Guide的这一页的最后一部分是"仅将stderr重定向到管道"。

    # Redirecting only stderr to a pipe.

    1
    2
    3
    4
    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的其他部分应该能够帮助你,这是非常好的。


    看一下命名管道:

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