如何在 shell 脚本中将文本输出到屏幕和文件?

如何在 shell 脚本中将文本输出到屏幕和文件?

目前我有一个 shell 脚本,它将消息记录到日志文件中,如下所示:

log_file="/some/dir/log_file.log"
echo "some text" >> $log_file
do_some_command
echo "more text" >> $log_file
do_other_command

执行此脚本时,屏幕上没有输出,并且由于我通过 putty 连接到服务器,因此我必须打开另一个连接并执行“tail -f log_file_path.log”,因为我无法终止运行脚本,我想实时查看输出。

显然,我想要的是将文本消息打印在屏幕上并打印到文件中,但我想在一行中完成,而不是两行,其中一行没有重定向到文件。

如何实现这一目标?

答案1

这有效:

command | tee -a "$log_file"

tee将输入保存到文件(使用-a附加而不是覆盖),并将输入复制到标准输出。

因为该命令可以检测到它现在正在以非交互方式运行,所以这可能会改变它的行为。最常见的副作用是它会禁用颜色输出。如果发生这种情况(并且您想要 ANSI 颜色编码输出),您必须检查命令文档以查看是否有办法强制其恢复为交互行为,例如grep --color=always.请注意,这意味着日志文件将包含这些转义代码,您需要使用less --RAW-CONTROL-CHARS "$log_file"它来读取它,而不会分散转义代码文字的注意力。另请注意,没有办法使日志文件内容不同的运行上述命令时打印到屏幕上的内容,因此您不能将颜色编码的输出显示到屏幕上,而将非彩色输出显示在日志文件中。

答案2

您可以使用此处文档和 .将其用于高效、POSIX 友好的通用收集器模型。

. 8<<-\IOHERE /proc/self/fd/8

command
fn() { declaration ; } <<WHATEVER
# though a nested heredoc might be finicky
# about stdin depending on shell
WHATEVER
cat -u ./stdout | ./works.as >> expect.ed
IOHERE

当您打开heredoc时,您使用IOHERE输入标记向shell发出信号,指示它应该将其输入重定向到您指定的文件描述符,直到它遇到限制器标记的另一端。我环顾四周,但没有看到很多使用重定向 fd 号的示例,正​​如我上面结合 heredoc 运算符所示的那样,尽管 POSIX 基本 shell 命令指南中明确指定了它的用法。大多数人只是将其指向 stdin 并进行射击,但我发现以这种方式获取 scriptlet 可以保持 stdin 自由,并且组成应用程序不会抱怨 I/O 路径阻塞。

Heredoc 的内容会流式传输到您指定的文件描述符,然后该文件描述符会被解释为 shell 代码并由 .内置的,但并非没有指定 的特定路径。 。如果 /proc/self 路径给您带来麻烦,请尝试 /dev/fd/n 或 /proc/$$。顺便说一句,同样的方法也适用于管道:

cat ./*.sh | . /dev/stdin

可能至少像看起来那样不明智。当然,您可以对 sh 执行相同的操作,但是 . 的目的是在当前 shell 环境中执行,这可能正是您想要的,并且根据您的 shell,与heredoc一起使用的可能性更大使用标准匿名管道。

无论如何,正如您可能已经注意到的那样,我仍然没有回答您的问题。但如果你仔细想想,就像heredoc将所有代码流式传输到.中一样,它也为你提供了一个单一的、简单的出点:

. 5<<EOIN /dev/fd/5 |\ 
    tee -a ./log.file | cat -u >$(tty)
script
more script
EOIN

因此,在你的heredoc中执行的任何代码中的所有终端标准输出都是从.当然,可以很容易地从一根管道上开球。我包含了无缓冲的 cat 调用,因为我不清楚当前的标准输出方向,但它可能是多余的(几乎可以肯定它是如所写的那样)并且管道可能会在 tee 处结束。

您可能还会质疑第二个示例中缺少的反斜杠引号。在开始之前了解这一部分很重要,并且可能会给您一些关于如何使用它的想法。带引号的heredoc限制器(到目前为止我们已经使用了IOHERE和EOIN,第一个我用反斜杠引用,尽管“单”或“双”引号可以达到相同的目的)将禁止shell在内容,但未加引号的限制符将使其内容开放以进行扩展。当你的定界符是 时,这样做的后果是。来源是戏剧性的:

. 3<<HD ${fdpath}/3
: \${vars=$(printf '${var%s=$((%s*2))},' `seq 1 100`)} 
HD
echo $vars
> 4,8,12… 
echo $var1 $var51
> 4 104

因为我没有引用定界符限制器,所以 shell 在读取内容时以及将生成的文件描述符提供给 .执行。这本质上导致命令被解析两次——无论如何都是可扩展的。因为我反斜杠引用了 $vars 参数扩展,所以 shell 在第一次传递时忽略了它的声明,只删除了反斜杠,因此整个 printf 扩展内容可以在 时由 null 求值。在第二遍中获取了脚本。

此功能基本上正是危险的 eval shell 内置函数可以提供的功能,即使在定界文档中引用比在 eval 中更容易处理,并且同样危险。除非您仔细计划,否则最好习惯性地引用“EOF”限制器。只是说。

编辑:嗯,我回头看这一点,觉得这有点太过了。如果您需要做的只是将多个输出连接到一个管道中,那么最简单的方法就是使用:

{ or ( command ) list ; } | tee -a ea.sy >>pea.sy

花括号将尝试运行当前 shell 中的内容,而括号将自动分出。尽管如此,任何人都可以告诉你这一点,至少在我看来,. Heredoc 解决方案是更有价值的信息,特别是如果您想了解 shell 的实际工作原理。

玩得开心!

答案3

如果您不想使用 tee 语句来运行它,请执行以下操作:

#!/bin/bash
# A Shell subroutine to echo to screen and a log file

log_file_name="/some/dir/log_file.log"

echolog()
(
    echo "$@"
    echo "$@" >> $log_file_name
)


echo "no need to log this"
echolog "some important text that needs logging"

所以现在在我的原始脚本中,我可以将“echo”更改为“echolog”,我希望在日志文件中输出。

答案4

我在 Macos 上使用,它应该也可以在 Linux 机器上工作。

command > /dev/stdin > mylog.log

相关内容