如何让STDOUT和STDERR转到终端和日志文件?

JPL*_*mme 77 bash shell logging

我有一个脚本,将由非技术用户以交互方式运行.该脚本将状态更新写入STDOUT,以便用户可以确保脚本运行正常.

我希望STDOUT和STDERR都重定向到终端(这样用户就可以看到脚本工作正常,看看是否有问题).我还希望将两个流重定向到日志文件.

我在网上看到了很多解决方案.有些不起作用,有些则非常复杂.我已经开发了一个可行的解决方案(我将作为答案输入),但它很笨拙.

完美的解决方案是一行代码,可以合并到任何脚本的开头,该脚本将两个流发送到终端和日志文件.

编辑:将STDERR重定向到STDOUT并将结果传递给tee工作,但这取决于用户记住重定向和管道输出.我希望日志记录是万无一失的(这就是为什么我希望能够将解决方案嵌入脚本本身.)

Pau*_*lin 132

使用"tee"重定向到文件和屏幕.根据您使用的shell,首先必须使用stderr将stderr重定向到stdout

./a.out 2>&1 | tee output
Run Code Online (Sandbox Code Playgroud)

要么

./a.out |& tee output
Run Code Online (Sandbox Code Playgroud)

在csh中,有一个名为"script"的内置命令可以捕获进入文件的所有内容.您可以通过键入"script"来启动它,然后执行您要捕获的任何操作,然后按control-D关闭脚本文件.我不知道sh/bash/ksh的等价物.

此外,由于您现在已经指出这些是您自己可以修改的sh脚本,您可以通过用括号或括号括起整个脚本来内部进行重定向,例如

  #!/bin/sh
  {
    ... whatever you had in your script before
  } 2>&1 | tee output.file
Run Code Online (Sandbox Code Playgroud)

  • 请注意,stdout和stderr之间的区别将会丢失,因为tee会将所有内容打印到stdout. (6认同)
  • 我不知道你可以在shell脚本中括起命令.有趣. (3认同)
  • @Flimm,有没有办法(任何其他方式)保留 stdout 和 stderr 之间的区别? (3认同)
  • 我也很欣赏括号快捷方式!由于某种原因,`2>&1 | tee -a filename` 没有将 stderr 从我的脚本保存到文件中,但当我复制命令并将其粘贴到终端时,它工作得很好!不过,括号技巧效果很好。 (2认同)
  • 仅供参考:大多数脚本中都提供“脚本”命令(它是util-linux软件包的一部分) (2认同)
  • 如果需要保留脚本的退出状态,可以将其与 Jason Sydes/MatrixManAtYrService 的答案结合起来,变为:“{ ... } 1> >(tee -a output.file) 2>&1”。所包含脚本的输出顺序将被保留,但对“output.file”的写入将有点滞后。 (2认同)

Mat*_*ice 16

模式

the_cmd 1> >(tee stdout.txt ) 2> >(tee stderr.txt >&2 )
Run Code Online (Sandbox Code Playgroud)

这会分别重定向 stdout 和 stderr,并将stdout 和 stderr 的单独副本发送给调用者(可能是您的终端)。

  • 在 zsh 中,在tees 完成之前,它不会继续执行下一条语句。

  • 在 bash 中,您可能会发现最后几行输出出现接下来出现的任何语句之后。

在任何一种情况下,正确的位都到正确的地方。


解释

这是一个脚本(存储在 ./example 中):

#! /usr/bin/env bash
the_cmd()
{
    echo out;
    1>&2 echo err;
}

the_cmd 1> >(tee stdout.txt ) 2> >(tee stderr.txt >&2 )
Run Code Online (Sandbox Code Playgroud)

这是一个会话:

$ foo=$(./example)
    err

$ echo $foo
    out

$ cat stdout.txt
    out

$ cat stderr.txt
    err
Run Code Online (Sandbox Code Playgroud)

这是它的工作原理:

  1. 两个tee进程都启动了,它们的标准输入被分配给文件描述符。因为它们包含在进程替换中,这些文件描述符的路径在调用命令中被替换,所以现在看起来像这样:

the_cmd 1> /proc/self/fd/13 2> /proc/self/fd/14

  1. the_cmd 运行,将 stdout 写入第一个文件描述符,将 stderr 写入第二个。

  2. 在 bash 情况下,一旦the_cmd完成,以下语句立即发生(如果您的终端是调用者,那么您将看到提示出现)。

  3. 在 zsh 的情况下,一旦the_cmd完成,shell 会等待两个tee进程都完成后再继续。更多关于这里

  4. 第一个tee进程从the_cmd的标准输出中读取,将标准输出的副本写回给调用者,因为这就是tee它的作用。它的输出不会被重定向,所以它们会原封不动地返回给调用者

  5. 第二个tee进程将它stdout重定向到调用者的stderr(这很好,因为它的 stdin 正在从the_cmd的 stderr读取)。因此,当它写入其标准输出时,这些位将进入调用者的标准错误。

这使文件和命令输出中的 stderr 与 stdout 分开。

如果第一个 tee 写入任何错误,它们将同时显示在 stderr 文件和命令的 stderr 中,如果第二个 tee 写入任何错误,它们将仅显示在终端的 stderr 中。

  • @DonHatch你能提出一个解决这个问题的解决方案吗? (2认同)

Jas*_*des 14

差不多五年后......

