关于bash:如何处理stderr,而不是stdout?

How to pipe stderr, and not stdout?

我有一个程序,它将信息写入stdoutstderr,我需要grep,通过stderr,而不考虑stdout。

我当然可以分两步完成:

1
2
command > /dev/null 2> temp.file
grep 'something' temp.file

但我更愿意在没有临时文件的情况下完成这项工作。有什么巧妙的管道技巧吗?


首先将stderr重定向到stdout-管道;然后将stdout重定向到/dev/null(不更改stderr的去向):

1
command 2>&1 >/dev/null | grep 'something'

有关各种I/O重定向的详细信息,请参阅bash参考手册中有关重定向的章节。

注意,I/O重定向的顺序是从左到右解释的,但是管道是在解释I/O重定向之前设置的。文件描述符(如1和2)是对打开文件描述的引用。操作2>&1使文件描述符2(即stderr)引用与文件描述符1(即stdout)当前引用的相同的打开文件描述(参见dup2()open())。操作>/dev/null随后更改文件描述符1,使其引用/dev/null的打开文件描述,但这并不改变文件描述符2引用文件描述符1最初指向的打开文件描述的事实,即管道。


或者将stderr和stdout的输出交换为过度使用:

1
command 3>&1 1>&2 2>&3

这将创建一个新的文件描述符(3)并将其分配到与1(stdout)相同的位置,然后将fd1(stdout)分配到与fd2(stderr)相同的位置,最后将fd2(stderr)分配到与fd3(stdout)相同的位置。stderr现在作为stdout提供,旧stdout保留在stderr中。这可能有点过分,但希望给出更多关于bash文件描述符的详细信息(每个进程有9个可用的描述符)。


在bash中,还可以使用进程替换重定向到子shell:

1
command > >(stdlog pipe)  2> >(stderr pipe)

对于手头的案件:

1
command 2> >(grep 'something') >/dev/null


结合这些最佳答案,如果您这样做:

command 2> >(grep -v something 1>&2)

…然后所有stdout都保留为stdout,所有stderr都保留为stderr,但在stderr中不会看到包含字符串"something"的任何行。

这有一个独特的优点,即不会反转或丢弃stdout和stderr,也不会将它们弄脏在一起,也不会使用任何临时文件。


如果您考虑"重定向"和"管道"的实际情况,那么可视化事情就容易得多。bash中的重定向和管道只做一件事:修改进程文件描述符0、1和2指向的位置(请参见/proc/[pid]/fd/*)。

当命令行中存在管道或""运算符时,首先发生的事情是bash创建一个FIFO,并将左侧命令的fd 1指向此FIFO,并将右侧命令的fd 0指向同一FIFO。

接下来,从左到右对每侧的重定向操作符进行评估,并且每当描述符出现重复时,都使用当前设置。这一点很重要,因为由于首先设置了管道,因此FD1(左侧)和FD0(右侧)已经与正常情况不同,任何重复都会反映出这一事实。

因此,当您键入如下内容时:

1
command 2>&1 >/dev/null | grep 'something'

以下是发生的事情,顺序如下:

  • 将创建管道(FIFO)。命令FD1"指向此管道。GREP FD0"也指向此管道
  • "command fd2"指向"command fd1"当前指向的位置(管道)
  • "command fd1"指向/dev/null
  • 因此,"command"写入其fd 2(stderr)的所有输出都会进入管道,并由另一侧的"grep"读取。"command"写入其fd 1(stdout)的所有输出都会进入/dev/null。

    如果改为运行以下命令:

    1
    command >/dev/null 2>&1 | grep 'something'

    以下是发生的事情:

  • 创建一个管道,并将"command fd 1"和"grep fd 0"指向它
  • "command fd 1"指向/dev/null
  • "command fd 2"指向fd 1当前指向的位置(/dev/null)
  • 所以,来自"command"的所有stdout和stderr都将转到/dev/null。管道中没有任何内容,因此"grep"将关闭而不在屏幕上显示任何内容。

    还要注意,重定向(文件描述符)可以是只读(<)、只写(>)或读写(<>)。

    最后一个音符。程序是否向FD1或FD2写入某些内容完全取决于程序员。良好的编程实践要求错误消息应该转到fd 2,正常输出到fd 1,但是您经常会发现混合了这两者的编程很草率,或者忽略了约定。


    你在用bash吗?如果是这样:

    1
    command >/dev/null |& grep"something"

    http://www.gnu.org/software/bash/manual/bashref.html管道


    对于那些希望将stdout和stderr永久重定向到文件的用户,请在stderr上grep,但保留stdout以将消息写入tty:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # save tty-stdout to fd 3
    exec 3>&1
    # switch stdout and stderr, grep (-v) stderr for nasty messages and append to files
    exec 2> >(grep -v"nasty_msg">> std.err) >> std.out
    # goes to the std.out
    echo"my first message">&1
    # goes to the std.err
    echo"a error message">&2
    # goes nowhere
    echo"this nasty_msg won't appear anywhere">&2
    # goes to the tty
    echo"a message on the terminal">&3

    这将把command1 stderr重定向到command2 stdin,同时保持command1 stdout不变。

    1
    2
    3
    exec 3>&1
    command1 2>&1 >&3 3>&- | command2 3>&-
    exec 3>&-

    自民党


    我刚刚想出了一个解决方案,用命名管道将stdout发送给一个命令,将stderr发送给另一个命令。

    来吧。

    1
    2
    3
    4
    5
    mkfifo stdout-target
    mkfifo stderr-target
    cat < stdout-target | command-for-stdout &
    cat < stderr-target | command-for-stderr &
    main-command 1>stdout-target 2>stderr-target

    以后移除命名管道可能是个好主意。


    你可以用钢筋混凝土外壳。

    首先安装软件包(小于1MB)。

    这是一个您将如何在rc中丢弃stdout并将stderr管道输送到grep的示例:

    find /proc/ >[1] /dev/null |[2] grep task

    您可以在不离开bash的情况下完成它:

    rc -c 'find /proc/ >[1] /dev/null |[2] grep task'

    正如您可能已经注意到的,您可以通过在管道后面使用括号来指定要管道化的文件描述符。

    标准文件描述符的计算方法如下:

    • 0:标准输入
    • 1:标准输出
    • 2:标准误差

    我喜欢做这样的事情

    composer test &>> /tmp/bob && vim /tmp/bob && rm /tmp/bob


    我试着跟随,发现它也能工作,

    1
    command > /dev/null 2>&1 | grep 'something'