问题描述
假设您有一个bash脚本,并且要打印并将输出(stderr和stdout)保存到日志文件。基于以下答案:@cdarke的https://stackoverflow.com/a/49514467/835098,这就是您的操作方式。
#!/bin/bash
exec > >(tee my.log) 2>&1
echo "Hello"
但是,如果您有一个脚本,其中需要将不同部分转到不同的日志文件,该怎么办?假设您要将典型的configure
,make
,make test
输出分开到各自的日志文件中?天真的方法可能看起来像这样(为了简单起见,configure
在这里变成了echo
):
#!/bin/bash
# clear logs
rm -f configure.log make.log make_test.log
exec > >(tee configure.log) 2>&1
echo "configure"
exec > >(tee make.log) 2>&1
echo "make"
exec > >(tee make_test.log) 2>&1
echo "make test"
但是在执行此脚本时,您会注意到只有最后一个输出包含了应该包含的内容:
$ tail *.log
==> configure.log <==
configure
make
make test
==> make.log <==
make
make test
==> make_test.log <==
make test
还要注意,每个日志文件都以正确的输出开头。我考虑过将一个中间文件复制到最终目的地后,坚持使用一个日志文件并截断它。该脚本有效,但我想知道它是否有用:
#!/bin/bash
# clear logs
rm -f configure.log make.log make_test.log tmp.log
exec > >(tee tmp.log) 2>&1
echo "configure"
cp tmp.log configure.log && truncate -s 0 tmp.log
echo "make"
cp tmp.log make.log && truncate -s 0 tmp.log
echo "make test"
cp tmp.log make_test.log && truncate -s 0 tmp.log
$ tail *.log
==> configure.log <==
configure
==> make.log <==
make
==> make_test.log <==
make test
==> tmp.log <==
例如,该方法的缺点是,如果命令成功,则最终的日志文件将可用。实际上,这非常糟糕,也是找到其他解决方案的充分理由。
解决方法
原因是因为您在每次对exec
的调用中都复制了stdout文件描述符,从而导致对所有三个文件的所有三个echo
调用的输出。即在设置了初始exec
之后,当您这样做
exec > >(tee make.log) 2>&1
echo "make"
不仅最近的echo'd语句进入了make.log
,而且也进入了configure.log
,因为您先前的exec
确实为标准输出的内容设置了对该日志文件的写入。现在,第一个日志文件包含来自两个echo'd语句的行。在您的下一种情况下也会再次复制。
为避免此问题,请为每种唯一情况设置不同的文件描述符,即
exec 3> >(tee configure.log) 2>&1
echo "configure" >&3
exec 4> >(tee make.log) 2>&1
echo "make" >&4
exec 5> >(tee make_test.log) 2>&1
echo "make test" >&5
# Releasing the file descriptors back to shell
exec 3>&-
exec 4>&-
exec 5>&-
或者如果您使用的bash版本> 4.1,则可以让shell为您分配文件描述符,这样您就不必显式选择数字了。
exec {fd1}> >(tee configure.log) 2>&1
echo "configure" >&"$fd1"
exec {fd2}> >(tee make.log) 2>&1
echo "make" >&"$fd2"
exec {fd3}> >(tee make_test.log) 2>&1
echo "make test" >&"$fd3"
执行exec {fd1}>
时,bash会自动使用文件描述符(通常大于10)填充fd1
变量。现在,您可以像以前一样在通话中使用$fd1
和tee
。和以前一样,您可以通过将其关闭为
exec >&"$fd1"-
exec >&"$fd2"-
exec >&"$fd3"-
如果您还希望stderr也转到相应的日志文件,请将重定向2>&1
更改为打开的相应文件描述符,即
2>&3
2>&"$fd1"