如何将标准错误输出重定向,而不是标准输出?

1253

我有一个程序,它将信息写入stdoutstderr,我需要使用grep处理stderr,而将stdout放在一边。

使用临时文件,可以分两步完成:

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

但是,如何在不使用临时文件的情况下,仅使用一个命令和管道来实现此目标呢?


4
一个类似的问题,但保留标准输出:http://unix.stackexchange.com/questions/3514/how-to-grep-standard-error-stream-stderr - joeytwiddle
5
@Rolf 你的意思是什么?Bash会相当经常地更新;你提出的语法不是很好,因为它与现有约定冲突,但实际上你可以使用|&来同时传递stderr和stdout(这并不完全符合原帖作者的要求,但接近于我猜测你的建议的含义)。 - tripleee
1
@Z4-tier,这是如何向后不兼容和含糊不清的?在bash中2|表示什么意思? - Rolf
1
@Rolf 这两个命令的行为是不同的:echo 2 | tee my_fileecho 2|tee my_file - Z4-tier
2
@Z4-tier 谢谢。2 | 并不是2|,我不会称其为模棱两可,更像是潜在的导致错误,就像echo 2 > /myfileecho 2> /myfile一样,这甚至更加严重。无论如何,这不是为了节省几个按键,我发现其他解决方案很复杂和古怪,而且我还没有完全理解它们,这就是为什么我会启动rc,因为它有一个简单直观的语法来确定你想要重定向的流。 - Rolf
显示剩余3条评论
11个回答

1531

先将标准错误输出重定向到标准输出(使用管道);然后将标准输出重定向到/dev/null(不改变标准错误输出),实现如下:

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所指向的——即管道。


58
我最近偶然发现了/dev/stdout /dev/stderr /dev/stdin,我很好奇它们是不是好的代替方式?我一直认为2>&1有点晦涩。类似这样的命令command 2> /dev/stdout 1> /dev/null | grep 'something' - Mike Lyons
19
你可以使用/dev/stdout等,或者使用/dev/fd/N。除非shell将它们视为特殊情况,否则它们的效率会略微降低;纯数字表示法不涉及按名称访问文件,但是使用设备确实意味着需要查找文件名。你能否测量到这一点还有待商榷。我喜欢数字表示法的简洁性 - 但我已经使用它超过四分之一个世纪了(疼!),所以我不适合评判它在现代世界中的价值。 - Jonathan Leffler
25
@Jonathan Leffler: 我对您的简单文本解释 “将标准错误重定向到标准输出,然后再将标准输出重定向到/dev/null” 有一点异议 —— 因为我们必须从右到左阅读重定向链(而非从左到右),所以我们也应该调整我们的简洁文本解释为:“将标准输出重定向到/dev/null,然后将标准错误重定向到原来标准输出的位置”。 - Kurt Pfeifle
140
相反地,必须从左到右阅读重定向链,因为这是 shell 处理它们的方式。第一步操作是 2>&1,它意味着“将 stderr 连接到当前 stdout 正在连接的文件描述符”。第二步操作是“更改 stdout,使其输出到 /dev/null”,这样 stderr 仍然会输出到原始 stdout,也就是管道。Shell 首先在管道符号处分割内容,所以管道重定向发生在 2>&1>/dev/null 重定向之前,但仅限于此;其他操作是从左到右进行的(从右到左行不通)。 - Jonathan Leffler
16
让我感到惊讶的是,这个方法在Windows系统上也能奏效(只需要将“/dev/null”重命名为Windows等价的“nul”即可)。 - Michael Burr
显示剩余24条评论

409

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

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

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

标准错误现在作为标准输出可用,旧的标准输出被保留在标准错误中。这可能有点过度,但它希望更详细地说明Bash文件描述符(每个进程有九个可用)。


134
最后一个微调是3>&-,用于关闭你从stdout创建的多余描述符。 - Jonathan Leffler
2
我们能否创建一个文件描述符,其中一个具有stderr,另一个具有stderrstdout的组合?换句话说,stderr可以同时发送到两个不同的文件吗? - Stuart
2
@JonathanLeffler 出于好奇,您的调整除了可能澄清观察者的文件描述符(3)的角色之外,是否在性能方面有任何目的? - Jonas Dahlbæk
2
@JonasDahlbæk:这个调整主要是为了整洁。在真正的神秘情况下,它可能会使进程检测到EOF与否的差异,但这需要非常特殊的情况。 - Jonathan Leffler
5
注意:这假定FD 3尚未被使用,不会关闭它,也不会撤消文件描述符1和2的交换,因此您不能继续将其管道传递到另一个命令。有关更多详细信息和解决方法,请参见此答案。对于{ba,z}sh的更清晰语法,请参见此答案 - Tom Hale
显示剩余8条评论

