我有一个程序,它将信息写入stdout
和stderr
,我需要使用grep
处理stderr
,而将stdout
放在一边。
使用临时文件,可以分两步完成:
command > /dev/null 2> temp.file
grep 'something' temp.file
但是,如何在不使用临时文件的情况下,仅使用一个命令和管道来实现此目标呢?
先将标准错误输出重定向到标准输出(使用管道);然后将标准输出重定向到/dev/null
(不改变标准错误输出),实现如下:
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所指向的——即管道。
command 2> /dev/stdout 1> /dev/null | grep 'something'
。 - Mike Lyons/dev/stdout
等,或者使用/dev/fd/N
。除非shell将它们视为特殊情况,否则它们的效率会略微降低;纯数字表示法不涉及按名称访问文件,但是使用设备确实意味着需要查找文件名。你能否测量到这一点还有待商榷。我喜欢数字表示法的简洁性 - 但我已经使用它超过四分之一个世纪了(疼!),所以我不适合评判它在现代世界中的价值。 - Jonathan Leffler2>&1
,它意味着“将 stderr 连接到当前 stdout 正在连接的文件描述符”。第二步操作是“更改 stdout,使其输出到 /dev/null
”,这样 stderr 仍然会输出到原始 stdout,也就是管道。Shell 首先在管道符号处分割内容,所以管道重定向发生在 2>&1
或 >/dev/null
重定向之前,但仅限于此;其他操作是从左到右进行的(从右到左行不通)。 - Jonathan Leffler或者要交换标准错误和标准输出的输出,请使用:
command 3>&1 1>&2 2>&3
这将创建一个新的文件描述符(3),并将其赋值到与1(标准输出)相同的位置,然后将fd 1(标准输出)赋值到与fd 2(标准错误)相同的位置,最后将fd 2(标准错误)赋值到与fd 3(标准输出)相同的位置。
标准错误现在作为标准输出可用,旧的标准输出被保留在标准错误中。这可能有点过度,但它希望更详细地说明Bash文件描述符(每个进程有九个可用)。
3>&-
,用于关闭你从stdout创建的多余描述符。 - Jonathan Lefflerstderr
,另一个具有stderr
和stdout
的组合?换句话说,stderr
可以同时发送到两个不同的文件吗? - Stuartcommand > >(stdout pipe) 2> >(stderr pipe)
对于目前的情况:
command 2> >(grep 'something') >/dev/null
command 2> >(grep 'something' > grep.log)
之后,grep.log包含与command 2> ungrepped.log
中的ungrepped.log相同的输出。 - Tim2> >(stderr管道 >&2)
。否则,“stderr管道”的输出将通过“stdlog管道”进行传输。 - ceving2> >(...)
可以工作,我尝试过 2>&1 > >(...)
但它没有起作用。 - Deeawk -f /new_lines.awk <in-content.txt > out-content.txt 2> >(tee new_lines.log 1>&2 )
在这种情况下,我想要_同时_看到控制台上的错误输出。但STDOUT将写入输出文件。因此,在子shell中,您需要在括号内将STDOUT重定向回STDERR。虽然这样可以工作,但是tee
命令的STDOUT输出最终会出现在out-content.txt
文件的末尾。这对我来说似乎不一致。 - will2>&1 1> >(目标管道)
。 - Alireza Mohamadi... >(...
而不是... > (...
(空格是错误的) - Cadoiz综合这些答案的最佳方案是,如果您执行以下命令:
command 2> >(grep -v something 1>&2)
...那么所有标准输出都将保留为标准输出且所有错误输出将被保留为错误输出,但您不会看到任何包含字符串“something”的错误输出行。
这种方法的独特优势在于不会颠倒或丢弃标准输出和错误输出,也不会将它们混在一起,也不会使用任何临时文件。
1>&2
,command 2> >(grep -v something)
不是一样的吗? - Francesc Rosastar cfz my.tar.gz mydirectory/ 2> >(grep -v 'changed as we read it' 1>&2)
应该可以解决问题。 - razzed如果你考虑“重定向”和“管道”的实际作用,那么可视化事物会更容易。在bash中,“重定向”和“管道”所做的只有一件事情:修改进程文件描述符0、1和2指向的位置(请参见/proc/[pid]/fd/*)。
当命令行中存在管道或“|”运算符时,首先要发生的是bash创建一个fifo并将左侧命令的FD1指向该fifo,并将右侧命令的FD0指向同一个fifo。
接下来,从左到右评估每个侧面的重定向运算符,并在复制描述符时使用当前设置。这很重要,因为由于管道是首先设置的,因此FD1(左侧)和FD0(右侧)已经与通常可能有的不同,任何对它们的复制都将反映这一事实。
因此,当你键入以下内容时:
command 2>&1 >/dev/null | grep 'something'
以下是按顺序发生的事情:
因此,“command”写入其FD 2(stderr)的所有输出都会通过管道传递并由另一侧的“grep”读取。而“command”写入其FD 1(stdout)的所有输出都被重定向到了/dev/null。
如果你运行下面的命令:
command >/dev/null 2>&1 | grep 'something'
下面是发生的事情:
因此,“command”的所有标准输出和标准错误都被重定向到/dev/null中,没有任何内容被发送到管道中,因此“grep”会关闭而不在屏幕上显示任何内容。
还要注意的是,重定向(文件描述符)可以是只读(<)、只写(>)或读写(<>)。
最后一点。程序是否将某些内容写入FD1或FD2,完全取决于程序员。良好的编程实践应该是将错误消息发送到FD 2,将正常输出发送到FD 1,但通常会发现不规范的编程混合两者或者忽略了这个约定。
如果您正在使用Bash,则使用:
command >/dev/null |& grep "something"
http://www.gnu.org/software/bash/manual/bashref.html#Pipelines
mplayer a 2>/dev/null |& grep i
(有输出结果) 和 mplayer a >/dev/null |& grep i
(没有任何输出!)来测试文件名为a
不存在的情况,不知道为什么你的答案不起作用,可能是由于bash版本的问题?后来我发现需要使用fd 3,即 mplayer a 3>/dev/null |& grep i
才能得到输出结果。 - 林果皞>/dev/null |&
扩展成 >/dev/null 2>&1 |
,它的意思是将标准输出的inode管道传递给空设备文件,因为没有任何人(#1 #2都连接到/dev/null inode)与标准输出的inode相连。例如,ls -R /tmp/* >/dev/null 2>&1 | grep i
将会得到一个空结果,但是 ls -R /tmp/* 2>&1 >/dev/null | grep i
将允许与标准输出inode相连的#2管道传递数据。 - 林果皞( echo out; echo err >&2 ) >/dev/null |& grep "."
没有输出(我们希望得到“err”)。man bash
说,“如果使用|& ...是2>&1 |的简写。在命令指定的任何重定向之后,将标准错误隐式重定向到标准输出。”所以首先我们将命令的FD1重定向到null,然后将命令的FD2重定向到FD1所指向的位置,即null,因此grep的FD0没有输入。更深入的解释请参见https://dev59.com/63E95IYBdhLWcg3wXcrd#18342079。 - unhammer如果你想永久地将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
exec 3>&1
command1 2>&1 >&3 3>&- | command2 3>&-
exec 3>&-
Taken from LDP
3>&1
)。接下来将command1
的错误重定向到其输出(2>&1
),然后将command1
的标准输出指向父进程的标准输出副本(>&3
)。清理command1
中的重复文件描述符(3>&-
)。在command2
中,我们只需要删除重复的文件描述符(3>&-
)。这些重复是由于父进程分叉自身以创建两个进程而引起的,因此我们只需清理它们。最后,在结束时,我们删除父进程的文件描述符(3>&-
)。 - smac89command1
的原始stdout指针,现在它指向父进程的stdout,而它的stderr指向它以前的stdout所在的位置,使其成为command2
的新stdout。 - smac89我刚想出一个使用命名管道将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
最好在使用后删除命名管道。
rc
语法用于管道 stderr 比在 bash
中所需做的要好得多,因此我认为值得一提。 - Rolf
|&
来同时传递stderr和stdout(这并不完全符合原帖作者的要求,但接近于我猜测你的建议的含义)。 - tripleee2|
表示什么意思? - Rolfecho 2 | tee my_file
和echo 2|tee my_file
。 - Z4-tier2 |
并不是2|
,我不会称其为模棱两可,更像是潜在的导致错误,就像echo 2 > /myfile
和echo 2> /myfile
一样,这甚至更加严重。无论如何,这不是为了节省几个按键,我发现其他解决方案很复杂和古怪,而且我还没有完全理解它们,这就是为什么我会启动rc
,因为它有一个简单直观的语法来确定你想要重定向的流。 - Rolf