【问题标题】:Log bash output (stderr, and stdout) to multiple files one at a time in one script在一个脚本中一次将 bash 输出(stderr 和 stdout)记录到多个文件
【发布时间】:2020-09-04 09:12:42
【问题描述】:

假设您有一个 bash 脚本,并且您想要打印并将输出(stderr 和 stdout)保存到日志文件中。基于这个答案:@cdarke 的https://stackoverflow.com/a/49514467/835098,这就是你可以做到的。

#!/bin/bash

exec > >(tee my.log) 2>&1

echo "Hello"

但是,如果您有一个脚本,其中不同的部分需要转到不同的日志文件,该怎么办?假设您想将典型的configuremakemake test 输出分别放入各自的日志文件中?一种天真的方法可能看起来像这样(为了简单起见,configure 和类似的在这里变成了echos):

#!/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 <==

例如,这种方法的一个缺点是,如果命令成功,最终的日志文件将可用。实际上,这很糟糕,也是寻找其他解决方案的好理由。

【问题讨论】:

    标签: bash exec file-descriptor


    【解决方案1】:

    原因是,您在每次调用 exec 时都复制了 stdout 文件描述符,从而导致您从所有三个 echo 调用的输出到所有三个文件。即在设置初始exec 之后,当你这样做时

    exec > >(tee make.log) 2>&1
    echo "make"
    

    不仅最近的 echo'd 语句发送到 make.log,而且还发送到 configure.log,因为您之前的 exec 确实设置了写入该日志文件以获取来自 stdout 的内容。现在第一个日志文件包含来自两个 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}&gt; 时,bash 会自动使用文件描述符(通常大于 10)填充 fd1 变量。现在您可以像以前一样在通话中使用$fd1tee。和以前一样,您可以通过将它们关闭来释放已使用的描述符

    exec >&"$fd1"-
    exec >&"$fd2"-
    exec >&"$fd3"-
    

    如果您希望 stderr 也转到相应的日志文件,请将重定向 2&gt;&amp;1 更改为打开的相应文件描述符,即

    2>&3        
    2>&"$fd1"
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-03-29
      • 1970-01-01
      • 2021-01-12
      • 1970-01-01
      • 2023-01-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多