将stdout的COPY重定向到bash脚本本身的日志文件

Vit*_*ner 224 bash shell logging redirect

我知道如何将stdout重定向到文件:

exec > foo.log
echo test
Run Code Online (Sandbox Code Playgroud)

这会将'test'放入foo.log文件中.

现在我想将输出重定向到日志文件并将其保存在stdout上

即它可以从脚本外部轻松完成:

script | tee foo.log
Run Code Online (Sandbox Code Playgroud)

但我想在脚本本身内声明它

我试过了

exec | tee foo.log
Run Code Online (Sandbox Code Playgroud)

但它不起作用.

Dev*_*lar 284

#!/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
Run Code Online (Sandbox Code Playgroud)

请注意,这bash不是sh.如果使用调用脚本sh myscript.sh,则会出现错误syntax error near unexpected token '>'.

如果您正在使用信号陷阱,则可能需要使用该tee -i选项以避免在发生信号时中断输出.(感谢JamesThomasMoon1979的评论.)


根据是否写入管道或终端(ls例如,使用颜色和列化输出)来更改输出的工具将检测上述构造,意​​味着它们输出到管道.

有一些选项可以强制执行着色/列化(例如ls -C --color=always).请注意,这将导致颜色代码也被写入日志文件,从而降低了可读性.

  • @Barry:[POSIX](http://pubs.opengroup.org/onlinepubs/007904875/utilities/tee.html)指定`tee`不应缓冲其输出.如果它在大多数系统上都有缓冲,那么它在大多数系统上都会被破坏.这是'tee`实现的问题,而不是我的解决方案. (12认同)
  • 我建议将`-i`传递给`tee`.否则,信号中断(陷阱)将中断主脚本中的stdout.例如,如果你有一个'陷阱'echo foo'EXIT`然后按`ctrl + c`,你将看不到"*foo*".所以我会修改`exec&>>(tee -ia文件)`的答案. (10认同)
  • 大多数系统上的T恤都是缓冲的,因此在脚本完成之后输出可能无法到达.此外,由于此tee在子shell中运行,而不是子进程,因此wait不能用于将输出同步到调用进程.你想要的是一个类似于http://bogomips.org/rainbows.git/commit/?id=95417ca711a75612da86a25acd20134efdbc0e67的无缓冲版tee (3认同)
  • @Sebastian:`exec`非常强大,但也很参与.您可以将当前标准输出"备份"到不同的文件描述符,然后稍后恢复.谷歌"bash exec教程",有很多高级的东西. (2认同)
  • @AdamSpiers:我也不确定巴里是怎么回事.Bash的`exec`是*记录*不是为了启动新进程,`>(tee ...)`是一个标准的命名管道/进程替换,并且重定向中的`&`当然与后台无关. .?:-) (2认同)
  • @DevSolar问题是调用脚本正在调用`sh myscript.sh`而不是`bash myscript.sh`.很抱歉在发布前没有检查. (2认同)
  • 那么,有没有办法恢复输出,或者强制将某些东西输出到真正的原始 STDOUT ? (2认同)
  • 请注意,使用此解决方案,即使在脚本完成后 tee 也会继续运行。这可能会导致例如脚本终止后 SSH 连接未完成。 (2认同)

Ada*_*ers 167

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

./script.sh >/dev/null
Run Code Online (Sandbox Code Playgroud)

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

./script.sh 2>/dev/null
Run Code Online (Sandbox Code Playgroud)

将输出两个foobar终端.显然,这不是普通用户可能期望的行为.这可以通过使用两个单独的tee进程来修复,这两个进程都附加到同一个日志文件:

#!/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
Run Code Online (Sandbox Code Playgroud)

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

>foo.log
Run Code Online (Sandbox Code Playgroud)

到脚本的顶部.)

所述的POSIX.1-2008规范tee(1)要求输出是无缓冲的,即偶数行缓冲没有,所以在这种情况下可能的是,STDOUT和STDERR可以在同一行上的结束foo.log; 然而,这也可能发生在终端上,所以日志文件将是一个什么样的忠实反映可以看出在终端上,如果不是它的精确镜像.如果您希望STDOUT线与STDERR线完全分离,请考虑使用两个日志文件,每行可能带有日期戳前缀,以便稍后按时间顺序重新组装.

  • 我建议将`-i`传递给`tee`.否则,信号中断(陷阱)将中断脚本中的stdout.例如,如果你"回忆"echo foo'EXIT`然后按`ctrl + c`,你将看不到"*foo*".所以我会修改`exec >>(tee -ia foo.log)`的答案. (13认同)
  • 这种方法的问题在于,进入`STDOUT` 的消息首先作为一个批处理出现,然后进入`STDERR` 的消息出现。它们不像通常预期的那样交错。 (3认同)

jba*_*low 27

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

接受的答案肯定是bash的最佳选择.我在没有访问bash的Busybox环境中工作,并且它不理解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
Run Code Online (Sandbox Code Playgroud)


WRe*_*ach 19

在脚本文件中,将所有命令放在括号内,如下所示:

(
echo start
ls -l
echo end
) | tee foo.log
Run Code Online (Sandbox Code Playgroud)

  • {}在当前shell环境中执行列表.()在子shell环境中执行列表. (8认同)
  • 迂腐,也可以使用大括号(`{}`) (5认同)

Tob*_*obu 14

轻松地将bash脚本日志记录到syslog中.脚本输出既可以/var/log/syslog通过stderr 也可以通过stderr获得.syslog将添加有用的元数据,包括时间戳.

在顶部添加此行:

exec &> >(logger -t myscript -s)
Run Code Online (Sandbox Code Playgroud)

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

exec &> >(ts |tee -a /tmp/myscript.output >&2 )
Run Code Online (Sandbox Code Playgroud)

这需要moreutils(对于ts添加时间戳的命令).


小智 10

使用接受的答案我的脚本保持异常提前返回(在'exec >>(tee ...)之后'),让我的脚本的其余部分在后台运行.由于我无法以我的方式使用该解决方案,因此我找到了另一个解决方案/解决问题:

# Logging setup
logfile=mylogfile
mkfifo ${logfile}.pipe
tee < ${logfile}.pipe $logfile &
exec &> ${logfile}.pipe
rm ${logfile}.pipe

# Rest of my script
Run Code Online (Sandbox Code Playgroud)

这使得脚本的输出从进程,通过管道进入"tee"的子后台进程,该进程将所有内容记录到磁盘和脚本的原始stdout.

请注意,'exec&>'重定向stdout和stderr,如果我们愿意,我们可以单独重定向它们,或者如果我们只想要stdout则更改为'exec>'.

即使您在脚本开头将管道从文件系统中删除,它也将继续运行,直到进程完成.我们只是不能使用rm-line之后的文件名来引用它.