将标准输出的 COPY 从 bash 脚本本身重定向到日志文件

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3173131/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 19:25:36  来源:igfitidea点击:

redirect COPY of stdout to log file from within bash script itself

bashshellredirectlogging

提问by Vitaly Kushner

I know how to redirect stdoutto a file:

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

exec > foo.log
echo test

this will put the 'test' into the foo.log file.

这会将“测试”放入 foo.log 文件中。

Now I want to redirect the output into the log file AND keep it on stdout

现在我想将输出重定向到日志文件并将其保存在标准输出中

i.e. it can be done trivially from outside the script:

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

script | tee foo.log

but I want to do declare it within the script itself

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

I tried

我试过

exec | tee foo.log

but it didn't work.

但它没有用。

回答by DevSolar

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

Note that this is bash, not sh. If you invoke the script with sh myscript.sh, you will get an error along the lines of syntax error near unexpected token '>'.

请注意,这是bash,不是sh。如果您使用 调用脚本sh myscript.sh,您将收到类似syntax error near unexpected token '>'.

If you are working with signal traps, you might want to use the tee -ioption to avoid disruption of the output if a signal occurs. (Thanks to JamesThomasMoon1979 for the comment.)

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



Tools that change their output depending on whether they write to a pipe or a terminal (lsusing colors and columnized output, for example) will detect the above construct as meaning that they output to a pipe.

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

There are options to enforce the colorizing / columnizing (e.g. ls -C --color=always). Note that this will result in the color codes being written to the logfile as well, making it lessreadable.

有选项可以强制着色/列化(例如ls -C --color=always)。请注意,这将导致颜色代码被写入日志文件为好,使之可读性。

回答by Adam Spiers

The accepted answer does not preserve STDERR as a separate file descriptor. That means

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

./script.sh >/dev/null

will not output barto the terminal, only to the logfile, and

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

./script.sh 2>/dev/null

will output both fooand barto the terminal. Clearly that's not the behaviour a normal user is likely to expect. This can be fixed by using two separate tee processes both appending to the same log file:

将同时输出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

(Note that the above does not initially truncate the log file - if you want that behaviour you should add

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

>foo.log

to the top of the script.)

到脚本的顶部。)

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 couldbe 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.

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

回答by jbarlow

Solution for busybox, macOS bash, and non-bash shells

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

The accepted answer is certainly the best choice for bash. I'm working in a Busybox environment without access to bash, and it does not understand the exec > >(tee log.txt)syntax. It also does not do exec >$PIPEproperly, trying to create an ordinary file with the same name as the named pipe, which fails and hangs.

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

Hopefully this would be useful to someone else who doesn't have bash.

希望这对没有 bash 的其他人有用。

Also, for anyone using a named pipe, it is safe to rm $PIPE, because that unlinks the pipe from the VFS, but the processes that use it still maintain a reference count on it until they are finished.

此外,对于任何使用命名管道的人来说,使用 是安全的rm $PIPE,因为这会断开管道与 VFS 的链接,但是使用它的进程在完成之前仍然对其保持引用计数。

Note the use of $* is not necessarily safe.

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

#!/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 
(
echo start
ls -l
echo end
) | tee foo.log
$* >$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

回答by WReach

Inside your script file, put all of the commands within parentheses, like this:

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

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

回答by Tobu

Easy way to make a bash script log to syslog. The script output is available both through /var/log/syslogand through stderr. syslog will add useful metadata, including timestamps.

将 bash 脚本记录到 syslog 的简单方法。脚本输出既可以/var/log/syslog通过 stderr获得,也可以通过 stderr 获得。syslog 将添加有用的元数据,包括时间戳。

Add this line at the top:

在顶部添加此行:

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

Alternatively, send the log to a separate file:

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

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

# Rest of my script

This requires moreutils(for the tscommand, which adds timestamps).

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

回答by fgunger

Using the accepted answer my script kept returning exceptionally early (right after 'exec > >(tee ...)') leaving the rest of my script running in the background. As I couldn't get that solution to work my way I found another solution/work around to the problem:

使用接受的答案,我的脚本一直异常早地返回(就在 'exec > >(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) 
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
$@ >> log.txt TEE=false
exec >foo.log
tail -f foo.log &
# rest of your script
$@ 2>&1 | tee --append log.txt exit $? fi } check_tee_output $@ rest of my script

This makes output from script go from the process, through the pipe into the sub background process of 'tee' that logs everything to disc and to original stdout of the script.

这使得脚本的输出从进程通过管道进入“tee”的子后台进程,该进程将所有内容记录到光盘和脚本的原始标准输出。

Note that 'exec &>' redirects both stdout and stderr, we could redirect them separately if we like, or change to 'exec >' if we just want stdout.

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

Even thou the pipe is removed from the file system in the beginning of the script it will continue to function until the processes finishes. We just can't reference it using the file name after the rm-line.

即使您在脚本开始时从文件系统中删除了管道,它也会继续运行,直到进程完成。我们只是不能使用 rm 行后的文件名来引用它。

回答by Paused until further notice.

Bash 4 has a coproccommand which establishes a named pipe to a command and allows you to communicate through it.

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

回答by Oliver

Can't say I'm comfortable with any of the solutions based on exec. I prefer to use tee directly, so I make the script call itself with tee when requested:

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

PIPE=tmp.fifo
mkfifo $PIPE
exec >$PIPE
tee foo.log <$PIPE &
# rest of your script
rm $PIPE

This allows you to do this:

这允许您执行以下操作:

##代码##

You can customize this, e.g. make tee=false the default instead, make TEE hold the log file instead, etc. I guess this solution is similar to jbarlow's, but simpler, maybe mine has limitations that I have not come across yet.

您可以自定义它,例如将 tee=false 设为默认值,让 TEE 保存日志文件等。我想这个解决方案类似于 jbarlow 的,但更简单,也许我的有一些限制,我还没有遇到过。

回答by David Z

Neither of these is a perfect solution, but here are a couple things you could try:

这些都不是完美的解决方案,但您可以尝试以下几点:

##代码##

or

或者

##代码##

The second one would leave a pipe file sitting around if something goes wrong with your script, which may or may not be a problem (i.e. maybe you could rmit in the parent shell afterwards).

如果您的脚本出现问题,第二个将留下一个管道文件,这可能是也可能不是问题(即,您可能rm之后可以在父 shell 中使用它)。