如何管stderr,而不是stdout?

933 bash grep stdout pipe stderr

我有一个程序可以将信息写入stdoutstderr,并且我需要grep通过什么来到stderr,而忽略了stdout.

我当然可以分2步完成:

command > /dev/null 2> temp.file
grep 'something' temp.file
Run Code Online (Sandbox Code Playgroud)

但我宁愿能够在没有临时文件的情况下做到这一点.有没有任何智能管道技巧?

Jon*_*ler 1114

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

command 2>&1 >/dev/null | grep 'something'
Run Code Online (Sandbox Code Playgroud)

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

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

  • @KurtPfeifle:au对比!必须从左到右读取重定向链,因为这是shell处理它们的方式.第一个操作是`2>&1`,这意味着'将stderr连接到stdout是_currently_去'的文件描述符.第二个操作是'更改stdout,所以它转到`/ dev/null`',让stderr转到原来的stdout,即管道.shell首先在管道符号处拆分东西,因此,管道重定向发生在`2>&1`或`>/dev/null`重定向之前,但这就是全部; 其他操作是从左到右.(从右到左不起作用.) (103认同)
  • 我前几天偶然发现了/ dev/stdout/dev/stderr/dev/stdin,我很好奇这些是不是做同样事情的好方法?我一直认为2>&1有点混淆.所以类似:`command 2>/dev/stdout 1>/dev/null | grep'thing'` (38认同)
  • @Jonathan Leffler:我对你的纯文本解释有点问题*'将stderr重定向到stdout然后**然后**stdout到/ dev/null'* - 因为必须从右到左读取重定向链(不是从从左到右),我们还应该对我们的纯文本解释进行调整:*'将stdout重定向到/ dev/null,然后将stderr重定向到stdout以前的位置'*. (21认同)
  • 您可以使用`/ dev/stdout`等,或使用`/ dev/fd/N`.除非贝壳将它们视为特殊情况,否则它们的效率会略微降低; 纯数字表示法不涉及按名称访问文件,但使用设备确实意味着文件名查找.你是否可以衡量这是值得商榷的.我喜欢数字符号的简洁 - 但我已经使用它很长时间了(超过四分之一世纪;哎哟!),我没有资格判断它在现代世界中的优点. (15认同)
  • 令我惊讶的是,它在Windows上也适用(在将`/ dev/null`重命名为Windows等效,'nul`之后). (11认同)
  • @KurtPfeifle:我以前也很难理解重定向所需的顺序.直到我写了一个小shell,并意识到如何使用`fd_redirect_into = open("file")完成重定向; 关闭(fd_to_redirect); DUP(fd_redirect_into); close(fd_redirect_into);`系统调用序列. (3认同)
  • @ legends2k:总的来说,`command>/dev/null |&grep'thing'`相当于`command 2>&1>/dev/null | grep'thing'`.另请参阅Bash手册中的[pipelines](http://www.gnu.org/software/bash/manual/bash.html#Pipelines).实际上,它说:_如果使用'|&'',command1的标准错误除了标准输出外,还通过管道连接到command2的标准输入; 它是'2>&1 |`的简写.标准错误到标准输出的隐式重定向是在命令指定的任何重定向之后执行的.嗯,它就是这样; RTFM适用. (3认同)
  • `&> 1`是一个Bash新词; 在我(古老,胡思乱想)的视图中,它很可怕,我不会用驳船杆触碰它.如果我在[`&>`](https://www.gnu.org/software/bash/manual/bash.html#Redirecting-Standard-Output-and-Standard-Error)上阅读Bash手册trusted,`&> 1`表示'将标准输出和标准错误重定向到名为`1`的文件 - 而不是文件描述符编号1.我没有尝试过它; 我目前没有计划这样做.我发现它不是shell语法的有用补充. (3认同)
  • @JFSebastian 感谢您的回复。这是一个非常有趣的方法!不过,有人可能会检查 bash 版本。例如,在 **GNU bash, version 3.2.53(1)-release-(x86_64-apple-darwin13)** 中,它确实在意外标记 &* 附近引发了*语法错误。在 linux 中使用 **GNU Bash 4.3** ..没问题! (2认同)
  • @legends2k:请注意,首先管道被分成一系列通过管道连接的命令。`|&` 是 `|` 的特例;它将标准输出和标准错误重定向到管道。然后从左到右处理普通重定向。因此,“command > /dev/null |& grep 'something'”在“|&”处分割管道。在LHS上,标准输出和标准错误被重定向到管道;然后 `>/dev/null` 重定向将标准输出发送到 `/dev/null`,因此只有标准错误会发送到管道。`grep` 从管道中读取,寻找“东西”。 (2认同)

小智 346

或者在使用时交换stderr和stdout的输出: -

command 3>&1 1>&2 2>&3
Run Code Online (Sandbox Code Playgroud)

这将创建一个新的文件描述符(3)并将其分配到与1(stdout)相同的位置,然后将fd 1(stdout)分配给与fd 2(stderr)相同的位置,最后将fd 2(stderr)分配给相同的位置放置为fd 3(标准输出).Stderr现在可以作为stdout使用,旧stdout保存在stderr中.这可能有点过分,但希望提供有关bash文件描述符的更多详细信息(每个进程有9个可用).

  • 最后的调整是`3>& - `来关闭你从stdout创建的备用描述符 (83认同)
  • **注意**:这假设 FD 3 尚未使用,不会关闭它,并且不会撤消文件描述符 1 和 2 的交换,因此您不能继续将其传递给另一个命令. 有关更多详细信息和解决方法,请参阅 [此答案](/sf/answers/3680264941/)。有关 {ba,z}sh 更简洁的语法,请参阅 [此答案](/sf/answers/3680256121/)。 (3认同)
  • 我们可以创建一个包含“stderr”的文件描述符和另一个包含“stderr”和“stdout”组合的文件描述符吗?换句话说,“stderr”可以同时转到两个不同的文件吗? (2认同)
  • @JonathanLeffler 出于好奇,除了可能为观察者澄清文件描述符(3)的作用之外,您的调整是否在性能方面有任何目的? (2认同)
  • @JonasDahlbæk:调整主要是整洁问题。在真正神秘的情况下,它可能会在进程检测和不检测 EOF 之间产生差异,但这需要非常特殊的情况。 (2认同)

小智 204

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

command > >(stdlog pipe)  2> >(stderr pipe)
Run Code Online (Sandbox Code Playgroud)

对于手头的情况:

command 2> >(grep 'something') >/dev/null
Run Code Online (Sandbox Code Playgroud)

  • 使用`2 >>(stderr pipe>&2)`.否则,"stderr管道"的输出将通过"stdlog管道". (8认同)

Pin*_*nko 184

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

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

...然后所有stdout都保存为stdout,并且所有stderr都保存为stderr,但是你不会在stderr中看到包含字符串"something"的任何行.

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

  • 不,没有它,过滤后的stderr最终被路由到stdout. (11认同)

Mic*_*nez 96

如果你想一想"重定向"和"管道"的真实情况,那么可视化的东西要容易得多.bash中的重定向和管道做一件事:修改进程文件描述符0,1和2指向的位置(参见/ proc/[pid]/fd/*).

管道或"|" 运算符出现在命令行中,首先要发生的是bash创建一个fifo并将左侧命令的FD 1指向此fifo,并将右侧命令的FD 0指向同一个fifo.

接下来,从左到右评估每一侧的重定向运算符,并且每当重复描述符时使用当前设置.这很重要,因为由于管道是先设置的,所以FD1(左侧)和FD0(右侧)已经从它们通常的状态改变,而这些的重复将反映这一事实.

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

command 2>&1 >/dev/null | grep 'something'
Run Code Online (Sandbox Code Playgroud)

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

  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'
Run Code Online (Sandbox Code Playgroud)

这是发生的事情:

  1. 创建管道并指向"命令FD 1"和"grep FD 0"
  2. "命令FD 1"指向/ dev/null
  3. "命令FD 2"指向FD 1当前指向的位置(/ dev/null)

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

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

最后一点.程序是否将某些内容写入FD1或FD2,完全取决于程序员.良好的编程习惯要求错误消息应该转到FD 2并且正常输出转换为FD 1,但是您经常会发现混合两者的草率编程或者忽略惯例.

  • 真的很好的答案.我的一个建议是用"fifo(命名管道)"取代你第一次使用"fifo".我一直在使用Linux一段时间,但不知何故从未设法学习这是命名管道的另一个术语.这样可以让我免于查找,但是当我发现它时,我不会再学到其他的东西! (6认同)
  • @Loomchild当然.我的评论的意思是,即使作为一名经验丰富的开发人员,我也从未将FIFO用作命名管道的*同义词*.换句话说,我不知道这个:https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)#Pipes - 澄清在答案中会节省我的时间. (5认同)
  • @MarkEdington请注意,在管道和IPC*的上下文中,FIFO只是命名管道*的另一个术语.在更一般的上下文中,FIFO表示先进先出,其描述了从队列数据结构的插入和删除. (3认同)

Ken*_*arp 35

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

command >/dev/null |& grep "something"
Run Code Online (Sandbox Code Playgroud)

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

  • @Profpatsch:Ken的答案是正确的,看看他在将stdout和stderr组合之前将stdout重定向为null,所以你只能在stderr中使用std,因为stdout之前已经被droout转换为/ dev/null. (8认同)
  • 不,`|&`等于将stdout和stderr组合在一起的`2&gt;&1`。该问题明确要求输出* without * stdout。 (3认同)
  • "如果使用'|&',则command1的标准错误通过管道连接到command2的标准输入; 它是2>&1 |"从你的链接的第四段逐字逐句采取的简写. (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"."`没有输出(我们想要"err").`man bash`说*如果|&被使用...是2>&1 |的简写.标准错误到标准输出的隐式重定向是在命令指定的任何重定向之后执行的.*所以首先我们将命令的FD1重定向到null,然后我们将命令的FD2重定向到FD1指向的位置,即.null,所以grep的FD0没有输入.有关更深入的说明,请参见http://stackoverflow.com/a/18342079/69663. (2认同)

JBD*_*JBD 10

对于那些想要将stdout和stderr永久重定向到文件的人,grep on stderr,但是让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
Run Code Online (Sandbox Code Playgroud)


the*_*hin 5

这会将command1 stderr重定向到command2 stdin,同时保留command1 stdout.

exec 3>&1
command1 2>&1 >&3 3>&- | command2 3>&-
exec 3>&-
Run Code Online (Sandbox Code Playgroud)

取自自民党