将标准错误发送到管道

时间:2013-06-20 19:51:47

标签: 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
OUT 1
ERR 1
OUT 2
ERR 2

这是我的脚本stderr.awk添加STDERR:

#!/bin/awk -f
{print "STDERR: " $0}

如果我运行./testscript.pl | ./stderr.awk(这显然是错误的,因为我标准输出而不是标准错误):

lorkenpeist$ ./testscript.pl | ./stderr.awk
ERR 1
ERR 2
STDERR: OUT 1
STDERR: OUT 2

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

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

lorkenpeist$ ./testscript.pl 2>&1 | ./stderr.awk
STDERR: ERR 1
STDERR: ERR 2
STDERR: OUT 1
STDERR: OUT 2

不仅stderr.awk处理所有内容而不仅仅是标准错误,而且不会保留打印语句的顺序。有没有办法将标准错误发送到stderr.awk,还保留打印语句的顺序?我真正希望看到的是:

OUT 1
STDERR: ERR 1
OUT 2
STDERR: ERR 2

我开始怀疑IO重定向根本就不是答案,但我对其他选择感到茫然。

修改 鉴于标准输出是缓冲的而标准错误不是,看起来我无法完全控制打印语句在终端上出现的顺序。话虽如此,我更希望订单至少在某种程度上保留,而不是在任何标准输出之前打印所有标准错误。或者,有没有办法使标准输出和/或管道无缓冲?

2 个答案:

答案 0 :(得分:4)

Andy Lutomirski一样,我认为How to pipe stderr and not stdout可以提供帮助。

这是一个在不改变标准输出的情况下过滤标准误差的命令序列; ./genouterr.sh生成有关标准输出和标准错误的信息。

( ./genouterr.sh 2>&1 1>&3 | sed 's/^/STDERR: /' >&2) 3>&1

它的作用是:

  • 运行子shell (...)并将其输出从文件描述符3发送到标准输出(3>&1)。
  • 在子shell中,它运行命令./genouterr.sh,将其标准输出汇总到sed
  • 但是,在运行shell脚本之前,它会安排错误输出转到标准输出(2>&1),并将标准输出转到文件描述符3(1>&3)。因此,脚本写入标准输出的任何内容实际上都会转到文件描述符3,而脚本写入标准错误的任何内容都会沿着管道发送。
  • 管道的RHS为sed,在每行的开头插入STDERR:,然后将其标准输出重定向到标准错误(1>&2)。

最终结果是./genouterr.sh在标准输出上写入的内容出现在标准输出上; ./genouterr.sh在标准错误上写的内容以STDERR:为前缀,并显示在标准错误上。

请注意,由于缓冲等原因,命令的输出可能会使标准输出和标准错误交错,与它们在终端屏幕上显示的方式不同,而不进行任何I / O重定向。这非常接近不可避免。如果你要避免它,你就会参与使用伪ttys(ptys),这是一个复杂的领域,如果你需要通过sed过滤某些东西,我也不想保证这种行为。

脚本genouterr.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包含genouterr.sh写入标准输出的内容,而out.2包含过滤后的版本genouterr.sh写给标准错误的内容。

答案 1 :(得分:2)

How to pipe stderr, and not stdout?的答案可能有用。你最好的选择可能是:

(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)。