按时间顺序捕获 STDOUT 和 STDERR
Chronologically capturing STDOUT and STDERR
这很可能属于 KISS(保持简单)原则,但我仍然很好奇并希望了解为什么我没有收到预期结果。那么,我们开始吧...
我有一个 shell 脚本来捕获 STDOUT 和 STDERR 而不会干扰原始文件描述符。这是为了保留用户在终端上看到的原始输出顺序(请参阅下面的 test.pl
)。
不幸的是,我仅限于使用 sh,而不是 bash(但我欢迎示例),因为我是从另一个套件调用它的,我可能希望将来在 cron 中使用它(我知道 cron 有 SHELL
环境变量)。
wrapper.sh
包含:
#!/bin/sh
stdout_and_stderr=
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
资源,因为它们具有相同的 "destination"。你会如何解决这个问题?
相关:How do I write stderr to a file while using "tee" with a pipe?
stderr
没有缓冲是 C 运行时库的一个特性(并且可能被其他运行时库模仿)。一旦写入,stderr 就会将其所有字符推送到目标设备。
默认情况下 stdout
有一个 512 字节的缓冲区。
stderr
和 stdout
的缓冲可以通过 setbuf
or setvbuf
调用更改。
来自标准输出的 Linux 手册页:
NOTES: The stream stderr is unbuffered. The stream stdout is line-buffered when it points to a terminal. Partial lines will not appear until fflush(3) or exit(3) is called, or a newline is printed. This can produce unexpected results, especially with debugging output. The buffering mode of the standard streams (or any other stream) can be changed using the setbuf(3) or setvbuf(3) call. Note that in case stdin is associated with a terminal, there may also be input buffering in the terminal driver, entirely unrelated to stdio buffering. (Indeed, normally terminal input is line buffered in the kernel.) This kernel input handling can be modified using calls like tcsetattr(3); see also stty(1), and termios(3).
在@wallyk 的启发下进行了更多搜索后,我对 wrapper.sh
进行了以下修改:
#!/bin/sh
stdout_and_stderr=
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?
这很可能属于 KISS(保持简单)原则,但我仍然很好奇并希望了解为什么我没有收到预期结果。那么,我们开始吧...
我有一个 shell 脚本来捕获 STDOUT 和 STDERR 而不会干扰原始文件描述符。这是为了保留用户在终端上看到的原始输出顺序(请参阅下面的 test.pl
)。
不幸的是,我仅限于使用 sh,而不是 bash(但我欢迎示例),因为我是从另一个套件调用它的,我可能希望将来在 cron 中使用它(我知道 cron 有 SHELL
环境变量)。
wrapper.sh
包含:
#!/bin/sh
stdout_and_stderr=
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
资源,因为它们具有相同的 "destination"。你会如何解决这个问题?
相关:How do I write stderr to a file while using "tee" with a pipe?
stderr
没有缓冲是 C 运行时库的一个特性(并且可能被其他运行时库模仿)。一旦写入,stderr 就会将其所有字符推送到目标设备。
默认情况下 stdout
有一个 512 字节的缓冲区。
stderr
和 stdout
的缓冲可以通过 setbuf
or setvbuf
调用更改。
来自标准输出的 Linux 手册页:
NOTES: The stream stderr is unbuffered. The stream stdout is line-buffered when it points to a terminal. Partial lines will not appear until fflush(3) or exit(3) is called, or a newline is printed. This can produce unexpected results, especially with debugging output. The buffering mode of the standard streams (or any other stream) can be changed using the setbuf(3) or setvbuf(3) call. Note that in case stdin is associated with a terminal, there may also be input buffering in the terminal driver, entirely unrelated to stdio buffering. (Indeed, normally terminal input is line buffered in the kernel.) This kernel input handling can be modified using calls like tcsetattr(3); see also stty(1), and termios(3).
在@wallyk 的启发下进行了更多搜索后,我对 wrapper.sh
进行了以下修改:
#!/bin/sh
stdout_and_stderr=
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?