如何在不重复使用echo/printf语句的情况下有条件地将输出复制到文件中?

32 浏览
0 Comments

如何在不重复使用echo/printf语句的情况下有条件地将输出复制到文件中?

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

exec > foo.log
echo test

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

现在我想要将输出重定向到日志文件,并保留在标准输出中。

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

script | tee foo.log

但我想在脚本内部声明它。

我尝试过

exec | tee foo.log

但它没有起作用。

0
0 Comments

这个问题的出现是因为接受的答案没有将标准错误(STDERR)作为单独的文件描述符保存。这意味着将输出重定向到/dev/null时,只会将"bar"输出到日志文件中而不会在终端上显示,而将标准错误重定向到/dev/null时,"foo"和"bar"都会在终端上显示。显然,这不是一个正常用户可能期望的行为。可以通过使用两个单独的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

(注意,上述代码不会最初截断日志文件 - 如果你想要这种行为,应该在脚本的顶部添加`>foo.log`)

POSIX.1-2008规范要求`tee(1)`的输出是无缓冲的,即不是即时缓冲,因此在这种情况下,STDOUT和STDERR可能会出现在`foo.log`的同一行上;然而这也可能发生在终端上,因此日志文件将是终端上可以看到的内容的忠实反映,如果不是一个精确的镜像。如果你想要STDOUT行与STDERR行清晰地分开,可以考虑使用两个日志文件,可能在每行上添加日期时间戳前缀,以便以后按时间顺序重新组装。

在我的情况下,当脚本从C程序的system()调用中执行时,两个tee子进程在主脚本退出后仍然存在。因此,我不得不添加如下陷阱:`exec > >(tee -a $LOG)`、`trap "kill -9 $! 2>/dev/null" EXIT`、`exec 2> >(tee -a $LOG >&2)`、`trap "kill -9 $! 2>/dev/null" EXIT`。

我建议给`tee`传递`-i`选项。否则,信号中断(trap)将中断脚本中的标准输出。例如,如果你`trap 'echo foo' EXIT`,然后按下`ctrl+c`,你将看不到"foo"。因此,我会修改答案为`exec > >(tee -ia foo.log)`。

我根据这个方法创建了一些小的"可源代码化"脚本。可以在脚本中使用`. log`或`. log foo.log`的形式来使用它们:[sam.nipl.net/sh/log](http://sam.nipl.net/sh/log) [sam.nipl.net/sh/log-a](http://sam.nipl.net/sh/log-a)

这种方法的问题是STDOUT的消息首先作为一个批次出现,然后是STDERR的消息。它们没有像通常期望的那样交错出现。

0
0 Comments

问题的原因:在Busybox环境中无法使用bash的方法将输出条件地复制到文件中。

解决方法:通过创建命名管道实现输出的复制。

整理成的文章如下:

在Busybox环境中,我们无法像bash那样使用exec > >(tee log.txt)的语法来将输出复制到文件中。同时,它也无法正确执行exec >$PIPE,会尝试创建一个与命名管道同名的普通文件,导致失败并陷入僵局。

希望这对那些没有bash的人有所帮助。

另外,对于使用命名管道的人来说,可以放心地使用rm $PIPE来删除它,因为这样可以解除管道与VFS的链接,但使用它的进程仍然会保留对它的引用,直到它们完成。

需要注意的是,使用$*可能不是安全的。

下面是一个解决方法的示例代码,适用于没有bash的Busybox环境:

#!/bin/sh
if [ "$SELF_LOGGING" != "1" ]
then
    # 父进程将进入此分支并设置日志记录
    # 为子进程的输出创建一个命名管道
    PIPE=tmp.fifo
    mkfifo $PIPE
    # 通过将stdout重定向到命名管道来启动子进程
    SELF_LOGGING=1 sh $0 $* >$PIPE &
    # 保存子进程的PID
    PID=$!
    # 在单独的进程中启动tee命令
    tee logfile <$PIPE &
    # 删除$PIPE,因为父进程不再需要它
    rm $PIPE    
    # 等待子进程,子进程正在运行此脚本的其余部分
    wait $PID
    # 返回子进程的错误代码
    exit $?
fi
# 脚本的其余部分写在这里

这是我目前看到的唯一在mac上可行的解决方法。

0
0 Comments

在上述代码中,我们可以看到以下问题和解决方法:

问题:

1. 如果将脚本以sh myscript.sh的方式运行,会出现类似于"syntax error near unexpected token '>'"的错误。

2. tee命令默认是带缓冲的,因此输出可能要等到脚本结束后才会出现。

3. 该方法会导致多个tee进程泄漏,如果需要停止日志记录,可能需要手动逐个杀死相关的脚本进程。

4. 如果脚本中使用了颜色输出或者列化输出的工具,这种方法可能会导致日志文件中也包含颜色代码,降低可读性。

解决方法:

1. 使用bash而不是sh来运行脚本,可以解决第一个问题。

2. 使用tee命令的"-i"选项,可以避免在发生信号中断时中断脚本的输出。

3. 脚本中可以使用exec命令将stdout备份到不同的文件描述符,然后在需要时恢复。这样可以控制输出的目标。

根据以上问题和解决方法,我们可以整理成以下文章:

标题:如何在不重复使用echo/printf语句的情况下有条件地将输出复制到文件中?

内容:

在编写脚本时,我们经常需要将输出重定向到文件中进行日志记录。通常情况下,我们会使用echo或printf语句来输出内容,并使用重定向符号(>)将输出保存到文件中。然而,如果我们希望根据条件将输出复制到文件中,而不是每次都重复使用echo/printf语句,该怎么办呢?

我们可以使用以下方法来解决这个问题。首先,我们需要将stdout重定向到一个命名管道中,并使用tee命令将其保存到文件中。具体的代码如下所示:

#!/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.
exec 2>&1
echo "foo"
echo "bar" >&2

需要注意的是,这个方法只适用于bash,而不是sh。如果以"sh myscript.sh"的方式运行脚本,会出现类似于"syntax error near unexpected token '>'"的错误。

此外,如果我们需要处理信号中断时的输出,可以使用tee命令的"-i"选项。这样可以避免在发生信号中断时中断脚本的输出。

但是,需要注意的是,tee命令默认是带缓冲的,因此输出可能要等到脚本结束后才会出现。如果我们希望实时地将输出保存到文件中,可以使用类似于"bogomips.org/rainbows.git/commit/..."的无缓冲版本的tee命令。

另外,需要注意的是,使用这种方法可能会导致tee进程泄漏的问题。如果我们需要停止日志记录,可能需要手动逐个杀死相关的脚本进程。

最后,如果脚本中使用了颜色输出或者列化输出的工具,比如ls命令,这种方法可能会导致日志文件中也包含颜色代码,降低了可读性。可以使用工具的特定选项来强制启用颜色或者列化输出。

我们可以通过将stdout重定向到命名管道并使用tee命令将其保存到文件中,以实现有条件地将输出复制到文件的目的。这种方法可以避免重复使用echo/printf语句,并提供了一种灵活的日志记录方案。

0