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

exec > foo.log
echo test

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

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

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

script | tee foo.log

但我想在脚本中声明它

我试着

exec | tee foo.log

但这并没有起作用。


当前回答

使bash脚本日志转换为syslog的简单方法。脚本输出可以通过/var/log/syslog和stderr获得。Syslog将添加有用的元数据,包括时间戳。

在顶部添加这一行:

exec &> >(logger -t myscript -s)

或者,将日志发送到一个单独的文件:

exec &> >(ts |tee -a /tmp/myscript.output >&2 )

这需要更多的utils(用于ts命令,它添加时间戳)。

其他回答

不能说我对任何基于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的类似,但是更简单,也许我的解决方案有我还没有遇到的局限性。

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

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

接受的答案不保留STDERR作为单独的文件描述符。这意味着

./script.sh >/dev/null

会不会输出条到终端,只到日志文件,和

./script.sh 2>/dev/null

将输出foo和bar到终端。显然不是这样的 一个正常用户可能会期待的行为。这可以是 通过使用两个独立的三通过程都附加到相同的固定 日志文件:

#!/bin/bash

# See (and upvote) the comment by JamesThomasMoon1979 
# explaining the use of the -i option to tee.
exec >  >(tee -ia foo.log)
exec 2> >(tee -ia foo.log >&2)

echo "foo"
echo "bar" >&2

(请注意,上面的内容最初并不会截断日志文件——如果你想要这种行为,你应该添加

>foo.log

到脚本的顶部。)

The POSIX.1-2008 specification of tee(1) requires that output is unbuffered, i.e. not even line-buffered, so in this case it is possible that STDOUT and STDERR could end up on the same line of foo.log; however that could also happen on the terminal, so the log file will be a faithful reflection of what could be seen on the terminal, if not an exact mirror of it. If you want the STDOUT lines cleanly separated from the STDERR lines, consider using two log files, possibly with date stamp prefixes on each line to allow chronological reassembly later on.

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

#!/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)。注意,这也会导致将颜色代码写入日志文件,使其可读性降低。