ChatGPT解决这个技术问题 Extra ChatGPT

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

我有一个将信息写入 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 确切要求的,但非常接近我猜你的提案可能意味着)。
@Z4 层谢谢。 2 | 确实不是 2|,我不会称它为模棱两可,更像是潜在的错误诱导,就像 echo 2 > /myfileecho 2> /myfile 更成问题一样。无论如何,这不是为了节省一些击键,我发现其他解决方案令人费解和古怪,还没有完全理解它们,这就是为什么我会启动 rc ,它有一个简单的语法来确定你想要的流重定向。

j
jtepe

首先将stderr重定向到stdout——管道;然后将 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:我对您的纯文本解释“将标准错误重定向到标准输出,然后将标准输出重定向到 /dev/null”提出了一点问题——因为必须从右到左(而不是从左到右)读取重定向链,所以我们还应该使我们的纯文本解释适应这一点:'将标准输出重定向到 /dev/null,然后将标准错误重定向到标准输出曾经所在的位置'。
@KurtPfeifle:相反!必须从左到右读取重定向链,因为这是 shell 处理它们的方式。第一个操作是 2>&1,意思是“将 stderr 连接到 stdout 当前要去的文件描述符”。第二个操作是“更改标准输出,使其转到 /dev/null”,让标准错误进入原始标准输出,即管道。 shell 首先在管道符号处拆分内容,因此管道重定向发生在 2>&1>/dev/null 重定向之前,但仅此而已;其他操作是从左到右的。 (从右到左不起作用。)
真正让我吃惊的是它也可以在 Windows 上运行(在将 /dev/null 重命名为 Windows 等效项 nul 之后)。
P
Peter Mortensen

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

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

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

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


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

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

command > >(stdout 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)
P
Pinko

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

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

...然后所有标准输出都保留为标准输出,所有标准错误都保留为标准错误,但您不会在标准错误中看到任何包含字符串“某物”的行。

这具有不反转或丢弃 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) 应该可以工作。
M
Michael Martinez

如果您考虑一下“重定向”和“管道”到底发生了什么,那么将事物可视化会容易得多。 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'

这是发生的事情:

创建了一个管道,并且“命令 FD 1”和“grep FD 0”指向它“命令 FD 1”指向 /dev/null “命令 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 - 澄清答案会节省我的时间。
P
Peter Mortensen

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

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

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


不,|& 等于结合了 stdout 和 stderr 的 2>&1。该问题明确要求输出 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(例如 {3 } 将给出空,但 ls -R /tmp/* 2>&1 >/dev/null | grep i 将让与 stdout inode 绑定的#2 管道)。
Ken Sharp,我测试过,( echo out; echo err >&2 ) >/dev/null |& grep "." 没有输出(我们想要“错误”的地方)。 man bash如果 |& is used … 是 2>&1 | 的简写。标准错误到标准输出的这种隐式重定向是在命令指定的任何重定向之后执行的。 所以首先我们将命令的 FD1 重定向到 null,然后我们将命令的 FD2 重定向到 FD1 指向的位置,即。 null,所以 grep 的 FD0 没有输入。有关更深入的说明,请参阅 stackoverflow.com/a/18342079/69663
J
JBD

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

t
theDolphin

这会将 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 所在的位置,使其成为 command2 的新 stdout。
T
Tripp Kinetics

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

您可以使用 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 中必须做的要好得多,所以我认为值得一提。
l
lasteye

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

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

不工作。它只是将 stderr 发送到终端。忽略管道。