Contents

将程序输出重定向到文件和标准流

1. 概述

在本教程中,我们将探讨一些常见的策略来将进程的输出同时重定向到文件和标准流,例如stdoutstderr

2. tee命令

*tee *命令是我们可以用来重定向进程输出的最流行的 Linux 命令之一。

2.1.重定向stdout

让我们举一个简单的用例,将ls命令的输出重定向到stdout和一个临时文件*/tmp/out.log*:

$ ls -C | tee /tmp/out.log
bin   dev  home  lib32	libx32	mnt  proc  run	 srv  tmp  var
boot  etc  lib	 lib64	media	opt  root  sbin  sys  usr

我们可以验证文件的内容是否与执行命令生成的输出相同:

$ cat /tmp/out.log
bin   dev  home  lib32	libx32	mnt  proc  run	 srv  tmp  var
boot  etc  lib	 lib64	media	opt  root  sbin  sys  usr

另一个需要注意的重要事情是tee 命令的默认行为是覆盖文件的内容。但是,如果需要,我们可以选择***-a*选项将新内容附加**到文件的现有内容之后。

2.2. 将stdoutstderr重定向到同一个文件

我们需要了解,在内部,**tee命令充当 **传入stdin的 T-splitter,以便可以将数据重定向到stdout和一个或多个文件。让我们利用这种理解将进程的stderr重定向到stdout和文件:

$ (ls -C; cmd_with_err) 2>&1 | tee /tmp/out.log
bin   dev  home  lib32	libx32	mnt  proc  run	 srv  tmp  var
boot  etc  lib	 lib64	media	opt  root  sbin  sys  usr
bash: cmd_with_err: command not found

我们可以注意到cmd_with_err是一个未知命令,因此它会生成一条错误消息。为了让tee命令可以使用它,我们将stderr文件描述符 ( fd = 2 ) 重定向到stdout文件描述符 ( fd = 1 )。

或者,我们也可以**使用 |& 作为 2>&1| 的简写符号。**得到相同的结果:

$ (ls -C; cmd_with_err) |& tee /tmp/out.log
bin   dev  home  lib32	libx32	mnt  proc  run	 srv  tmp  var
boot  etc  lib	 lib64	media	opt  root  sbin  sys  usr
bash: cmd_with_err: command not found

现在,让我们验证*/tmp/out.log*文件的内容:

$ cat /tmp/out.log
bin   dev  home  lib32	libx32	mnt  proc  run	 srv  tmp  var
boot  etc  lib	 lib64	media	opt  root  sbin  sys  usr
bash: cmd_with_err: command not found

2.3. 将stdoutstderr重定向到单独的文件

在某些情况下,我们可能需要将进程的stdoutstderr重定向到单独的文件。我们可以通过在调用tee 命令时使用进程替换来做到这一点。在此之前,让我们看一下模板代码片段,它将使tee命令能够侦听特定的文件描述符并写回相同的文件描述符流和文件

fd> >(tee file_name fd>&fd)

我们必须注意fd只是文件描述符的占位符,实际值为 1 表示stdout,2 表示stderr,0 表示stdin。 现在,让我们利用这种理解将进程的stdoutstderr输出分别重定向到**/tmp/out.log和*/tmp/err.log*:

$ ((ls -C; cmd_with_err) 1> >(tee /tmp/out.log)) 2> >(tee /tmp/err.log 2>&2)
bin   dev  home  lib32	libx32	mnt  proc  run	 srv  tmp  var
boot  etc  lib	 lib64	media	opt  root  sbin  sys  usr
bash: cmd_with_err: command not found

我们可以验证*/tmp/out.log包含有效的stdout消息,而/tmp/err.log包含来自stderr*的错误消息:

$ cat /tmp/out.log
bin   dev  home  lib32	libx32	mnt  proc  run	 srv  tmp  var
boot  etc  lib	 lib64	media	opt  root  sbin  sys  usr
$ cat /tmp/err.log
bash: cmd_with_err: command not found

3. 重定向延迟

在少数情况下,调用tee命令将进程的输出重定向到文件和stdout可能会引入延迟。在本节中,我们将探索这种情况并学习如何缓解它。

3.1. 设想

让我们看一个简单的 python 脚本,它每隔一秒打印一次当前时间:

$ cat time.py
#!/usr/bin/python
from datetime import datetime
import time
import sys
from sys import stdout
while True:
    sys.stdout.write(datetime.today().strftime("%H:%M:%S %p\n"))
    time.sleep(1)

如果我们执行这个脚本,我们将观察到stdout上写入的任意两个连续时间戳之间有一秒的延迟:

$ ./time.py
 6:49:48 PM
 6:49:49 PM
 6:49:50 PM
 6:49:51 PM
 6:49:52 PM
 6:49:53 PM

3.2. 延迟重定向

现在,让我们使用tee命令将此进程的输出重定向到stdouttime.out文件:

$ ./time.py | tee time.out

与之前不同,我们会注意到很长一段时间没有输出写入stdout,之后大量输出将一次性转储到stdout

延迟是由于 glibc 中 Linux 的stdio缓冲策略而引入的, glibc是 python 内部使用的系统库。缓冲策略导致对stdout的写入通过一个4096字节的缓冲区,从而减少了在流上写入所需的 I/O 调用次数。

对于交互式应用程序,这种重定向延迟是不可接受的。所以,让我们想办法缓解重定向延迟问题。

3.3. 减缓延迟

由于问题的根本原因与将数据刷新到stdout的延迟有关,因此解决此问题的一种方法是确保及时刷新数据以在应用程序代码中流式传输

$ cat time.py
#!/usr/bin/python
from datetime import datetime
import time
import sys
from sys import stdout
while True:
    sys.stdout.write(datetime.today().strftime("%H:%M:%S %p\n"))
    sys.stdout.flush()
    time.sleep(1)

让我们验证延迟确实消失了:

$ ./time.py | tee time.out
19:29:12 PM
19:29:13 PM

在这种情况下,我们可以直接访问应用程序代码,因此我们能够对其进行修改。

但是,在许多情况下,程序可能是可执行的二进制文件,我们可能无权修改它。在这种情况下,我们可以使用 Linux 中的unbuffer命令来解决缓冲写入stdout造成的延迟。

让我们从脚本中删除sys.stdout.flush()方法调用,并使用**unbuffer命令重新执行重定向命令:

$ unbuffer ./time.py | tee time.out
19:34:22 PM
19:34:23 PM

我们可以观察到现在stdout写入没有意外延迟。