2010-12-21 146 views
9

出于测试目的,我想保存stdout和单独标准错误通过后续的代码检查。例如,错误输入的测试运行应该导致输出到标准错误,但没有任何标准输出,而测试运行正确的输入应导致输出到标准输出,但没有标准错误。节省必须是同步的,以避免与测试竞争条件(所以我不能使用process substitution)。保存标准输出,标准错误和标准输出+标准错误同步

为了能够在事后调试测试,我需要看到stdout和stderr在它们输出的顺序。因此,我必须将它们保存到相同的文件/变量/任何内容,或者将它们同时保存到终端。

要测试发生了哪个错误,我还需要命令的退出代码

由于效率和准确性的原因,我当然不能每次运行两次测试。

它是例如可能重定向标准输出到stdout.log,stderr到stderr.log,他们都在同一个命令output.log?或者对stdout和stderr分别使用同步tee命令?或者保存stdout和stderr的副本来分离变量?

更新:它看起来像Tim的解决方案几乎作品(修改为输出端上,而不是记录到all.log):

$ set -o pipefail 
$ { 
    { 
     echo foo | tee stdout.log 2>&3 3>&- 
    } 2>&1 >&4 4>&- | tee stderr.log 2>&3 3>&- 
} 3>&2 4>&1 
foo 
$ cat stdout.log 
foo 
$ cat stderr.log 
$ { 
    { 
     echo foo >&2 | tee stdout.log 2>&3 3>&- 
    } 2>&1 >&4 4>&- | tee stderr.log 2>&3 3>&- 
} 3>&2 4>&1 
foo 
$ cat stdout.log 
$ cat stderr.log 
foo 
$ bar=$({ 
    { 
     echo foo | tee stdout.log 2>&3 3>&- 
    } 2>&1 >&4 4>&- | tee stderr.log 2>&3 3>&- 
} 3>&2 4>&1) 
$ echo "$bar" 
foo 
$ cat stdout.log 
foo 
$ cat stderr.log 
$ bar=$({ 
    { 
     echo foo >&2 | tee stdout.log 2>&3 3>&- 
    } 2>&1 >&4 4>&- | tee stderr.log 2>&3 3>&- 
} 3>&2 4>&1) 
$ cat stdout.log 
$ cat stderr.log 
foo 
$ echo "$bar" 
foo 

这似乎除了最后一次迭代,其中值工作的bar被设置为stderr的内容。任何建议所有这些工作?

回答

11

请参阅BashFAQ/106。吃蛋糕并吃它也不容易。

在这个页面:

但是有些人不会接受输出和错误,或者行不同步之间的分离或者损失。他们是纯粹主义者,所以他们要求所有最难的形式 - 我想将stdout和stderr一起记录到一个文件中,但我也希望他们保持原来的单独目的地。

为了做到这一点,我们首先要提出几点注意事项:

  • 如果有将是两个独立的输出和错误流,那么一些过程中有写他们每个人。
  • 由于shell没有poll(2)或select(2)接口,因此无法在shell脚本中编写一个进程,该进程从两个独立的FD中读取数据,只要其中一个FD可用。
  • 因此,我们需要两个独立的写入器进程。
  • 保持两个独立作者的输出不会互相破坏的唯一方法是确保它们都以附加模式打开它们的输出。以追加模式打开的FD具有保证的属性,即每次写入数据时,都会首先跳到结尾。

所以:

# Bash 
> mylog 
exec > >(tee -a mylog) 2> >(tee -a mylog >&2) 

echo A >&2 
cat file 
echo B >&2 

这将确保日志文件是正确的。它并不能保证作家的下一个shell提示符之前完成:

~$ ./foo 
A 
hi mom 
B 
~$ cat mylog 
A 
hi mom 
B 
~$ ./foo 
A 
hi mom 
~$ B 

另请参阅BashFAQ/002BashFAQ/047

+1

假设我们正在谈论基于行的字符串,我们可以管两个流通过,说,'sed'在行首添加标记?然后,我们可以从日志文件中知道哪一行来自哪个流。 – Raphael 2017-05-10 09:04:45

+1

@Raphael:你应该能够在每个进程替换内部的'tee'之前,例如:'exec>>(sed's/^/from STDOUT:/'| tee -a mylog)2>>(sed's/^ /来自STDERR:/'| tee -a mylog>&2)'(未经测试)。顺便说一下,这将导致输出到终端的也有这些前缀。 – 2017-05-10 14:24:28

+0

酷!这不会使您给出的部分报价无效吗?现在我们有一个sychronized共享输出文件,并且仍然知道哪个是哪个,或者我错过了什么? – Raphael 2017-05-10 18:20:28

2

这听起来像是一个多任务的任务。

要测试同步输出或磁盘写入,而不使用进程替换可能要玩弄的Bash shell重定向技巧和T(1)类似以下内容:

echos() { echo "hello on stdout"; echo "hello on stderr" 1>&2; return 0; } 

    { 
    { 
    echos 3>&- | 
     tee stdout.log 2>&3 3>&- 
    } 2>&1 >&4 4>&- | 
     tee stderr.log 2>&3 3>&- 
} 3>&2 4>&1 2>&1 | tee /dev/stderr > all.log 

open -e stdout.log stderr.log all.log # Mac OS X 

欲了解更多信息,请参见:http://codesnippets.joyent.com/posts/show/8769

要获取命令的退出码,你可能要分别检查出pipefail或PIPESTATUS(除了最后退出码变量“$?”):

help set | grep -i pipefail 
man bash | less -p pipefail 
man bash | less -p PIPESTATUS 
+0

PIPESTATUS似乎不起作用 - 如果回声命令返回1,它仍然只有两个零。 – l0b0 2010-12-21 13:30:42

+0

'set -o pipefail'虽然工作。 – l0b0 2010-12-21 14:00:59

1

最后一次迭代,其中条的值设置为标准错误的内容,请尝试:

# restore original stdout & stderr at the end by adding: 1>&2 2>&1 
bar="$(
{ 
    { 
     echo foo >&2 | tee stdout.log 2>&3 3>&- 
    } 2>&1 >&4 4>&- | tee stderr.log 2>&3 3>&- 
} 3>&2 4>&1 1>&2 2>&1 
)" 

echo "$bar" 
相关问题