如何使用grep标准错误流(stderr)?


76

我正在使用ffmpeg获取音频剪辑的元信息。但是我无法重复。

    $ ffmpeg -i 01-Daemon.mp3  |grep -i Duration
    FFmpeg version SVN-r15261, Copyright (c) 2000-2008 Fabrice Bellard, et al.
      configuration: --prefix=/usr --bindir=/usr/bin 
      --datadir=/usr/share/ffmpeg --incdir=/usr/include/ffmpeg --libdir=/usr/lib
      --mandir=/usr/share/man --arch=i386 --extra-cflags=-O2 
      ...

我检查了一下,将此ffmpeg输出定向到stderr。

$ ffmpeg -i 01-Daemon.mp3 2> /dev/null

因此,我认为grep无法读取错误流以捕获匹配的行。我们如何启用grep读取错误流?

使用nixCraft链接,我将标准错误流重定向到标准输出流,然后grep工作了。

$ ffmpeg -i 01-Daemon.mp3 2>&1 | grep -i Duration
  Duration: 01:15:12.33, start: 0.000000, bitrate: 64 kb/s

但是,如果我们不想将stderr重定向到stdout怎么办?


1
我相信grep只能在stdout上运行(尽管我找不到规范的源来支持它),这意味着任何流都需要首先转换为stdout。
Stefan Lasiewski

9
@Stefan:grep只能在stdin上运行。它是由外壳程序创建的管道,它将grep的stdin连接到另一个命令的stdout。而且外壳程序只能将标准输出连接到标准输入。
吉尔斯(Gilles)2010年

糟糕,您是对的。我想这就是我真正想说的,我只是没有想通。谢谢@吉尔斯。
Stefan Lasiewski

您是否仍要打印标准输出?
Mikel

Answers:


52

如果您使用的是bash为什么不使用匿名管道,实质上是phunehehe所说的简写:

ffmpeg -i 01-Daemon.mp3 2> >(grep -i Duration)


3
+1好!仅重击,但比其他方法更干净。
Mikel

1
+1我用它来检查cp输出cp -r dir* to 2> >(grep -v "svn")
Betlista

5
如果您想再次在stderr上过滤后的重定向输出,请添加一个>&2,例如command 2> >(grep something >&2)
tlo

2
请说明其运作方式。2>将stderr重定向到文件,我明白了。这是从file读取的>(grep -i Duration)。但是文件从不存储?这种技术叫什么,以便我能进一步了解它?
MarkoAvlijaš18年

1
创建一个管道(不是文件)是“语法糖”,完成后删除该管道。它们实际上是匿名的,因为在文件系统中没有给它们指定名称。Bash称这种过程替代。
JE队列

48

除了从stdout到stdin之外,所有普通的shell(甚至zsh)都不允许管道。但是,所有的Bourne风格的shell都支持文件描述符重新分配(如中所述1>&2)。因此,您可以暂时将stdout转移到fd 3,将stderr转移到stdout,然后再将fd 3放回stdout。如果stuff在stdout上产生一些输出,而在stderr上产生一些输出,并且您想filter在错误输出上应用而保持标准输出不变,则可以使用{ stuff 2>&1 1>&3 | filter 1>&2; } 3>&1

$ stuff () {
  echo standard output
  echo more output
  echo standard error 1>&2
  echo more error 1>&2
}
$ filter () {
  grep a
}
$ { stuff 2>&1 1>&3 | filter 1>&2; } 3>&1
standard output
more output
standard error

这种方法有效。不幸的是,在我的情况下,如果返回一个非零的返回值,它会丢失-返回的值对我来说是0。这可能并不总是发生,但是在我目前正在研究的情况下会发生。有什么办法可以保存吗?
Faheem Mitha

2
@FaheemMitha不确定您在做什么,但可能pipestatus会有所帮助
Gilles

1
@FaheemMitha,这也set -o pipefail可能会有所帮助,具体取决于您要对错误状态进行的处理。(例如,如果您set -e打开时因任何错误而失败,则可能set -o pipefail还需要。)
通配符

@Wildcard是的,我已set -e打开,无法发生任何错误。
Faheem Mitha

rcshell允许管道标准错误。请参阅下面的答案
罗尔夫

20

这类似于phunehehe的“临时文件技巧”,但改用命名管道,使您可以在输出结果时稍微接近结果,这对于长时间运行的命令非常方便:

$ mkfifo mypipe
$ command 2> mypipe | grep "pattern" mypipe

在此构造中,stderr将定向到名为“ mypipe”的管道。由于grep已使用文件参数调用,因此它不会寻找STDIN作为输入。不幸的是,完成后,您仍然必须清理该命名管道。

如果您使用的是Bash 4,则有一个的快捷语法command1 2>&1 | command2,即command1 |& command2。但是,我认为这纯粹是语法快捷方式,您仍在将STDERR重定向到STDOUT。



1
|&语法非常适合清除肿的Ruby stderr堆栈跟踪。我终于可以在没有太多麻烦的情况下复制这些内容。
pgr

8

Gilles和Stefan Lasiewski的答案都不错,但是这种方式比较简单:

ffmpeg -i 01-Daemon.mp3 2>&1 >/dev/null | grep "pattern"

我假设您不希望ffmpeg's输出stdout。

这个怎么运作:

  • 管道先
    • ffmpeg和grep启动,ffmpeg的stdout转到grep的stdin
  • 重定向,从左到右
    • ffmpeg的stderr设置为它的stdout是什么(当前是管道)
    • ffmpeg的标准输出设置为/ dev / null

