我怎样才能管道标准错误,而不是标准输出?

Posted

技术标签:

【中文标题】我怎样才能管道标准错误,而不是标准输出?【英文标题】:How can I pipe stderr, and not stdout? 【发布时间】:2011-01-21 12:27:00 【问题描述】:

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

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

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

但是如何在没有临时文件的情况下使用一个命令和管道来实现呢?

【问题讨论】:

一个类似的问题,但保留标准输出:unix.stackexchange.com/questions/3514/… 这个问题是针对 Bash 但值得一提的是与 Bourne / Almquist shell 相关的 article。 我期待这样的事情:command 2| othercommand。 Bash 是如此完美,以至于开发在 1982 年就结束了,所以恐怕我们永远不会在 bash 中看到它。 @Rolf 你是什么意思? Bash 相当定期地获得更新;您提出的语法不是很好,因为它与现有约定冲突,但您实际上可以使用 |& 来管道 stderr 和 stdout (这不是 OP 确切要求的,但非常接近我猜你的提案可能意味着)。 @tripleee 我的意思是功能或语法的开发似乎已经结束或正在以非常缓慢的速度发生,因此我们似乎被几十年前确定的语法所困。 【参考方案1】:

首先将标准错误重定向到标准输出——管道;然后将stdout重定向到/dev/null(不改变stderr的去向):

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

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

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

【讨论】:

前几天我偶然发现了 /dev/stdout /dev/stderr /dev/stdin,我很好奇这些是否是做同样事情的好方法?我一直认为 2>&1 有点混淆。比如:command 2> /dev/stdout 1> /dev/null | grep 'something' 你可以使用/dev/stdout等,或者使用/dev/fd/N。除非 shell 将它们视为特殊情况,否则它们的效率会略微降低;纯数字符号不涉及按名称访问文件,但使用设备确实意味着文件名查找。您是否可以衡量这一点值得商榷。我喜欢数字符号的简洁性——但我已经使用了很长时间(超过 25 年;哎呀!),以至于我没有资格判断它在现代世界中的优点。 @Jonathan Leffler:我对你的纯文本解释有点问题 'Redirect stderr to stdout and then stdout to /dev/null' -- 因为必须从右到左(而不是从左到右)读取重定向链,我们也应该将我们的纯文本解释调整为:'Redirect stdout to /dev/null, and then stderr to where stdout曾经是'. @KurtPfeifle:相反!必须从左到右读取重定向链,因为这是 shell 处理它们的方式。第一个操作是2>&1,意思是“将stderr 连接到stdout当前要去的文件描述符”。第二个操作是“更改标准输出,使其进入/dev/null”,让标准错误进入原始标准输出,即管道。 shell首先在管道符号处拆分事物,因此管道重定向发生在2>&1>/dev/null重定向之前,但仅此而已;其他操作是从左到右的。 (从右到左不起作用。) 真正让我吃惊的是它也可以在 Windows 上运行(在将 /dev/null 重命名为 Windows 等效项 nul 之后)。【参考方案2】:

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

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

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

标准错误现在可作为标准输出使用,旧的标准输出保留在标准错误中。这可能有点矫枉过正,但希望能提供有关 Bash 文件描述符的更多详细信息(每个进程有 9 个可用)。

【讨论】:

最后的调整是3>&- 以关闭您从标准输出创建的备用描述符 我们能否创建一个文件描述符,它包含stderr 和另一个包含stderrstdout 的组合?换句话说,stderr 可以同时访问两个不同的文件吗? @JonathanLeffler 出于好奇,除了为观察者阐明文件描述符 (3) 的作用之外,您的调整是否在性能方面有任何目的? @JonasDahlbæk:调整主要是整洁的问题。在真正神秘的情况下,它可能会在进程检测和不检测 EOF 之间产生差异,但这需要非常特殊的情况。 注意:这假定 FD 3 尚未在使用中,不会关闭它,也不会撤消文件描述符 1 和 2 的交换,因此您可以不要继续将它传递给另一个命令。有关更多详细信息和解决方法,请参阅this answer。如需更简洁的 ba,zsh 语法,请参阅 this answer。【参考方案3】:

在 Bash 中,您还可以使用 process substitution 重定向到子 shell:

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

对于手头的情况:

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

【讨论】:

非常适合输出到屏幕。如果我将 grep 输出重定向到文件中,您知道为什么会再次出现未经处理的内容吗?在command 2> >(grep 'something' > grep.log) grep.log 包含与来自command 2> ungrepped.log的 ungrepped.log 相同的输出 使用2> >(stderr pipe >&2)。否则“stderr 管道”的输出将通过“stdlog 管道”。 是的!,2> >(...) 有效,我试过 2>&1 > >(...) 但它没有 这是一个小例子,下次我查找如何执行此操作时可能会对我有所帮助。考虑以下... 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)【参考方案4】:

对于那些想要将 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

【讨论】:

【参考方案5】:

结合最好的答案,如果你这样做:

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

...然后所有的标准输出都被保留为标准输出并且所有的标准错误都被保留为标准错误,但是你不会在标准错误中看到任何包含字符串“something”的行。

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

【讨论】:

