【问题标题】:Chronologically capturing STDOUT and STDERR按时间顺序捕获 STDOUT 和 STDERR
【发布时间】:2018-10-31 07:59:40
【问题描述】:

这很可能属于 KISS(保持简单)原则,但我仍然很好奇并希望了解为什么我没有收到预期的结果。所以,我们开始...

我有一个 shell 脚本来捕获 STDOUT 和 STDERR 而不会干扰原始文件描述符。这是为了保留用户在终端上看到的原始输出顺序(参见下面的test.pl)。

不幸的是,我仅限于使用 sh,而不是 bash(但我欢迎示例),因为我是从另一个套件中调用它的,我可能希望将来在 cron 中使用它(我知道 cron 有 @ 987654323@环境变量)。

wrapper.sh 包含:

#!/bin/sh
stdout_and_stderr=$1
shift
command=$@

out="${TMPDIR:-/tmp}/out.$$"
err="${TMPDIR:-/tmp}/err.$$"
mkfifo ${out} ${err}
trap 'rm ${out} ${err}' EXIT
> ${stdout_and_stderr}
tee -a ${stdout_and_stderr} < ${out} &
tee -a ${stdout_and_stderr} < ${err} >&2 &
${command} >${out} 2>${err}

test.pl 包含:

#!/usr/bin/perl

print "1: stdout1\n";
print STDERR "2: stderr1\n";
print "3: stdout2\n";

在场景中:

sh wrapper.sh /tmp/xxx perl test.pl

STDOUT 包含:

1: stdout1
3: stdout2

STDERR 包含:

2: stderr1

到目前为止一切都很好......

/tmp/xxx 包含:

2: stderr1
1: stdout1
3: stdout2

但是,我期待 /tmp/xxx 包含:

1: stdout1
2: stderr1
3: stdout2

谁能向我解释为什么 STDOUT 和 STDERR 没有按照我预期的顺序附加/tmp/xxx?我的猜测是后台的tee 进程正在阻止/tmp/xxx 资源,因为它们具有相同的“目的地”。你会如何解决这个问题?

相关:How do I write stderr to a file while using "tee" with a pipe?

【问题讨论】:

  • 无论你在这里做什么,如果tees 中的一个被阻塞了一段时间(因为系统负载过重,或其他原因),通过它的数据也会被延迟。确实没有办法解决这个问题,因为进程的输出不包含可用于在另一端正确安排写入的排序或时间戳。

标签: linux unix sh tee


【解决方案1】:

stderr 不被缓冲是 C 运行时库的一个特性(并且可能被其他运行时库模仿)。一旦写入,stderr 就会将其所有字符推送到目标设备。

默认情况下stdout 有一个 512 字节的缓冲区。

stderrstdout 的缓冲可以通过 setbufsetvbuf 调用来更改。

来自标准输出的 Linux 手册页:

注意:流 stderr 是无缓冲的。当流标准输出指向终端时,它是行缓冲的。在调用 fflush(3) 或 exit(3) 或打印换行符之前,部分行不会出现。这可能会产生意想不到的结果,尤其是在调试输出时。可以使用 setbuf(3) 或 setvbuf(3) 调用更改标准流(或任何其他流)的缓冲模式。请注意,如果 stdin 与终端相关联,终端驱动程序中也可能存在输入缓冲,与 stdio 缓冲完全无关。 (实际上,通常终端输入在内核中是行缓冲的。)可以使用诸如 tcsetattr(3) 之类的调用来修改此内核输入处理;另请参见 stty(1) 和 termios(3)。

【讨论】:

    【解决方案2】:

    经过一番搜索,受到@wallyk 的启发,我对wrapper.sh 进行了以下修改:

    #!/bin/sh
    stdout_and_stderr=$1
    shift
    command=$@
    
    out="${TMPDIR:-/tmp}/out.$$"
    err="${TMPDIR:-/tmp}/err.$$"
    mkfifo ${out} ${err}
    trap 'rm ${out} ${err}' EXIT
    > ${stdout_and_stderr}
    tee -a ${stdout_and_stderr} < ${out} &
    tee -a ${stdout_and_stderr} < ${err} >&2 &
    script -q -F 2 ${command} >${out} 2>${err}
    

    现在产生预期的:

    1: stdout1
    2: stderr1
    3: stdout2
    

    解决方案是在$command 前加上script -q -F 2,这使得script 相当(-q),然后强制文件描述符2(STDOUT)立即刷新(-F 2)。

    我现在正在研究以确定它的便携性。我认为-F pipe 可能是 Mac 和 FreeBSD,-f--flush 可能是其他发行版...

    相关:How to make output of any shell command unbuffered?

    【讨论】:

    • 现在我正在努力解决调用脚本的参数扩展问题。我正在尝试将命令写入 tmp 文件并让 wrapper.sh 读取它,但这只会变得比它的价值更麻烦。也许有人会为此找到另一种用途
    猜你喜欢
    • 2013-09-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-05
    • 2019-07-03
    • 2012-02-29
    • 2013-01-12
    • 2020-06-23
    相关资源
    最近更新 更多