我相信这是OP寻求的"完美解决方案".

这是一个可以添加到Bash脚本顶部的单行程序:

exec > >(tee -a $HOME/logfile) 2>&1
Run Code Online (Sandbox Code Playgroud)

这是一个演示其用途的小脚本:

#!/usr/bin/env bash

exec > >(tee -a $HOME/logfile) 2>&1

# Test redirection of STDOUT
echo test_stdout

# Test redirection of STDERR
ls test_stderr___this_file_does_not_exist
Run Code Online (Sandbox Code Playgroud)

(注意:这仅适用于Bash.它不适用于/ bin/sh.)

改编自这里 ; 根据我的判断,原始文件没有在日志文件中捕获STDERR.修复了这里的注释.

  • 请注意,stdout和stderr之间的区别将会丢失,因为tee会将所有内容打印到stdout. (2认同)
  • 与迄今为止提出的大多数其他解决方案一样,该解决方案容易出现竞争问题。也就是说,当当前脚本完成并返回时,无论是用户提示符还是某些更高级别的调用脚本,在后台运行的 tee 仍将运行,并且可能会将最后几行发送到屏幕并发送给日志文件迟到(即,在提示之后到屏幕,以及在日志文件预计完成之后到日志文件)。 (2认同)

Don*_*tch 8

编辑:我发现我出轨了,最终回答了与提出的问题不同的问题。真正问题的答案位于保罗·汤布林答案的底部。(如果您想增强该解决方案以出于某种原因分别重定向 stdout 和 stderr,您可以使用我在此处描述的技术。)


我一直想要一个保留 stdout 和 stderr 之间区别的答案。不幸的是,迄今为止给出的所有保留这种区别的答案都容易出现种族问题:正如我在评论中指出的那样,它们冒着程序看到不完整输入的风险。

我想我终于找到了一个保留区别的答案,不存在种族倾向,而且也不是非常繁琐。

第一个构建块:交换 stdout 和 stderr:

my_command 3>&1 1>&2 2>&3-
Run Code Online (Sandbox Code Playgroud)

第二个构建块:如果我们只想过滤(例如 tee)stderr,我们可以通过交换 stdout 和 stderr,过滤,然后交换回来来实现:

{ my_command 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3-
Run Code Online (Sandbox Code Playgroud)

现在剩下的很简单:我们可以在开头添加一个标准输出过滤器:

{ { my_command | stdout_filter;} 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3-
Run Code Online (Sandbox Code Playgroud)

或者在最后:

{ my_command 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3- | stdout_filter
Run Code Online (Sandbox Code Playgroud)

为了让自己相信上述两个命令都有效,我使用了以下命令:

alias my_command='{ echo "to stdout"; echo "to stderr" >&2;}'
alias stdout_filter='{ sleep 1; sed -u "s/^/teed stdout: /" | tee stdout.txt;}'
alias stderr_filter='{ sleep 2; sed -u "s/^/teed stderr: /" | tee stderr.txt;}'
Run Code Online (Sandbox Code Playgroud)

输出是:

...(1 second pause)...
teed stdout: to stdout
...(another 1 second pause)...
teed stderr: to stderr
Run Code Online (Sandbox Code Playgroud)

正如预期的那样,我的提示在“”之后立即返回teed stderr: to stderr

关于 zsh 的脚注

上面的解决方案在 bash 中有效(也许还有其他一些 shell,我不确定),但在 zsh 中不起作用。zsh 失败的原因有两个:

  1. 2>&3-zsh 无法理解该语法;必须重写为2>&3 3>&-
  2. 在 zsh 中(与其他 shell 不同),如果您重定向已经打开的文件描述符,在某些情况下(我不完全理解它是如何决定的),它会执行内置的类似 tee 的行为。为了避免这种情况,您必须在重定向之前关闭每个 fd。

因此,例如,我的第二个解决方案必须为 zsh 重写为{my_command 3>&1 1>&- 1>&2 2>&- 2>&3 3>&- | stderr_filter;} 3>&1 1>&- 1>&2 2>&- 2>&3 3>&- | stdout_filter(它也适用于 bash,但非常冗长)。

另一方面,您可以利用 zsh 神秘的内置隐式 tee 来获得更短的 zsh 解决方案,它根本不运行 tee:

my_command >&1 >stdout.txt 2>&2 2>stderr.txt
Run Code Online (Sandbox Code Playgroud)

(我不会从我发现的文档中猜到 和>&12>&2触发 zsh 隐式发球的东西;我通过反复试验发现了这一点。)


flo*_*olo 5

将 stderr 重定向到 stdout 在您的命令中附加此:2>&1 对于输出到终端并登录到文件,您应该使用tee

两者放在一起看起来像这样:

 mycommand 2>&1 | tee mylogfile.log
Run Code Online (Sandbox Code Playgroud)

编辑:为了嵌入到你的脚本中,你会做同样的事情。所以你的脚本

#!/bin/sh
whatever1
whatever2
...
whatever3
Run Code Online (Sandbox Code Playgroud)

最终会变成

#!/bin/sh
( whatever1
whatever2
...
whatever3 ) 2>&1 | tee mylogfile.log
Run Code Online (Sandbox Code Playgroud)

  • 请注意,stdout 和 stderr 之间的区别将丢失,因为 tee 将所有内容打印到 stdout。 (2认同)