command 2> >(grep -v something)(没有1>&2)不一样吗? 不,没有它,过滤后的标准错误最终会被路由到标准输出。 这就是我所需要的 - tar 总是为一个目录输出“文件在我们读取时已更改”,所以只想过滤掉那一行,但看看是否发生任何其他错误。所以tar cfz my.tar.gz mydirectory/ 2> >(grep -v 'changed as we read it' 1>&2) 应该可以工作。【参考方案6】:

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

当一个管道或“|”操作符出现在命令行上,首先发生的是 bash 创建一个 fifo 并将左侧命令的 FD 1 指向该 fifo,并将右侧命令的 FD 0 指向同一个 fifo。

接下来,从左到右评估每一侧的重定向运算符,并且每当出现重复描述符时使用当前设置。这一点很重要,因为由于首先设置了管道,FD1(左侧)和 FD0(右侧)已经与它们通常的状态有所不同,任何重复都将反映这一事实。

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

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

这是按顺序发生的:

    已创建管道 (fifo)。 “command FD1”指向这个管道。 “grep FD0”也指向这个管道 “command FD2”指向“command FD1”当前指向的位置(管道) “command FD1”指向/dev/null

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

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

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)

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

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

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

【讨论】:

非常好的答案。我的一个建议是将您第一次使用的“fifo”替换为“fifo(命名管道)”。我使用 Linux 已经有一段时间了,但不知何故从未学会这是命名管道的另一个术语。这本来可以让我免于查找它,但是当我发现它时,我又不会学到我看到的其他东西! @MarkEdington 请注意,FIFO 只是命名管道的另一个术语在管道和 IPC 的上下文中。在更一般的上下文中,FIFO 表示先进先出,它描述了从队列数据结构中插入和删除。 @Loomchild 当然可以。我评论的重点是,即使作为一个经验丰富的开发人员,我也从未见过 FIFO 被用作命名管道的 同义词。换句话说,我不知道这一点:en.wikipedia.org/wiki/FIFO_(computing_and_electronics)#Pipes - 澄清答案会节省我的时间。【参考方案7】:

我尝试跟随,发现它也有效,

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

【讨论】:

不起作用。它只是将 stderr 发送到终端。忽略管道。【参考方案8】:

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

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

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

【讨论】:

不,|& 等于 2>&1,它结合了 stdout 和 stderr。该问题明确要求输出 without 标准输出。 „如果使用'|&',则command1的标准错误通过管道连接到command2的标准输入;它是 2>&1 | 的简写”从您的链接的第四段逐字记录。 @Profpatsch:Ken 的回答是正确的,看看他在结合 stdout 和 stderr 之前将 stdout 重定向到 null,所以你只会进入管道 stderr,因为 stdout 之前被删除到 /dev/null . 但我仍然发现你的答案是错误的,>/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 将管道)。 Ken Sharp,我测试过,( echo out; echo err >&2 ) >/dev/null |& grep "." 没有输出(我们想要“错误”的地方)。 man bash如果使用 |& … 是 2>&1 | 的简写。标准错误到标准输出的这种隐式重定向是在命令指定的任何重定向之后执行的。 所以首先我们将命令的 FD1 重定向到 null,然后我们将命令的 FD2 重定向到 FD1 指向的位置,即。 null,所以 grep 的 FD0 没有输入。请参阅***.com/a/18342079/69663 以获得更深入的解释。【参考方案9】:

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

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

取自LDP

【讨论】:

所以如果我理解正确的话,我们首先复制当前进程的标准输出 (3>&1)。接下来将command1 的错误重定向到其输出(2>&1),然后将command1 的标准输出point 重定向到父进程的标准输出副本(>&3)。清理command1 (3>&-) 中的重复文件描述符。在command2 中,我们只需要删除重复的文件描述符(3>&-)。这些重复是在父进程自己创建两个进程时引起的,所以我们只是清理它们。最后最后我们删除父进程的文件描述符(3>&-)。 最后,我们有command1的原始stdout指针,现在指向父进程的stdout,而它的stderr指向它原来的stdout所在的位置,使其成为新的stdout command2.【参考方案10】:

我刚刚想出了一个解决方案,使用命名管道将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

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

【讨论】:

支持 FIFO 使用【参考方案11】:

您可以使用rc shell。

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

这是一个示例,说明如何在rc 中丢弃标准输出并将标准错误通过管道传送到grep:

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

你可以在不离开 Bash 的情况下做到这一点:

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

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

标准文件描述符是这样计算的:

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

【讨论】:

建议安装一个完全不同的外壳对我来说似乎有点过激。 @xdhmoore 这有什么过激的?它不会取代默认的 shell,软件只占用几 K 的空间。管道 stderr 的 rc 语法比您在 bash 中所做的要好得多,所以我认为值得一提。

以上是关于我怎样才能管道标准错误,而不是标准输出?的主要内容,如果未能解决你的问题,请参考以下文章

aplay 管道使用文件而不是标准输入和标准输出来记录

文件输入输出的管理以及管道的使用

管道弹出标准错误和标准输出

如何欺骗应用程序认为其标准输出是终端,而不是管道

linux学习第一周;标准输入输出和错误重定向与管道

标准输入输出和管道