我有一个将信息写入 stdout
和 stderr
的程序,我需要使用 grep
处理 stderr
,而将 stdout
放在一边。
使用临时文件,可以分两步完成:
command > /dev/null 2> temp.file
grep 'something' temp.file
但是,如果没有临时文件,如何使用一个命令和管道来实现呢?
command 2| othercommand
。 Bash 是如此完美,以至于开发在 1982 年就结束了,所以恐怕我们永远不会在 bash 中看到它。
|&
来管道 stderr 和 stdout (这不是 OP 确切要求的,但非常接近我猜你的提案可能意味着)。
2 |
确实不是 2|
,我不会称它为模棱两可,更像是潜在的错误诱导,就像 echo 2 > /myfile
和 echo 2> /myfile
更成问题一样。无论如何,这不是为了节省一些击键,我发现其他解决方案令人费解和古怪,还没有完全理解它们,这就是为什么我会启动 rc
,它有一个简单的语法来确定你想要的流重定向。
首先将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 最初指向的打开文件描述的事实——即管道。
或者交换标准错误和标准输出的输出,使用:
command 3>&1 1>&2 2>&3
这将创建一个新的文件描述符 (3) 并将其分配到与 1(标准输出)相同的位置,然后将 fd 1(标准输出)分配到与 fd 2(标准错误)相同的位置,最后分配 fd 2(标准错误) ) 到与 fd 3 (标准输出)相同的位置。
标准错误现在可作为标准输出使用,旧的标准输出保留在标准错误中。这可能有点矫枉过正,但它希望能提供有关 Bash 文件描述符的更多详细信息(每个进程有九个可用)。
3>&-
以关闭您从标准输出创建的备用描述符
stderr
和另一个具有 stderr
和 stdout
组合的文件描述符吗?换句话说,stderr
可以同时转到两个不同的文件吗?
在 Bash 中,您还可以使用 process substitution 重定向到子 shell:
command > >(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 相同的输出
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
文件的末尾结束。这对我来说似乎不一致。
2>&1 1> >(dest pipe)
结合最好的这些答案,如果你这样做:
command 2> >(grep -v something 1>&2)
...然后所有标准输出都保留为标准输出,所有标准错误都保留为标准错误,但您不会在标准错误中看到任何包含字符串“某物”的行。
这具有不反转或丢弃 stdout 和 stderr,也不将它们混合在一起,也不使用任何临时文件的独特优势。
command 2> >(grep -v something)
(没有 1>&2
)不一样吗?
tar cfz my.tar.gz mydirectory/ 2> >(grep -v 'changed as we read it' 1>&2)
应该可以工作。
如果您考虑一下“重定向”和“管道”到底发生了什么,那么将事物可视化会容易得多。 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,但您经常会发现草率的编程混合了两者或忽略了约定。
如果您使用的是 Bash,请使用:
command >/dev/null |& grep "something"
http://www.gnu.org/software/bash/manual/bashref.html#Pipelines
|&
等于结合了 stdout 和 stderr 的 2>&1
。该问题明确要求输出 without 标准输出。
>/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 管道)。
( 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。
对于那些想要将 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
这会将 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。
我刚刚想出了一个解决方案,使用命名管道将 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 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:标准误差
rc
语法比您在 bash
中必须做的要好得多,所以我认为值得一提。
我尝试跟随,发现它也有效,
command > /dev/null 2>&1 | grep 'something'
command 2> /dev/stdout 1> /dev/null | grep 'something'
/dev/stdout
等,或使用/dev/fd/N
。除非 shell 将它们视为特殊情况,否则它们的效率会略微降低;纯数字符号不涉及按名称访问文件,但使用设备确实意味着文件名查找。您是否可以衡量这一点值得商榷。我喜欢数字符号的简洁性——但我已经使用了很长时间(超过 25 年;哎哟!),以至于我没有资格判断它在现代世界中的优点。2>&1
,意思是“将 stderr 连接到 stdout 当前要去的文件描述符”。第二个操作是“更改标准输出,使其转到/dev/null
”,让标准错误进入原始标准输出,即管道。 shell 首先在管道符号处拆分内容,因此管道重定向发生在2>&1
或>/dev/null
重定向之前,但仅此而已;其他操作是从左到右的。 (从右到左不起作用。)/dev/null
重命名为 Windows 等效项nul
之后)。