如果重定向 stderr,脚本内的 tee /dev/stderr 会截断输出文件

如果重定向 stderr,脚本内的 tee /dev/stderr 会截断输出文件

给定如下脚本,如果我尝试将 stderr 重定向到文件,则该文件将在tee使用时被截断:

$ cat test.sh 
#!/bin/bash

set -eux
echo before
echo '{ "foo": "bar" }' | tee /dev/stderr | jq .foo
echo after
$ ./test.sh 2> log
before
"bar"
after
$ cat log 
{ "foo": "bar" }
+ echo after

log文件应该包含所有 stderr 输出。如果我运行相同的脚本而不重定向,我看到的所有内容:

$ ./test.sh
+ echo before
before
+ echo '{ "foo": "bar" }'
+ tee /dev/stderr
+ jq .foo
{ "foo": "bar" }
"bar"
+ echo after
after

那么为什么我只看到出现的线条 tee?使用2>>似乎没有帮助。

我不明白为什么会发生这种情况。我怎样才能在保留将整个脚本 stderr 重定向到文件的能力的同时,实现输出?

答案1

这是由于 Linux 中的工作方式/dev/stderr所致/proc/$pid/fd/$num。在那里,开放/dev/stderr确实不是重复文件描述符 2,而是访问 fd 直接连接的资源。

因此,由于tee通常会截断其写入的文件,因此它也会截断tee /dev/stderr.就您而言,这与做几乎相同tee log

var=$(</dev/stdin) 将 stdin 读入变量有什么问题?了解更多详情。


这只是 Linux 上的一个问题。例如,在 macOS 上,它的工作原理与您想象的一样。一个有点压缩的例子:

linux$ bash -xc 'false; echo "truncated?" | tee /dev/stderr >/dev/null; true' 2>log
linux$ cat log
truncated?
+ true

对阵

mac$ bash -xc 'false; echo "truncated?" | tee /dev/stderr >/dev/null; true' 2>log
mac$ cat log
+ false
+ tee /dev/stderr
+ echo 'truncated?'
truncated?
+ true

现在,为了使其工作,您需要避免使用/dev/stderr,而是以某种方式使用类似于>&2告诉 shell 复制文件描述符 2 的东西。

我想你可以用 来做到这一点tee >( cat >&2 )。这有点复杂,但是你不能告诉tee使用现有的 fd,你需要给它一个文件名,并且由于上述问题,它不能直接引用脚本的 stderr。

但是,它存在一个问题,即cat在后台运行,这可能会导致输出延迟,如下所示:

linux$ bash -xc 'false; echo "truncated?" | tee >(cat >&2) >/dev/null; true' 2>log
$ cat log
+ false
+ echo 'truncated?'
+ tee /dev/fd/63
++ cat
+ true
truncated?

请注意,这并tee -a /dev/stderr没有帮助,因为虽然这意味着任何tee写入都会到达文件末尾,但通过原始 stderr 写入的任何内容都会不是,但遵循该文件描述的写入位置。所以你会得到这样的东西:

$ bash -xc 'false; echo "truncated?" | tee -a /dev/stderr >/dev/null; true' 2>log
$ cat log
+ false
+ echo 'truncated?'
+ tee -a /dev/stderr
+ true
ed?

其中最后一个+ true<nl>(来自脚本的 stderr)被写在truncated?(来自tee)上。您还需要将原始重定向设为附加重定向,即bash ... 2>>log

或者只是将原始重定向转到管道而不是文件。使用管道,/dev/stderr工作方式更像您想象的那样,因为管道没有写入位置,并且不能被截断。只是这样做的方法有点复杂,你需要类似的东西2> >(cat > log)

答案2

当我写这篇文章时,我意识到我可能可以使用这个tee -a选项。

我仍然不太明白为什么这样做tee /dev/stderr会截断重定向文件

相关内容