如何在不停止写入终端的情况下复制stderr?

时间:2014-11-13 18:01:41

标签: bash stderr

我想编写一个运行命令的shell脚本,在它到达时将其stderr写入终端。但是,我还想将stderr保存到变量中,以便稍后检查它。

我怎样才能做到这一点?我应该使用tee,子shell还是别的什么?

我试过这个:

# Create FD 3 that can be used so stdout still comes through 
exec 3>&1

# Run the command, piping stdout to normal stdout, but saving stderr.
{ ERROR=$( $@ 2>&1 1>&3) ; }

echo "copy of stderr: $ERROR"

但是,这不会将stderr写入控制台,只会保存它。

我也试过了:

{ $@; } 2> >(tee stderr.txt >&2 )

echo "stderr was:"
cat stderr.txt

但是,我不想要临时文件。

2 个答案:

答案 0 :(得分:4)

我经常想要这样做,发现自己只能找到/dev/stderr,但这种做法可能存在问题;例如,Nix构建脚本提供"权限被拒绝"如果他们尝试写入/dev/stdout/dev/stderr,则会出错。

重新发明这个轮子几次后,我目前的方法是使用过程替换如下:

myCmd 2> >(tee >(cat 1>&2))

从外面读这个:

这将运行myCmd,将其stdout保持原样。 2>会将myCmd的stderr重定向到其他目的地;这里的目的地是>(tee >(cat 1>&2)),这将导致它被传送到命令tee >(cat 1>&2)

tee命令将其输入(在本例中为myCmd的stderr)复制到其stdout和给定目标。此处的目标是>(cat 1>&2),这会将数据通过管道传输到命令cat 1>&2

cat命令只是将其输入直接传递给stdout。 1>&2将stdout重定向到stderr。

从内到外阅读:

cat 1>&2命令将其stdin重定向到stderr,因此>(cat 1>&2)的行为类似于/dev/stderr

因此tee >(cat 1>&2)将其stdin复制到stdout和stderr,就像tee /dev/stderr一样。

我们使用2> >(tee >(cat 1>&2))获取2份stderr:一份在stdout上,一份在stderr上。

我们可以正常使用stdout上的副本,例如将其存储在变量中。我们可以将副本留在stderr上打印到终端。

如果我们愿意,我们可以将其与其他重定向结合起来,例如

# Create FD 3 that can be used so stdout still comes through 
exec 3>&1

# Run the command, redirecting its stdout to the shell's stdout,
# duplicating its stderr and sending one copy to the shell's stderr
# and using the other to replace the command's stdout, which we then
# capture
{ ERROR=$( $@ 2> >(tee >(cat 1>&2)) 1>&3) ; }

echo "copy of stderr: $ERROR"

答案 1 :(得分:2)

@Etan Reisner对该方法的基本原则表示赞赏;但是,最好使用tee/dev/stderr 而不是/dev/tty以保持正常行为(如果您发送到/dev/tty,则为世界并不认为它是stderr输出,既不能捕获也不能抑制它):

这是完整的成语:

exec 3>&1   # Save original stdout in temp. fd #3.
# Redirect stderr to *captured* stdout, send stdout to *saved* stdout, also send
# captured stdout (and thus stderr) to original stderr.
errOutput=$("$@" 2>&1 1>&3 | tee /dev/stderr)
exec 3>&-   # Close temp. fd.

echo "copy of stderr: $errOutput"