Unix 将标准错误发送到管道

Unix 将标准错误发送到管道,unix,io-redirection,Unix,Io Redirection,我正在尝试编写一个简单的脚本,它接受标准输出和标准错误,并将单词STDERR:放在标准错误的每一行的开头。对于测试,我有一个简单的脚本,它在标准输出和标准错误之间交替输出几行: #!/usr/bin/perl print "OUT 1\n"; print STDERR "ERR 1\n"; print "OUT 2\n"; print STDERR "ERR 2\n"; lorkenpeist$ ./testscript.pl | ./stderr.awk ERR 1 ERR 2 STDERR

我正在尝试编写一个简单的脚本,它接受标准输出和标准错误,并将单词
STDERR:
放在标准错误的每一行的开头。对于测试,我有一个简单的脚本,它在标准输出和标准错误之间交替输出几行:

#!/usr/bin/perl
print "OUT 1\n";
print STDERR "ERR 1\n";
print "OUT 2\n";
print STDERR "ERR 2\n";
lorkenpeist$ ./testscript.pl | ./stderr.awk
ERR 1
ERR 2
STDERR: OUT 1
STDERR: OUT 2
当我运行它时:

lorkenpeist$ ./testscript.pl
OUT 1
ERR 1
OUT 2
ERR 2
下面是我的脚本
stderr.awk
添加
stderr:

#!/bin/awk -f
{print "STDERR: " $0}
如果我运行
/testscript.pl |/stderr.awk
(这显然是错误的,因为我是在输出标准而不是标准错误):

我看到标准err立即输出,而标准输出由于管道而延迟。打印语句的原始顺序不保留

我还可以将标准错误重定向到标准输出:

lorkenpeist$ ./testscript.pl 2>&1 | ./stderr.awk
STDERR: ERR 1
STDERR: ERR 2
STDERR: OUT 1
STDERR: OUT 2
不仅所有内容都由
stderr.awk
处理,而不仅仅是标准err,而且打印语句的顺序也没有保留。有没有办法只将标准错误发送到
stderr.awk
,并保留打印语句的顺序?我真正想看到的是:

OUT 1
STDERR: ERR 1
OUT 2
STDERR: ERR 2
我开始怀疑IO重定向根本不是答案,但我对替代方案一无所知

编辑: 考虑到标准输出是缓冲的,而标准err不是缓冲的,看来我无法完全控制打印语句在终端上的显示顺序。这就是说,我更希望至少在某种程度上保留顺序,而不是在任何标准输出之前打印所有标准错误。或者,是否有办法使标准输出和/或管道无缓冲?

中的答案可能有用。您的最佳选择可能是:

(echo 'out'; echo 'err' >&2; echo 'out2'; echo 'err2' >&2) 2> >(sed -e 's/^/STDERR: /' >&2)
不过,这存在同步性问题——最后两行STDERR将在bash认为命令完成后编写

为了改进它,您可以将sed命令作为一个单独的异步进程启动,然后显式地等待它,但这很快就会变得混乱

不过,您需要小心:libc不会缓冲对stderr的写入。对于更具确定性的行为,您可以通过创建pseuto tty来确保stdout是一个tty(有关详细信息,请参阅pty的手册页——这方面的文档很少)。不过,对于那些花哨的东西,您可能必须使用真正的编程语言(例如C或Python)。

中的答案可能很有用。您的最佳选择可能是:

(echo 'out'; echo 'err' >&2; echo 'out2'; echo 'err2' >&2) 2> >(sed -e 's/^/STDERR: /' >&2)
不过,这存在同步性问题——最后两行STDERR将在bash认为命令完成后编写

为了改进它,您可以将sed命令作为一个单独的异步进程启动,然后显式地等待它,但这很快就会变得混乱

不过,您需要小心:libc不会缓冲对stderr的写入。对于更具确定性的行为,您可以通过创建pseuto tty来确保stdout是一个tty(有关详细信息,请参阅pty的手册页——这方面的文档很少)。不过,对于那些花哨的东西,您可能必须使用真正的编程语言(例如C或Python)。

我认为这会有所帮助

下面是一个命令序列,它过滤标准错误而不更改标准输出
/genouter.sh
生成有关标准输出和标准错误的信息

( ./genouterr.sh 2>&1 1>&3 | sed 's/^/STDERR: /' >&2) 3>&1
它的作用是:

  • 运行子shell
    (…)
    ,并将其输出从文件描述符3发送到标准输出(
    3>&1
  • 在子shell中,它运行命令
    /genouter.sh
    ,将其标准输出传输到
    sed
  • 但是,在运行shell脚本之前,它将错误输出安排到标准输出(
    2>&1
    ),并将标准输出安排到文件描述符3(
    1>&3
    )。因此,脚本写入标准输出的任何内容实际上都会进入文件描述符3,而脚本写入标准错误的任何内容都会进入管道
  • 管道的RHS是
    sed
    ,在每行的开头插入
    STDERR:
    ,然后将其标准输出重定向到标准错误(
    1>&2
最终结果是,
/genouter.sh
在标准输出上写入的内容出现在标准输出上;
/genouter.sh
在标准错误中写入的内容以
STDERR:
为前缀,并显示在标准错误中

( ./genouterr.sh 2>&1 1>&3 | sed 's/^/STDERR: /' >&2) 3>&1
请注意,由于缓冲等原因,命令的输出可能会交错标准输出和标准错误,这与它们在终端屏幕上显示的方式不同,而无需任何I/O重定向。这几乎是不可避免的。如果要避免这种情况,就要使用伪ttys(ptys),这是一个复杂的领域,即使需要通过
sed
过滤某些内容,我也不想保证这种行为

脚本
genouter.sh
只生成标准输出和标准错误行:

#!/bin/bash
for i in {01..50}
do
  echo "stdout $i"
  echo "stderr $i" >&2
done
我做的最初测试(我在一两个月前写了这个,作为扩展另一个问题的练习)是寻找包含以0结尾的数字的标准错误行(使用
grep
);将其更改为
sed
脚本是一项非常艰巨的工作

#!/bin/bash
set -x
rm -f out.[123]
./genouterr.sh 1>/dev/null
./genouterr.sh 2>/dev/null
( ./genouterr.sh 2>&1 1>&3 | grep '[0-9]0' >&2) 3>out.3 2>out.2 1>out.1
ls -l out.[123]
( ./genouterr.sh 2>&1 1>&3 | grep '[0-9]0' >&2) 3>&1
运行此脚本时,您会发现文件
out.1
为空,
out.3
包含
genouter.sh
写入标准输出的内容,
out.2
包含
genouter.sh
写入标准错误的内容的过滤版本。

我认为这会有所帮助

( ./genouterr.sh 2>&1 1>&3 | sed 's/^/STDERR: /' >&2) 3>&1
下面是一个命令序列,它过滤标准错误而不更改标准输出
/genouter.sh
生成有关标准输出和标准错误的信息

( ./genouterr.sh 2>&1 1>&3 | sed 's/^/STDERR: /' >&2) 3>&1
它的作用是:

  • 运行子shell
    (…)
    ,并将其输出从文件描述符3发送到标准输出(
    3>&1