在bash中,是否有一种方法可以在将标准错误流与标准输出流合并之前通过过滤器传递标准错误流?也就是说,我想要
STDOUT ────────────────┐
├─────> terminal/file/whatever
STDERR ── [ filter ] ──┘
与其
STDOUT ────┐
├────[ filter ]───> terminal/file/whatever
STDERR ────┘
在bash中,是否有一种方法可以在将标准错误流与标准输出流合并之前通过过滤器传递标准错误流?也就是说,我想要
STDOUT ────────────────┐
├─────> terminal/file/whatever
STDERR ── [ filter ] ──┘
与其
STDOUT ────┐
├────[ filter ]───> terminal/file/whatever
STDERR ────┘
以下是示例,模仿 如何在bash中交换文件描述符。 a.out的输出如下,没有前缀'STDXXX:'。
STDERR: stderr output
STDOUT: more regular
./a.out 3>&1 1>&2 2>&3 3>&- | sed 's/e/E/g'
more regular
stdErr output
引用上面的链接:
- 首先将 stdout 保存为 &3(将 &1 复制到 3)
- 接下来将 stdout 发送到 stderr(将 &2 复制到 1)
- 将 stderr 发送到 &3(即 stdout)(将 &3 复制到 2)
- 关闭 &3(将 -& 复制到 3)
$ cmd 2> >(stderr-filter >&2)
例子:
% cat /non-existant 2> >(tr o X >&2)
cat: /nXn-existant: NX such file Xr directXry
%
这个方法在bash和zsh中都可以使用。如今,Bash几乎无处不在,但是如果您真的需要一个(非常复杂的)解决方案来适用于POSIX sh
,那么请点击此处。
目前最简单的解决方案是通过进程替换将标准错误输出重定向:
进程替换允许使用文件名引用进程的输入或输出。它采用以下形式:
>(list)
进程列表是异步运行的,其输入或输出显示为文件名。
因此,通过进程替换得到的是一个文件名。
就像您可以执行以下操作一样:
$ cmd 2> filename
你可以做
$ cmd 2> >(filter >&2)
>&2
重定向将filter
的 STDOUT 返回到原始的 STDERR。
exec 2> >(while .. read .. echo)
时,我曾受到过此类问题的困扰。journald捕获了服务的fd2,并从“<N>”前缀推断出日志级别:在输出行之前添加 <2>
,你可以分配ERROR级别给journal记录。但有时,systemd在主要进程退出之前就更快地杀死整个进程组,导致它无法记录最后、最重要的消息! - kkm$ cmd 2> >(stderr-filter >&2)
示例:
% cat /non-existant 2> >(tr o X >&2)
cat: /nXn-existant: NX such file Xr directXry
%
StackExchange 网络上的许多答案都具有以下形式:
cat /non-existant 3>&1 1>&2 2>&3 3>&- | sed 's/e/E/g'
这里有一个内置的假设:文件描述符3没有被用于其他用途。
相反,使用一个命名的文件描述符,然后{ba,z}sh
将分配下一个可用的文件描述符>= 10:
cat /non-existant {tmp}>&1 1>&2 2>&$tmp {tmp}>&- | sed 's/e/E/g'
请注意,POSIX sh
不支持命名文件描述符。
上述方法的另一个问题是,如果不将STDOUT和STDERR再次交换回其原始值,则无法将命令管道传递给其他命令。
为了在POSIX sh
中允许进一步的管道操作(并仍然假设FD 3未被使用),情况变得复杂起来:
(cmd 2>&1 >&3 3>&- | stderr-filter >&2 3>&-) 3>&1
所以,考虑到这个假设和复杂的语法,你最好使用TL;DR中简单的bash
/zsh
语法,并在此处解释here。
实际演示,仅对stderr进行grepping:
$ ls -l . noexistABC noexistXYZ
ls: cannot access 'noexistABC': No such file or directory
ls: cannot access 'noexistXYZ': No such file or directory
.:
total 4
-rw-rw-r-- 1 frank frank 0 Aug 19 12:26 bar.txt
-rw-rw-r-- 1 frank frank 0 Aug 19 12:26 foo.txt
drwxrwxr-x 2 frank frank 4096 Aug 19 12:26 someFolder
$ ( ls -l . noexistABC noexistXYZ 2>&1 >&3 3>&- | grep ABC >&2 3>&-) 3>&1
.:
ls: cannot access 'noexistABC': No such file or directory
total 4
-rw-rw-r-- 1 frank frank 0 Aug 19 12:26 bar.txt
-rw-rw-r-- 1 frank frank 0 Aug 19 12:26 foo.txt
drwxrwxr-x 2 frank frank 4096 Aug 19 12:26 someFolder
一个天真的进程替换使用似乎允许单独过滤stderr
和stdout
:
:; ( echo out ; echo err >&2 ) 2> >( sed s/^/e:/ >&2 )
out
e:err
注意,stderr
输出在标准错误流上,而stdout
则输出在标准输出流上,我们可以通过将整个内容包装在另一个子shell中并重定向到文件o
和e
来查看。
( ( echo out ; echo err >&2 ) 2> >( sed s/^/e:/ >&2 ) ) 1>o 2>e
$
作为命令提示符。然后他们经常编写像这样的 shell 示例:$ cat /var/log/syslog | fgrep ...
。但是,由于 $
的存在,这行代码无法直接复制粘贴。:;
看起来像一个提示符,但实际上它只是一个 shell 中的 no-op;因此,您可以安全地选择并粘贴整行代码。 - solidsnack我发现使用bash进程替换更容易记忆和使用,因为它几乎逐字反映了原始意图。例如:
$ cat ./p
echo stdout
echo stderr >&2
$ ./p 2> >(sed -e 's/s/S/') | sed 's/t/T/'
sTdout
STderr
使用第一个sed命令作为stderr的过滤器,第二个sed命令用于修改已连接的输出。
请注意,2>后面的空格是必须的,以便正确解析该命令。
高级Bash脚本指南的此页面的最后一部分是"仅将stderr重定向到管道"。
这可能是你所需要的内容。如果不是,ABSG 的其他部分应该能够帮助你,它非常优秀。# Redirecting only stderr to a pipe.
exec 3>&1 # Save current "value" of stdout. ls -l 2>&1 >&3 3>&- | grep bad 3>&- # Close fd 3 for 'grep' (but not 'ls'). # ^^^^ ^^^^ exec 3>&- # Now close it for the remainder of the script.
# Thanks, S.C.
看一下命名管道:
$ mkfifo err
$ cmd1 2>err |cat - err |cmd2
err
的写入操作,直到 cat
完成对其的读取,但是 cat
在完成读取标准输入之前不会打开它,而在 cmd1
死亡之前它无法完成读取标准输入,但是 cmd1
无法启动直到它完成打开 err
。这是一个死锁。fifo(7)
记录了这一点:“通常,打开 FIFO 会阻塞,直到另一端也被打开。” - JoL