272
在Bash中,你也可以使用进程替换(process substitution)将输出重定向到一个子shell中: process substitution
command > >(stdout pipe)  2> >(stderr pipe)

对于目前的情况:

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

1
输出到屏幕效果非常好。你有任何想法为什么如果我将grep输出重定向到文件中,未经过grep的内容会再次出现吗?在command 2> >(grep 'something' > grep.log)之后,grep.log包含与command 2> ungrepped.log中的ungrepped.log相同的输出。 - Tim
13
使用2> >(stderr管道 >&2)。否则,“stderr管道”的输出将通过“stdlog管道”进行传输。 - ceving
是的!2> >(...) 可以工作,我尝试过 2>&1 > >(...) 但它没有起作用。 - Dee
这里有一个小例子,下次我查找如何做到这一点时可能会帮助我。考虑以下命令... awk -f /new_lines.awk <in-content.txt > out-content.txt 2> >(tee new_lines.log 1>&2 ) 在这种情况下,我想要_同时_看到控制台上的错误输出。但STDOUT将写入输出文件。因此,在子shell中,您需要在括号内将STDOUT重定向回STDERR。虽然这样可以工作,但是tee命令的STDOUT输出最终会出现在out-content.txt文件的末尾。这对我来说似乎不一致。 - will
@datdinhquoc 我以某种方式完成了它,类似于 2>&1 1> >(目标管道) - Alireza Mohamadi
从我的理解来看,你可以在管道中得到stderr和stdout。所有人:请注意,您必须键入... >(...而不是... > (...(空格是错误的) - Cadoiz

233

综合这些答案的最佳方案是,如果您执行以下命令:

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

...那么所有标准输出都将保留为标准输出所有错误输出将被保留为错误输出,但您不会看到任何包含字符串“something”的错误输出行。

这种方法的独特优势在于不会颠倒或丢弃标准输出和错误输出,也不会将它们混在一起,也不会使用任何临时文件。


不加 1>&2command 2> >(grep -v something) 不是一样的吗? - Francesc Rosas
14
没有这样做的话,筛选后的 stderr 会被路由到 stdout。 - Pinko
1
这正是我需要的 - tar 始终会输出“文件在读取时已更改”的信息,因此我只想过滤掉那一行,但仍要查看是否发生了其他错误。所以 tar cfz my.tar.gz mydirectory/ 2> >(grep -v 'changed as we read it' 1>&2) 应该可以解决问题。 - razzed
1
这是对问题唯一有效的答案。 - Spongman
@Pinko你确定你的评论还有效吗?我正在进行一些测试,似乎情况并非如此,至少不再是这样了。 - André Chalella

120

如果你考虑“重定向”和“管道”的实际作用,那么可视化事物会更容易。在bash中,“重定向”和“管道”所做的只有一件事情:修改进程文件描述符0、1和2指向的位置(请参见/proc/[pid]/fd/*)。

当命令行中存在管道或“|”运算符时,首先要发生的是bash创建一个fifo并将左侧命令的FD1指向该fifo,并将右侧命令的FD0指向同一个fifo。

接下来,从左到右评估每个侧面的重定向运算符,并在复制描述符时使用当前设置。这很重要,因为由于管道是首先设置的,因此FD1(左侧)和FD0(右侧)已经与通常可能有的不同,任何对它们的复制都将反映这一事实。

因此,当你键入以下内容时:

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

以下是按顺序发生的事情:

  1. 创建一个管道(fifo),将“command FD1”指向该管道,同时将“grep FD0”也指向该管道。
  2. 将“command FD2”指向“command FD1”当前指向的位置(即那个管道)。
  3. 将“command FD1”指向/dev/null。

因此,“command”写入其FD 2(stderr)的所有输出都会通过管道传递并由另一侧的“grep”读取。而“command”写入其FD 1(stdout)的所有输出都被重定向到了/dev/null。

如果你运行下面的命令:

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

下面是发生的事情:

  1. 创建一个管道,将“command FD 1”和“grep FD 0”指向它
  2. 将“command FD 1”指向/dev/null
  3. 将“command FD 2”指向当前FD 1所指向的位置(/dev/null)

因此,“command”的所有标准输出和标准错误都被重定向到/dev/null中,没有任何内容被发送到管道中,因此“grep”会关闭而不在屏幕上显示任何内容。

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

最后一点。程序是否将某些内容写入FD1或FD2,完全取决于程序员。良好的编程实践应该是将错误消息发送到FD 2,将正常输出发送到FD 1,但通常会发现不规范的编程混合两者或者忽略了这个约定。


7
非常好的回答。我的建议是,在您第一次使用“fifo”时,将其替换为“fifo(一种命名管道)”。我已经使用Linux一段时间了,但不知何故从未学过这是另一个称呼命名管道的术语。这将使我无需查找即可节省时间,但反过来,我也就学不到当我发现这个术语时看到的其他东西了! - Mark Edington
5
请注意,在管道和进程间通信的上下文中,FIFO 只是指命名管道的另一种术语。在更一般的情况下,FIFO 是“先进先出”(First in, first out)的缩写,用于描述 FIFO 数据结构中的插入和移除操作顺序。 - Loomchild
7
当然可以。我的评论的重点是,即使作为经验丰富的开发人员,我从未见过FIFO被用作命名管道的同义词。换句话说,我不知道这个:https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)#Pipes - 在答案中澄清这一点会节省我的时间。 - Mark Edington

53

11
不对,“|&”与“2>&1”等价,都会将标准输出和标准错误输出合并。问题明确要求没有标准输出的输出结果。 - Profpatsch
3
如果使用“|&”,则通过管道将command1的标准错误连接到command2的标准输入; 这是“2>&1 |”的简写形式。这是从您链接的第四段文字直接引用的。 - Profpatsch
12
@Profpatsch说,Ken的回答是正确的。注意他将stdout重定向到null后再合并stdout和stderr,所以在管道中只会得到stderr,因为之前的stdout已被丢弃到/dev/null中。 - Luciano
我使用 mplayer a 2>/dev/null |& grep i(有输出结果) 和 mplayer a >/dev/null |& grep i(没有任何输出!)来测试文件名为a不存在的情况,不知道为什么你的答案不起作用,可能是由于bash版本的问题?后来我发现需要使用fd 3,即 mplayer a 3>/dev/null |& grep i才能得到输出结果。 - 林果皞
4
但我仍然认为你的答案是错误的,>/dev/null |& 扩展成 >/dev/null 2>&1 |,它的意思是将标准输出的inode管道传递给空设备文件,因为没有任何人(#1 #2都连接到/dev/null inode)与标准输出的inode相连。例如,ls -R /tmp/* >/dev/null 2>&1 | grep i 将会得到一个空结果,但是 ls -R /tmp/* 2>&1 >/dev/null | grep i 将允许与标准输出inode相连的#2管道传递数据。 - 林果皞
5
Ken Sharp,我进行了测试,( echo out; echo err >&2 ) >/dev/null |& grep "."没有输出(我们希望得到“err”)。man bash说,“如果使用|& ...是2>&1 |的简写。在命令指定的任何重定向之后,将标准错误隐式重定向到标准输出。”所以首先我们将命令的FD1重定向到null,然后将命令的FD2重定向到FD1所指向的位置,即null,因此grep的FD0没有输入。更深入的解释请参见https://dev59.com/63E95IYBdhLWcg3wXcrd#18342079。 - unhammer

12

如果你想永久地将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

10
这将重定向command1的stderr到command2的stdin,同时保留command1的stdout不变。
exec 3>&1
command1 2>&1 >&3 3>&- | command2 3>&-
exec 3>&-

Taken from LDP


如果我理解正确的话,我们首先要复制当前进程的标准输出(3>&1)。接下来将command1的错误重定向到其输出(2>&1),然后将command1的标准输出指向父进程的标准输出副本(>&3)。清理command1中的重复文件描述符(3>&-)。在command2中,我们只需要删除重复的文件描述符(3>&-)。这些重复是由于父进程分叉自身以创建两个进程而引起的,因此我们只需清理它们。最后,在结束时,我们删除父进程的文件描述符(3>&-)。 - smac89
最终,我们拥有了command1的原始stdout指针,现在它指向父进程的stdout,而它的stderr指向它以前的stdout所在的位置,使其成为command2的新stdout。 - smac89

4

我刚想出一个使用命名管道将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

最好在使用后删除命名管道。


1
你可以使用 rc shell
首先安装该软件包(小于1 MB)。
以下是如何丢弃标准输出并将标准错误输出到grep的示例,使用rc:
find /proc/ >[1] /dev/null |[2] grep task

你可以在不离开Bash的情况下完成它:
rc -c 'find /proc/ >[1] /dev/null |[2] grep task'

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

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

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

1
我认为建议安装完全不同的Shell似乎有些过激。 - xdhmoore
1
@xdhmoore,这有什么大不了的?它不会替换默认 shell,而且该软件只占用了几个 K 的空间。rc 语法用于管道 stderr 比在 bash 中所需做的要好得多,因此我认为值得一提。 - Rolf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接