这很可能属于KISS(保持简单)的原则,但我仍然很好奇并希望接受教育,为什么我没有收到预期的结果。所以,我们在这里......
我有一个shell脚本来捕获STDOUT和STDERR而不会干扰原始文件描述符。这是为了保留终端上用户看到的原始输出顺序(参见下面的test.pl
)。
不幸的是,我只能使用sh而不是bash(但我欢迎例子),因为我从另一个套件中调用它,我可能希望将来在cron中使用它(我知道cron有{ {1}}环境变量)。
SHELL
包含:
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";
STDOUT包含:
sh wrapper.sh /tmp/xxx perl test.pl
STDERR包含:
1: stdout1
3: stdout2
到目前为止一切都很好......
2: stderr1
包含:
/tmp/xxx
但是,我期待2: stderr1
1: stdout1
3: stdout2
包含:
/tmp/xxx
有人可以向我解释为什么STDOUT和STDERR没有按照我预期的顺序追加1: stdout1
2: stderr1
3: stdout2
吗?我的猜测是,后台/tmp/xxx
进程阻止了tee
资源,因为它们具有相同的&#34;目的地&#34;。你会如何解决这个问题?
相关:How do I write stderr to a file while using "tee" with a pipe?
答案 0 :(得分:2)
这是C运行时库的一个特性(可能被其他运行时库模仿)stderr
未被缓冲。一旦写入,stderr就会将其所有字符推送到目标设备。
默认情况下stdout
有一个512字节的缓冲区。
stderr
和stdout
的缓冲可以通过setbuf
或setvbuf
来电更改。
来自stdout的Linux手册页:
注意:流stderr是无缓冲的。当流stdout指向终端时,它是行缓冲的。在调用fflush(3)或exit(3)或打印换行符之前,不会出现部分行。这可能会产生意外结果,尤其是调试输出时。可以使用setbuf(3)或setvbuf(3)调用来更改标准流(或任何其他流)的缓冲模式。注意,在stdin与终端相关联的情况下,终端驱动程序中也可能存在输入缓冲,与stdio缓冲完全无关。 (实际上,通常终端输入在内核中是行缓冲的。)这个内核输入处理可以使用像tcsetattr(3)这样的调用来修改。另见stty(1)和termios(3)。
答案 1 :(得分:0)
经过@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)立即刷新({{ 1}})。
我正在研究确定这是多么便携。我认为-F 2
可能是Mac和FreeBSD,而-F pipe
或-f
可能是其他发行版......