我如何用管道输送stderr而不是stdout?


981

我有一个要写入信息的程序stdoutstderr,我需要grep通过报应的标准错误,而忽视标准输出

我当然可以分两步完成:

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

但我希望能够在没有临时文件的情况下执行此操作。有任何智能管道技巧吗?


一个类似的问题,但保留标准输出:unix.stackexchange.com/questions/3514/…–
joeytwiddle

这个问题是针对Bash的,但值得一提的是有关Bourne / Almquist shell的相关文章
Stephen Niedzielski

10
我期待这样的事情:command 2| othercommand。Bash非常完美,以致于1982年开发结束,所以恐怕我们再也不会看到Bash了。
罗尔夫

Answers:


1188

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

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

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

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


44
前几天我只是偶然发现/ dev / stdout / dev / stderr / dev / stdin,我很好奇这是否是做同一件事的好方法?我一直认为2>&1有点混乱。就像这样:command 2> /dev/stdout 1> /dev/null | grep 'something'
Mike Lyons,

17
您可以使用/dev/stdoutet al或使用/dev/fd/N。除非外壳将它们视为特殊情况,否则它们的效率将略有降低;纯数字符号不涉及按名称访问文件,但是使用设备确实意味着对文件名的查找。您是否可以衡量这一点值得商bat。我喜欢数字符号的简洁性-但是我使用它已经很长时间了(超过25个世纪;哎呀!),以至于我没有资格判断它在现代世界中的优点。
乔纳森·勒夫勒

23
@Jonathan Leffler:我对您的纯文本解释“将stderr重定向到stdout,然后将 stdout 重定向到/ dev / null”有点问题,因为必须从右到左(而不是从左到右)读取重定向链,还应该使我们的纯文本解释适应以下情况:“将stdout重定向到/ dev / null,然后将stderr重定向到stdout曾经所在的位置”
Kurt Pfeifle 2012年

116
@KurtPfeifle:换位!必须从左到右读取重定向链,因为那是Shell处理它们的方式。第一个操作是2>&1,这意味着“将stderr连接到stdout 当前要去的文件描述符”。第二个操作是“更改stdout,使其转到/dev/null”,将stderr转到原始stdout,即管道。Shell首先在管道符号处分割内容,因此管道重定向发生在2>&1>/dev/null重定向之前,仅此而已;其他操作从左到右。(从右到左不起作用。)
乔纳森·莱夫勒

14
让我为此感到惊讶的是,它也可以在Windows上运行(重命名/dev/null为Windows 后nul)。
Michael Burr

364

或交换标准错误和标准输出的输出,请使用:

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

这将创建一个新的文件描述符(3),并将其分配到与1(标准输出)相同的位置,然后将fd 1(标准输出)分配给与fd 2(标准错误)相同的位置,最后分配fd 2(标准错误) )与fd 3(标准输出)的位置相同。

现在可以使用标准误差作为标准输出,并且将旧的标准输出保留在标准误差中。这可能有些大材小用,但希望能提供有关Bash文件描述符的更多详细信息(每个进程有9个可用)。


100
最后的调整将是3>&-关闭您从stdout创建的备用描述符
Jonathan Leffler 2012年

1
我们可以创建一个具有文件描述符stderr具有的结合,另一个stderrstdout?换句话说,可以一次stderr转到两个不同的文件吗?
斯图尔特

以下内容仍将错误输出到stdout。我想念什么?ls -l not_a_file 3>&1 1>&2 2>&3> errors.txt
2014年

1
@JonasDahlbæk:调整主要是整理问题。在真正神秘的情况下,可能会在过程检测与未检测到EOF之间产生区别,但这需要非常特殊的环境。
乔纳森·勒夫勒

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

218

在Bash中,您还可以使用流程替换重定向到子shell :

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

对于当前情况:

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

1
对于输出到屏幕效果很好。您是否知道如果将grep输出重定向到文件中,为什么未删除的内容再次出现?之后command 2> >(grep 'something' > grep.log)grep.log包含来自输出作为ungrepped.log同一样command 2> ungrepped.log
蒂姆·

9
使用2> >(stderr pipe >&2)。否则,“ stderr管道”的输出将通过“ stdlog管道”。
2013年

是的!,2> >(...)有效,我尝试过2>&1 > >(...)但没有尝试
daddinhquoc

这是一个小示例,下次我查找如何执行此操作时可能会有所帮助。请看下面...... awk -f /new_lines.awk <in-content.txt > out-content.txt 2> >(tee new_lines.log 1>&2 ) 在这种情况下,我想看看有什么出来作为我的控制台上的错误。但是STDOUT将转到输出文件。因此,在子外壳内,您需要将该STDOUT重定向回括号内的STDERR。在此期间,该tee命令的STDOUT输出将在out-content.txt文件末尾结束。这对我来说似乎很矛盾。

@datdinhquoc我以某种方式做到了2>&1 1> >(dest pipe)
Alireza Mohamadi

195

如果您这样做,请结合以下最佳答案:

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

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

这具有不反转或丢弃stdout和stderr,不将它们混在一起或使用任何临时文件的独特优势。


是不是command 2> >(grep -v something)(不1>&2)一样吗?
Francesc Rosas 2013年

11
不,没有它,经过过滤的stderr最终将被路由到stdout。
Pinko

