我有一个程序,它将信息写入stdout和stderr,我需要grep,通过stderr,而不考虑stdout。
我当然可以分两步完成:
1 2
| command > /dev/null 2> temp.file
grep 'something' temp.file |
但我更愿意在没有临时文件的情况下完成这项工作。有什么巧妙的管道技巧吗?
- 类似的问题,但unix.stackexchange.com /问题/固定输出:3514 / & hellip;
- 这个问题是在bash But it’s Worth提此相关的文章?/ Almquist外壳。
- 我是这样的:command 2| othercommandexpecting这样的事情。这是一个bash开发操作系统OS端在1982年,我们永远不会看到这是在派对上,我害怕。
首先将stderr重定向到stdout-管道;然后将stdout重定向到/dev/null(不更改stderr的去向):
1
| 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最初指向的打开文件描述的事实,即管道。
- 前几天我偶然遇到了/dev/stdout/dev/stderr/dev/stdin,我很好奇这些是否是做同样事情的好方法?我一直认为2>有点模糊。比如:command 2> /dev/stdout 1> /dev/null | grep 'something'。
- 你可以使用/dev/stdout等,或者使用/dev/fd/N。除非shell将它们视为特殊情况,否则它们的效率会稍低;纯数字表示法不涉及按名称访问文件,但使用设备确实意味着文件名查找。你能否衡量这一点还存在争议。我喜欢数字符号的简洁性——但我用它已经很久了(超过四分之一个世纪了;哎呀!)我没有资格评判它在现代世界的价值。
- 哈哈,很公平,乔纳森。我没有意识到2>1会提高效率。谢谢你指出这一点
- @Jonathan Leffler:我对您的纯文本解释有一个小问题:"将stderr重定向到stdout,然后将stdout重定向到/dev/null"——因为必须从右到左(而不是从左到右)读取重定向链,所以我们还应该调整纯文本解释:"将stdout重定向到/dev/null,然后将stderr重定向到stdout用于b的位置。E’。
- @库尔特菲夫:不,相反!必须从左到右读取重定向链,因为这是shell处理它们的方式。第一个操作是2>&1,这意味着"将stderr连接到stdout当前要使用的文件描述符"。第二个操作是"更改stdout,使其转到/dev/null",使stderr转到原始stdout,即管道。shell首先在管道符号处拆分内容,因此管道重定向发生在2>&1或>/dev/null重定向之前,但仅此而已;其他操作从左到右。(从右向左不行。)
- 您需要从右到左分析每个重定向操作。2>&1或2>& 1由操作符2>&和文件描述符(fd)参数1组成。shell than将fd参数的目标"duples"为嵌入在运算符中的文件描述符(本例中为2)。
- 令我吃惊的是,它也可以在Windows上运行(在将/dev/null重命名为Windows等价物nul之后)。
- @Kurtpfeifle:我以前也很难理解重定向的顺序。直到我写了一个小小的shell,并意识到如何使用fd_redirect_into = open("file"); close(fd_to_redirect); dup(fd_redirect_into); close(fd_redirect_into);系统调用序列进行重定向。
- @J.F.塞巴斯蒂安,你能解释一下吗?
- @Vassilisgr是一种bash语法,它合并了stdout/stderr(如2>&1)。它用于在此处捕获stderr:由于|&:命令的stderr被重定向到stdout,然后stdout被重定向到/dev/null,然后grep只从其stdin上的命令接收stderr。
- @J.F.塞巴斯蒂安,谢谢你的回复。这是一个非常有趣的方法!不过,有人可能会检查bash版本。例如,在gnu bash中,版本3.2.53(1)-release-(x86_-apple-darwin13)确实会在意外的标记&;附近引发语法错误。在Linux中使用gnu bash 4.3..没问题!
- @J.F.Sebastian从左到右(如Jonathan解释的那样)的读数不是>/dev/null |&首先将stdout重定向到/dev/null然后2>&1也将stderr重定向到/dev/null吗?
- @Legends2K:请注意,首先,管道被拆分为由管道连接的一系列命令。|&是|的一种特殊情况,它将标准输出和标准误差都重定向到管道。然后从左到右处理普通重定向。因此,command > /dev/null |& grep 'something'将|&处的管道分开。在lhs上,标准输出和标准错误被重定向到管道上;然后,>/dev/null重定向将标准输出发送到/dev/null,因此只有标准错误才会发送到管道上。grep从管道中读取信息,寻找"某物"。
- @勒让斯2:总的来说,command >/dev/null |& grep 'something'相当于command 2>&1 >/dev/null | grep 'something'。另请参见bash手册中的管道。事实上,这意味着:如果使用"|&",那么command1的标准错误除了其标准输出外,还通过管道连接到command2的标准输入;它是2>&1 |的缩写。将标准错误隐式重定向到标准输出是在命令指定的任何重定向之后执行的。呸!好吧,这就是它是什么;RTFM适用。
- 我应该补充一点,RTFM对我的应用至少和其他人一样多;直到我读到了这篇文章,我才发现|&的行为有点奇怪。
- 对。我引用了手册。它取代了我对它的解释。我不会使用|&;这是不必要的,不符合我对什么对我有用的想法。其他人可以随心所欲。
- @乔纳森·弗勒找到了!我们对|&是一个特殊案例的两种解释都是基于塞巴斯蒂安的评论(部分原因是它获得的赞成票数量)。但是,该评论是错误的。请看这里。我是来告诉你,你的解释,没有特殊情况,正如手册所解释的,实际上是正确的。因此,>/dev/null |&首先将stdout重定向到NUL,stderr也被指向NUL(从左到右);这不是OP想要的。
- @我删除了错误的评论。我认为|&处理发生在个别重定向之前,但正如手册所说,它发生在重定向之后,实际行为证实它发生在重定向之后,即command >/dev/null |& grep 'something'相当于command >/dev/null 2>&1 | grep 'something'(所有标准输出都是/dev/null。grep什么也没看到)
- @Kurtpfeifle我被"我们必须从右向左阅读"和"不,我们必须从左向右阅读"逗乐了。当然,这两种说法都是错误的,因为对每种方式都有合理的解释。就我个人而言,我的大脑总是希望command $a>&$b $c>&d $e>&$f做((command $a>&$b) $c>&d) $e>&$f(在某种意义上说是"从左到右"),但这是错误的。相反,它执行((command $e>&$f) $c>&d) $a>&$b(即,从某种意义上说,从右到左读取未附加的管道)。其他人也给出了"从左到右"的解释,这些解释也是有效的。
- @jonathanlefler;command &>1 >/dev/null | grep 'something'等同于command 2>&1 >/dev/null | grep 'something'吗?
- &>1是一个bash新词;在我(古老的,古怪的)看来,它很可怕,我不会用船竿碰它。如果要信任我对&>的bash手册的阅读,&>1意味着"将标准输出和标准错误重定向到名为1的文件",而不是文件描述符1。我没有用它做过试验,目前我没有这样做的计划。我不认为它是对shell语法的有用补充。
- 好啊。实际上,我看到了一个类似于if kill -0 &>1 > /dev/null $pid的测试来检查进程(进程ID存储在变量pid中)是否正在运行。这就是困惑的根源。我想应该是if kill -0 &> /dev/null $pid或if kill -0 $pid > /dev/null 2>&1。
- 相反,如果只想看到标准输出而不想看到标准错误,可以执行"command 1>&;2 2>/dev/null"。答案暗示了这一点,但想要为那些仅仅是寻求如何做到这一点的答案的人详细说明这一点。
- @乔治柯菲茨:通常,如果您想丢失标准错误,只需使用command 2>/dev/null。使用command 1>&2 2>/dev/null意味着命令的标准输出到标准错误发生的地方(可能是终端),即1>&2部分,然后标准错误(但不是标准输出)被发送到/dev/null。两者都使用并不是100%的错误,但这是不寻常的。同样要注意,顺序也很重要。command 2>/dev/null 1>&2向/dev/null发送标准错误,然后将标准输出发送到同一位置。
- 自从乔纳森勒弗勒第一次发表评论以来,我每3到6个月就回来阅读他于2012年7月1日发表的评论。今天,这是第一次……不仅有意义,而且感觉……修辞。比如,"噢,当然。"我想我终于明白了。我几个月后再回来看看。
- 如果挂起并且执行后不继续,请尝试将命令包装在{}花括号中。示例{command} 2>&1 >/dev/null | grep 'something'
- 注意,可以用文件名替换/dev/null,将stdout重定向到文件,stderr重定向到进程。
或者将stderr和stdout的输出交换为过度使用:
这将创建一个新的文件描述符(3)并将其分配到与1(stdout)相同的位置,然后将fd1(stdout)分配到与fd2(stderr)相同的位置,最后将fd2(stderr)分配到与fd3(stdout)相同的位置。stderr现在作为stdout提供,旧stdout保留在stderr中。这可能有点过分,但希望给出更多关于bash文件描述符的详细信息(每个进程有9个可用的描述符)。
- 最后一个调整是3>&-,关闭从stdout创建的备用描述符。
- 我们可以创建一个包含stderr和另一个包含stderr和stdout组合的文件描述符吗?换言之,stderr可以同时转到两个不同的文件吗?
- 以下仍将错误打印到stdout。我错过了什么?ls-l not_a_file 3>1 1 1>2 2 2>3>errors.txt
- @用户48956-我将/etc/passwd添加到您的命令中,这样它将有非空的stdout,以使事情更清楚。如果你的思想和我的一样,你假设你的ls -l /etc/passwd not_a_file 3>&1 1>&2 2>&3 > errors.txt应该给你和(ls -l /etc/passwd not_a_file 3>&1 1>&2 2>&3) > errors.txt一样的东西,这是错误的。如果需要的话,您可以通过键入确切的内容来获得后者。另一方面,如果您的目标只是简单地将2重定向到一个文件,那就更容易了:ls -l /etc/passwd not_a_file 2> errors.txt。
- @用户48956——为了理解你提出的命令ls -l /etc/passwd not_a_file 3>&1 1>&2 2>&3 > errors.txt的实际作用,从kramish的描述开始;到最后,你已经有效地交换了1和2,当从命令行运行时,这不是很有趣,因为它们最初都指向终端,所以它们都指向终端。最终的> errors.txt即1> errors.txt表示程序的输出1(listing/etc/passwd)最终被重定向到errors.txt,其输出2(抱怨没有文件)仍然指向终端。
- @Jonathanleffler出于好奇,除了为观察者澄清文件描述符(3)的角色之外,您的调整是否有任何性能上的用途?
- @Jonasdahlb&230;k:调整主要是一个整洁的问题。在真正神秘的情况下,它可能会使过程检测和不检测EOF有区别,但这需要非常特殊的情况。
- 我认为与3>&-的主要区别在于命令中的write(3,"blarg")将立即失败,而不是最终锁定应用程序。
- 警告:这假定fd 3尚未使用,不会关闭它,也不会撤消文件描述符1和2的交换,因此您不能继续将其传输到另一个命令。请参阅此答案以了解更多详细信息并加以解决。有关ba,z sh更清晰的语法,请参阅此答案。
在bash中,还可以使用进程替换重定向到子shell:
1
| command > >(stdlog pipe) 2> >(stderr pipe) |
对于手头的案件:
1
| command 2> >(grep 'something') >/dev/null |
- 输出到屏幕效果很好。如果我将grep输出重定向到一个文件中,您知道为什么会再次出现未替换的内容吗?在command 2> >(grep 'something' > grep.log)grep.log包含与command 2> ungrepped.log中undepped.log相同的输出之后
- @蒂姆,这对我来说是意料之中的事。
- 使用2> >(stderr pipe >&2)。否则,"stderr管道"的输出将通过"stdlog管道"。
- 是啊!我试了一下,但没有。
结合这些最佳答案,如果您这样做:
command 2> >(grep -v something 1>&2)
…然后所有stdout都保留为stdout,所有stderr都保留为stderr,但在stderr中不会看到包含字符串"something"的任何行。
这有一个独特的优点,即不会反转或丢弃stdout和stderr,也不会将它们弄脏在一起,也不会使用任何临时文件。
- command 2> >(grep -v something)(没有1>&2)不是一样吗?
- 不,没有这个,过滤后的stderr最终被路由到stdout。
- 这就是我需要的——tar总是为一个目录输出"文件在我们读取时发生了更改",所以只想过滤掉这一行,但看看是否发生了其他错误。因此,tar cfz my.tar.gz mydirectory/ 2> >(grep -v 'changed as we read it' 1>&2)应该起作用。
- 说"从字符串开始"是错误的。关于grep的呈现语法,没有什么可以使它只排除以给定字符串开头的行。如果行中的任何位置都包含给定的字符串,则将排除这些行。
- @Mikenakis谢谢——修好了!(这是我原来的答卷草稿留下的,在原稿中它是有意义的…)
- ???????很好,这是我很久以来一直在寻找的解决方案
如果您考虑"重定向"和"管道"的实际情况,那么可视化事情就容易得多。bash中的重定向和管道只做一件事:修改进程文件描述符0、1和2指向的位置(请参见/proc/[pid]/fd/*)。
当命令行中存在管道或""运算符时,首先发生的事情是bash创建一个FIFO,并将左侧命令的fd 1指向此FIFO,并将右侧命令的fd 0指向同一FIFO。
接下来,从左到右对每侧的重定向操作符进行评估,并且每当描述符出现重复时,都使用当前设置。这一点很重要,因为由于首先设置了管道,因此FD1(左侧)和FD0(右侧)已经与正常情况不同,任何重复都会反映出这一事实。
因此,当您键入如下内容时:
1
| command 2>&1 >/dev/null | grep 'something' |
以下是发生的事情,顺序如下:
将创建管道(FIFO)。命令FD1"指向此管道。GREP FD0"也指向此管道
"command fd2"指向"command fd1"当前指向的位置(管道)
"command fd1"指向/dev/null
因此,"command"写入其fd 2(stderr)的所有输出都会进入管道,并由另一侧的"grep"读取。"command"写入其fd 1(stdout)的所有输出都会进入/dev/null。
如果改为运行以下命令:
1
| 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)
所以,来自"command"的所有stdout和stderr都将转到/dev/null。管道中没有任何内容,因此"grep"将关闭而不在屏幕上显示任何内容。
还要注意,重定向(文件描述符)可以是只读(<)、只写(>)或读写(<>)。
最后一个音符。程序是否向FD1或FD2写入某些内容完全取决于程序员。良好的编程实践要求错误消息应该转到fd 2,正常输出到fd 1,但是您经常会发现混合了这两者的编程很草率,或者忽略了约定。
- 回答得很好。我的一个建议是用"FIFO(命名管道)"替换您第一次使用的"FIFO"。我使用Linux已经有一段时间了,但不知何故,我从来没有学会这是命名管道的另一个术语。这本可以让我不去查它,但我再也不会学到我发现的其他东西了!
- @请注意,FIFO只是管道和IPC上下文中命名管道的另一个术语。在更一般的上下文中,FIFO意味着先进先出,它描述了队列数据结构的插入和删除。
- @当然是狼疮。我的意见是,即使作为一个经验丰富的开发人员,我从来没有见过FIFO用作命名管道的同义词。换句话说,我不知道这一点:en.wikipedia.org/wiki/fifo_u(computing_u and_electronics)pipes-说明答案中的内容可以节省我的时间。
你在用bash吗?如果是这样:
1
| command >/dev/null |& grep"something" |
http://www.gnu.org/software/bash/manual/bashref.html管道
- 不,|&等于2>&1,它结合了stdout和stderr。问题明确要求输出不带stdout。
- ?如果使用"&;",则command1的标准错误通过管道连接到command2的标准输入;它是从链接处的第四段逐字提取的2>&;1"的简写。
- @profpatsch:ken的回答是正确的,看他在组合stdout和stderr之前将stdout重定向到空,所以您将只获得pipe stderr,因为stdout以前被丢弃到/dev/null。
- 我使用mplayer a 2>/dev/null |& grep i和mplayer a >/dev/null |& grep i(完全没有输出!)要测试哪个文件名a不存在,并想知道为什么您的答案不起作用,可能是因为bash版本?然后我想我需要使用fd 3,即mplayer a 3>/dev/null |& grep i来获得输出lol。
- 但是我仍然发现你的回答是错误的,>/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将允许2绑定到stdout inode将管道)。
- 我测试了ken sharp,( echo out; echo err >&2 ) >/dev/null |& grep"."没有输出(我们想要"err")。man bash表示如果使用…是2>的缩写。将标准错误隐式重定向到标准输出是在命令指定的任何重定向之后执行的。因此,首先我们将命令的fd1重定向到空值,然后将命令的fd2重定向到fd1指向的位置,即空值,这样grep的fd0就不会得到任何输入。请参阅stackoverflow.com/a/18342079/69663了解更深入的解释。
对于那些希望将stdout和stderr永久重定向到文件的用户,请在stderr上grep,但保留stdout以将消息写入tty:
1 2 3 4 5 6 7 8 9 10 11 12
| # 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 |
这将把command1 stderr重定向到command2 stdin,同时保持command1 stdout不变。
1 2 3
| exec 3>&1
command1 2>&1 >&3 3>&- | command2 3>&-
exec 3>&- |
自民党
我刚刚想出了一个解决方案,用命名管道将stdout发送给一个命令,将stderr发送给另一个命令。
来吧。
1 2 3 4 5
| 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 |
以后移除命名管道可能是个好主意。
你可以用钢筋混凝土外壳。
首先安装软件包(小于1MB)。
这是一个您将如何在rc中丢弃stdout并将stderr管道输送到grep的示例:
find /proc/ >[1] /dev/null |[2] grep task
您可以在不离开bash的情况下完成它:
rc -c 'find /proc/ >[1] /dev/null |[2] grep task'
正如您可能已经注意到的,您可以通过在管道后面使用括号来指定要管道化的文件描述符。
标准文件描述符的计算方法如下:
我喜欢做这样的事情
composer test &>> /tmp/bob && vim /tmp/bob && rm /tmp/bob
- 那该怎么办?
- 将输出连接到一个临时文件,用VIM打开它,然后将其移除。
我试着跟随,发现它也能工作,
1
| command > /dev/null 2>&1 | grep 'something' |
- 不起作用。它只是将stderr发送到终端。忽略管道。
- @Tripkinetics已编辑