将进程的输出重定向到文件和流?
概述
我们将了解一些将进程输出重定向到文件和标准流(如 STDOut 和 STDERR)的同时的方法。
tee 命令
Tee 是我们最常用的 Linux 命令行工具之一,可用于重定向进程的输出。它也称为“teeing”或“piping”。tee 命令接受两个参数 - 您希望将重定向的输出保存到的文件名,以及将用于写入原始输入的另一个文件名。
重定向stdout
开始了!我们将看一个简单的例子,将 ls(列表)命令的输出重定向到 stdout 和一个名为 out.log 的临时文件。
$ ls -C | tee /tmp/out.log bin dev home mnt proc run srv tmp var boot etc lib64 opt root sbin sys usr
我们可以确认文件的内容与执行命令生成的输出匹配。
$ cat /tmp/out.log bin dev home mnt proc run srv tmp var boot etc lib64 opt root sbin sys usr
另一个需要记住的重要一点是,tee 命令的默认行为是替换文件的内容。但是,如果需要,我们可以使用 -a(追加)命令在文件的现有内容之后添加新内容。
将stdout 和stderr 重定向到同一个文件
在内部,tee 命令充当传入流的 T 形分流器,以便可以将数据同时定向到输出流和一个或多个文件。我们可以利用此知识将进程的 stderrs 重定向到 stdouts 和文件。
$ (ls -C; cmd_with_err) 2>&1 | tee /tmp/out.log bin dev home mnt proc run srv tmp var boot etc lib64 opt root sbin sys usr bash: cmd_with_err: command not found
我们可以看到 cmd_with_error 是一个未知命令,这意味着它会产生错误消息。我们将 stderror fd(fd=2)重定向到 stdin fd(fd=1),以便 tee 可以同时从两个文件读取。
或者,我们也可以使用 |& 作为 2>&1| 的简写来获得相同的结果 -
$ (ls -C; cmd_with_err) |& tee /tmp/out.log bin dev home mnt proc run srv tmp var boot etc lib64 opt root sbin sys usr bash: cmd_with_err: command not found
现在,让我们验证 /tmp/out.log 文件的内容 -
$ cat /tmp/out.log bin dev home mnt proc run srv tmp var boot etc lib64 opt root sbin sys usr bash: cmd_with_err: command not found
将stdout 和stderr 重定向到不同的文件
有时我们可能希望将进程的输出重定向到两个不同的文件。我们可以使用进程替换来调用 tee 命令。在详细介绍 tee() 函数之前,以下是一个启用 tee() 函数监听特定 fd(文件描述符)并写回原始 fd 流和文件的模板代码片段示例。
fd> >(tee file_name fd>&fd)
我们必须指出,fd 只是一个文件描述符的示例;实际值对于 stdout 为 1,对于 stderror 为 2,对于 stdin 为 0。
现在,让我们利用我们对流的理解,将命令的 stdout 流重定向到 /tmp/out,将 stderr 流重定向到 /tmp/err。
$ ((ls -C; cmd_with_err) 1> >(tee /tmp/out.log)) 2> >(tee /tmp/err.log 2>&2) bin dev home mnt proc run srv tmp var boot etc lib64 opt root sbin sys usr bash: cmd_with_err: command not found
我们可以确认 /tmp/outfile.log 具有正确的输出,但 /tmp/errorfile.log 具有错误的错误消息。
$ cat /tmp/out.log bin dev home mnt proc run srv tmp var boot etc lib64 opt root sbin sys usr $ cat /tmp/err.log bash: cmd_with_err: command not found
重定向延迟
调用 tee (tee) 命令将进程的输出定向到文件和 STDOUT 可能会导致延迟。我们将研究一个我们可能希望避免的特定情况,并学习在发生这种情况时如何减轻它。
场景
开始了!我们将编写一个 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
")) time.sleep(1)
如果我们运行此脚本,我们将看到脚本打印出的每个时间戳之间有 1 秒的延迟。
$ ./time.py 9:29:48 PM 9:29:49 PM 9:29:50 PM 9:29:51 PM 9:29:52 PM 9:29:53 PM
延迟重定向
现在,让我们使用“te”命令将此脚本的输出重定向到 stdout 和“time.out”日志。
$ ./time.py | tee time.out
我们将看到,stdout 在很长一段时间内没有任何输出打印,然后会一次性打印出一大块 stdout。
Python 的内部缓冲区大小设置为 1MB,这意味着当写入 stdout 时,输出可能会延迟长达 1 秒才能实际出现在屏幕上。缓冲策略会导致写入 stdout 的内容通过 4096 字节(或更大)的缓冲区传递,从而减少输出文本到终端所需的 I/O 次数。
对于交互式应用程序,必须避免从一个页面重定向到另一个页面的任何延迟。为了解决重定向延迟问题,我们需要寻找减轻它的方法。
缓解措施
解决此问题的一种方法是确保定期将程序的输出刷新到控制台。
$ 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
")) sys.stdout.flush() time.sleep(1) Let's check if the delay has been removed: $ ./time.py | tee time.out 21:46:12 PM 21:46:13 PM
我们可以直接控制应用程序代码,因此我们可以更改它。
但是,在许多情况下,程序可能是编译后的二进制文件,我们可能无法访问其代码进行修改。如果我们想避免与写入 stdout 相关的延迟,我们可以使用 Linux 中的“unbuffer”程序来解决此问题。
让我们从我们的代码中删除 sys.stdout.flush() 方法调用,并使用 unbuffer 命令再次运行重定向。
$ unbuffer ./time.py | tee time.out 21:52:22 PM 21:52:23 PM
更改代码后,stdout 写入操作没有出现意外延迟。
结论
我们查看了将程序的输出同时重定向到 stdin 和 stdouts 的几个示例。我们还学习了一些解决缓冲请求导致的重定向延迟问题的技巧。