这种描述使我感到困惑。最后两个项目符号使我想到“将stderr重定向到stdout”,然后“将stdout(现在带有stderr)重定向到/ dev / null”。但是,这并不是真正的作用。这些说法似乎是相反的。
史蒂夫

1
@Steve第二个项目符号中没有“ with stderr”。您是否看到过unix.stackexchange.com/questions/37660/order-of-redirections
Mikel 2015年

不,我的意思是对您用英语的描述方式的解释。但是,您提供的链接非常有用。
史蒂夫

为什么需要将其重定向到/ dev / null?
Kanwaljeet Singh

7

请参阅下面的这些测试中使用的脚本。

Grep只能在stdin上运行,因此,您必须以Grep可以解析的形式转换stderr流。

通常,stdout和stderr都打印到屏幕上:

$ ./stdout-stderr.sh
./stdout-stderr.sh: Printing to stdout
./stdout-stderr.sh: Printing to stderr

要隐藏标准输出,但仍打印标准错误,请执行以下操作:

$ ./stdout-stderr.sh >/dev/null
./stdout-stderr.sh: Printing to stderr

但是grep无法在stderr上运行!您可能期望以下命令禁止包含“ err”的行,但实际上不包含。

$ ./stdout-stderr.sh >/dev/null |grep --invert-match err
./stdout-stderr.sh: Printing to stderr

这是解决方案。

以下Bash语法会将输出隐藏到stdout,但仍显示stderr。首先我们将stdout管道传输到/ dev / null,然后将stderr转换为stdout,因为Unix管道只能在stdout上运行。您仍然可以grep文本。

$ ./stdout-stderr.sh 2>&1 >/dev/null | grep err
./stdout-stderr.sh: Printing to stderr

(注意,上述命令是不同的,然后./command >/dev/null 2>&1,这是一个非常普遍的命令)。

这是用于测试的脚本。这将一行输出到stdout,一行输出到stderr:

#!/bin/sh

# Print a message to stdout
echo "$0: Printing to stdout"
# Print a message to stderr
echo "$0: Printing to stderr" >&2

exit 0

如果切换重定向,则不需要所有花括号。做吧./stdout-stderr.sh 2>&1 >/dev/null | grep err
Mikel

3

当将一个命令的输出通过管道传递到另一个命令时(使用|),您仅在重定向标准输出。所以应该解释为什么

ffmpeg -i 01-Daemon.mp3 | grep -i Duration

不会输出您想要的内容(尽管它确实可以工作)。

如果您不想将错误输出重定向到标准输出,则可以将错误输出重定向到文件,然后稍后将其grep

ffmpeg -i 01-Daemon.mp3 2> /tmp/ffmpeg-error
grep -i Duration /tmp/ffmpeg-error

谢谢。it does work, though,您是说它正在您的计算机上正常工作?其次,正如您所指出的,使用管道我们只能重定向标准输出。我对某些命令或bash功能感兴趣,这将使我重定向stderr。(但不是临时文件的技巧)
Andrew-Dufresne 2010年

@Andrew我的意思是,该命令按照其设计工作方式运行。它只是无法按您想要的方式工作:)
phunehehe 2010年

我不知道有什么方法可以将命令的错误输出重定向到另一个命令的标准输入。如果有人可以指出这一点将会很有趣。
phunehehe

2

您可以交换流。这将使您能够grep使用原始的标准错误流,同时仍然可以在终端中获取原始的输出到标准输出:

somecommand 3>&2 2>&1 1>&3- | grep 'pattern'

通过首先创建一个新的文件描述符(3)打开以进行输出并将其设置为标准错误流(3>&2)来工作。然后,我们将标准错误重定向到标准输出(2>&1)。最终,标准输出重定向到原始标准错误,并且新文件描述符关闭(1>&3-)。

在您的情况下:

ffmpeg -i 01-Daemon.mp3 3>&2 2>&1 1>&3- | grep -i Duration

测试它:

$ ( echo "error" >&2; echo "output" ) 3>&2 2>&1 1>&3- | grep "error"
output
error

$ ( echo "error" >&2; echo "output" ) 3>&2 2>&1 1>&3- | grep -v "error"
output

1

我喜欢rc在这种情况下使用外壳。

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

这是一个示例,说明如何丢弃stdout和管道stderr传递给grep rc

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

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

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

您可能已经注意到,语法很简单,这使它成为我的首选解决方案。

您可以在管道字符之后指定要通过括号插入的文件描述符。

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

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

0

bash子流程示例的一个变体:

将两行回显到stderr和tee stderr到一个文件,grep tee并通过管道返回到stdout

(>&2 echo -e 'asdf\nfff\n') 2> >(tee some.load.errors | grep 'fff' >&1)

标准输出:

fff

some.load.errors(例如stderr):

asdf
fff

-1

试试这个命令

  • 用随机名称创建文件
  • 发送输出到文件
  • 发送文件内容到管道
  • grep

ceva->只是一个变量名(英语=某物)

ceva=$RANDOM$RANDOM$RANDOM; ffmpeg -i qwerty_112_0_0_record.flv 2>$ceva; cat $ceva | grep Duration; rm $ceva;

我会使用/tmp/$ceva,而不是用临时文件乱丢当前目录-并且您假设当前目录是可写的。
罗尔夫
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.