给定如下脚本,如果我尝试将 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
会截断重定向文件