1

我正在尝试编写一个简单的脚本,该脚本采用标准输出和标准错误并将单词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

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

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

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

不仅所有内容都由stderr.awk标准 err 处理,而且打印语句的顺序也没有被保留。有没有办法只发送标准 err 到stderr.awk,并保留打印语句的顺序?我真正想看到的是:

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

我开始怀疑 IO 重定向根本不是答案,但我不知所措。

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

4

2 回答 2

4

Andy Lutomirski一样,我认为How to pipe stderr 而不是 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

于 2013-06-21T01:01:41.517 回答
2

如何管道标准错误的答案,而不是标准输出?可能有用。你最好的选择可能是这样的:

(echo 'out'; echo 'err' >&2; echo 'out2'; echo 'err2' >&2) 2> >(sed -e 's/^/STDERR: /' >&2)

但是,这存在同步性问题——最后几行 STDERR 行将在 bash 认为命令完成后编写。

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

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

于 2013-06-21T00:10:08.993 回答