1
这就是我所需要的-tar始终为目录输出“读取文件时文件已更改”,因此只想过滤掉那一行,然后查看是否发生其他错误。所以tar cfz my.tar.gz mydirectory/ 2> >(grep -v 'changed as we read it' 1>&2)应该工作。
2016年

说“从字符串开始”是错误的。呈现的grep语法没有什么可以使其仅排除以给定字符串开头的行。如果行中任何位置包含给定的字符串,则这些行将被排除。
Mike Nakis

@MikeNakis谢谢-固定!(那是我原来的答案草稿
留下

102

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

管道或“ |” 如果操作员出现在命令行中,则第一件事就是bash创建了一个fifo,并将左侧命令的FD 1指向该fifo,并将右侧命令的FD 0指向了同一fifo。

接下来,从左到右评估每一侧的重定向运算符,并在描述符重复出现时使用当前设置。这一点很重要,因为自从首先建立管道以来,FD1(左侧)和FD0(右侧)已经从它们通常的状态中进行了更改,并且任何重复都将反映这一事实。

因此,当您键入类似以下内容:

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

这是按顺序发生的事情:

  1. 管道(fifo)已创建。“命令FD1”指向该管道。“ grep FD0”也指向该管道
  2. “命令FD2”指向“命令FD1”当前指向的位置(管道)
  3. “命令FD1”指向/ dev / null

因此,“命令”写入其FD 2(stderr)的所有输出均进入管道,并由另一侧的“ grep”读取。“命令”写入其FD 1(stdout)的所有输出均进入/ dev / null。

如果相反,则运行以下命令:

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

这是发生了什么:

  1. 创建了一个管道,并指向该管道的“命令FD 1”和“ grep FD 0”
  2. “命令FD 1”指向/ dev / null
  3. “ FD 2命令”指向FD 1当前指向的位置(/ dev / null)

因此,“命令”中的所有stdout和stderr都转到/ dev / null。没有任何东西进入管道,因此“ grep”将关闭而不在屏幕上显示任何内容。

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

最后的笔记。程序是向FD1还是FD2写东西,完全取决于程序员。良好的编程习惯规定,错误消息应该发送到FD 2,正常输出应该发送到FD 1,但是您经常会发现混合这两者的草率编程,或者忽略了约定。


6
真是个好答案。我的一个建议是将“ fifo”的首次使用替换为“ fifo(命名管道)”。我已经使用Linux一段时间了,但是以某种方式从未设法得知这是命名管道的另一个术语。这本来可以使我免于查找,但是再次发现时,我将不会再学到其他东西了!
Mark Edington

3
@MarkEdington请注意,在管道和IPC上下文中,FIFO只是命名管道的另一个术语。在更一般的上下文中,FIFO表示先进先出,它描述了从队列数据结构中插入和删除。
Loomchild '17

5
@Loomchild当然。我的评论的重点是,即使作为经验丰富的开发人员,我也从未见过FIFO被用作命名管道的同义词。换句话说,我不知道这一点:en.wikipedia.org/wiki/FIFO_(computing_and_electronics)#Pipes-明确说明答案将节省我的时间。
马克·爱丁顿

39

如果您使用的是Bash,请使用:

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

http://www.gnu.org/software/bash/manual/bashref.html#Pipelines


4
不,|&等于2>&1将stdout和stderr组合在一起。该问题明确要求没有标准输出的输出。
Profpatsch 2014年

3
“如果使用'|&',则命令1的标准错误通过管道连接到命令2的标准输入;它是2>&1 |的简写。从您的链接的第四段中逐字获取。
Profpatsch 2014年

9
@Profpatsch:Ken的答案是正确的,看起来他在组合stdout和stderr之前将stdout重定向为null,因此您只能进入stderr管道,因为stdout先前已被丢弃到/ dev / null。
卢西亚诺

3
但是我仍然发现您的答案是错误的,请>/dev/null |&扩展至>/dev/null 2>&1 | ,这意味着stdout inode是空的,因为没有人(#1#2都绑定到/ dev / null inode)绑定到stdout inode(例如,ls -R /tmp/* >/dev/null 2>&1 | grep i将为空,但ls -R /tmp/* 2>&1 >/dev/null | grep i将让#与stdout inode绑定的2将通过管道传输)。
水果

3
肯·夏普(Ken Sharp),我进行了测试,( echo out; echo err >&2 ) >/dev/null |& grep "."但未提供任何输出(我们需要“错误”的位置)。man bash如果使用了&&…是2>&1 |的简写。将标准错误隐式重定向到标准输出是在命令指定的任何重定向之后执行的。因此,首先我们将命令的FD1重定向到null,然后将命令的FD2重定向到FD1指向的位置。null,因此grep的FD0没有输入。有关更深入的说明,请参见stackoverflow.com/a/18342079/69663
2013年

11

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

# 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

6

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

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

摘自自民党


2

我只是想出了一种使用命名管道发送stdout到一个命令和stderr另一个命令的解决方案。

开始。

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

之后删除命名管道可能是一个好主意。


0

您可以使用rc shell

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

这个示例说明了如何将标准输出和管道标准错误丢弃到greprc

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

您无需离开Bash就可以做到:

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

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

标准文件描述符的编号方式如下:

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

-3

我尝试关注,发现它也能正常工作,

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

不起作用 它只是将stderr发送到终端。忽略管道。
Tripp Kinetics
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.