我知道如何重定向标准输出到一个文件:

exec > foo.log
echo test

这将把“test”放到foo.log文件中。

现在我想将输出重定向到日志文件中,并将其保持在stdout

也就是说,它可以在脚本之外简单地完成:

script | tee foo.log

但我想在脚本中声明它

我试着

exec | tee foo.log

但这并没有起作用。


当前回答

busybox、macOS bash和非bash shell的解决方案

公认的答案当然是bash的最佳选择。我在Busybox环境中工作,没有访问bash,它不理解exec > >(tee log.txt)语法。它也没有正确地执行exec >$PIPE,试图创建一个与命名管道同名的普通文件,该文件失败并挂起。

希望这对那些没有bash的人有用。

同样,对于任何使用命名管道的人来说,rm $ pipe是安全的,因为这将断开管道与VFS的链接,但是使用它的进程仍然在它上维护引用计数,直到它们完成。

注意,使用$*并不一定安全。

#!/bin/sh

if [ "$SELF_LOGGING" != "1" ]
then
    # The parent process will enter this branch and set up logging

    # Create a named piped for logging the child's output
    PIPE=tmp.fifo
    mkfifo $PIPE

    # Launch the child process with stdout redirected to the named pipe
    SELF_LOGGING=1 sh $0 $* >$PIPE &

    # Save PID of child process
    PID=$!

    # Launch tee in a separate process
    tee logfile <$PIPE &

    # Unlink $PIPE because the parent process no longer needs it
    rm $PIPE    

    # Wait for child process, which is running the rest of this script
    wait $PID

    # Return the error code from the child process
    exit $?
fi

# The rest of the script goes here

其他回答

#!/usr/bin/env bash

# Redirect stdout ( > ) into a named pipe ( >() ) running "tee"
exec > >(tee -i logfile.txt)

# Without this, only stdout would be captured - i.e. your
# log file would not contain any error messages.
# SEE (and upvote) the answer by Adam Spiers, which keeps STDERR
# as a separate stream - I did not want to steal from him by simply
# adding his answer to mine.
exec 2>&1

echo "foo"
echo "bar" >&2

请注意,这是bash,而不是sh。如果您使用sh myscript.sh调用脚本,您将得到一个语法错误,接近意外令牌'>'。

如果使用信号陷阱,可能需要使用tee -i选项来避免出现信号时输出中断。(感谢JamesThomasMoon1979的评论。)


根据写入管道还是终端而改变输出的工具(例如,ls使用颜色和柱状输出)将检测上述结构是否意味着它们输出到管道。

有一些选项可以强制着色/柱状化(例如ls -C——color=always)。注意,这也会导致将颜色代码写入日志文件,使其可读性降低。

Bash 4有一个coproc命令,它建立了一个到命令的命名管道,并允许您通过它进行通信。

在你的脚本文件中,把所有的命令都放在括号里,就像这样:

(
echo start
ls -l
echo end
) | tee foo.log

不能说我对任何基于exec的解决方案都感到满意。我更喜欢直接使用tee,所以我在请求时使用tee调用脚本本身:

# my script: 

check_tee_output()
{
    # copy (append) stdout and stderr to log file if TEE is unset or true
    if [[ -z $TEE || "$TEE" == true ]]; then 
        echo '-------------------------------------------' >> log.txt
        echo '***' $(date) $0 $@ >> log.txt
        TEE=false $0 $@ 2>&1 | tee --append log.txt
        exit $?
    fi 
}

check_tee_output $@

rest of my script

这允许你这样做:

your_script.sh args           # tee 
TEE=true your_script.sh args  # tee 
TEE=false your_script.sh args # don't tee
export TEE=false
your_script.sh args           # tee

你可以自定义它,例如让tee=false作为默认值,让tee保存日志文件,等等。我想这个解决方案与jbarlow的类似,但是更简单,也许我的解决方案有我还没有遇到的局限性。

busybox、macOS bash和非bash shell的解决方案

公认的答案当然是bash的最佳选择。我在Busybox环境中工作,没有访问bash,它不理解exec > >(tee log.txt)语法。它也没有正确地执行exec >$PIPE,试图创建一个与命名管道同名的普通文件,该文件失败并挂起。

希望这对那些没有bash的人有用。

同样,对于任何使用命名管道的人来说,rm $ pipe是安全的,因为这将断开管道与VFS的链接,但是使用它的进程仍然在它上维护引用计数,直到它们完成。

注意,使用$*并不一定安全。

#!/bin/sh

if [ "$SELF_LOGGING" != "1" ]
then
    # The parent process will enter this branch and set up logging

    # Create a named piped for logging the child's output
    PIPE=tmp.fifo
    mkfifo $PIPE

    # Launch the child process with stdout redirected to the named pipe
    SELF_LOGGING=1 sh $0 $* >$PIPE &

    # Save PID of child process
    PID=$!

    # Launch tee in a separate process
    tee logfile <$PIPE &

    # Unlink $PIPE because the parent process no longer needs it
    rm $PIPE    

    # Wait for child process, which is running the rest of this script
    wait $PID

    # Return the error code from the child process
    exit $?
fi

# The rest of the script goes here