如何使用Popen同时写入stdout和日志文件?

61 投票
3 回答
73668 浏览
提问于 2025-04-17 19:48

我正在使用Popen来调用一个持续将输出和错误信息写入日志文件的shell脚本。有没有办法同时将这个日志文件的内容持续输出到屏幕上,或者让这个shell脚本同时写入日志文件和屏幕?

我基本上想在Python中做这样的事情:

cat file 2>&1 | tee -a logfile #"cat file" will be replaced with some script

这里的意思是,把错误信息和正常输出一起传给tee,tee会同时把它们写到屏幕和我的日志文件里。

我知道怎么在Python中把正常输出和错误信息写入日志文件。让我困惑的是,怎么把这些内容再复制到屏幕上:

subprocess.Popen("cat file", shell=True, stdout=logfile, stderr=logfile)

当然,我可以这样做,但有没有办法在不使用tee和shell文件描述符重定向的情况下实现呢?

subprocess.Popen("cat file 2>&1 | tee -a logfile", shell=True)

3 个回答

5

逐字逐字地写入终端,适用于交互式应用

这种方法会立即将接收到的每一个字节写入标准输出,这样更能模拟tee的行为,特别是对于交互式应用来说。

main.py

#!/usr/bin/env python3
import os
import subprocess
import sys
with subprocess.Popen(sys.argv[1:], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as proc, \
        open('logfile.txt', 'bw') as logfile:
    while True:
        byte = proc.stdout.read(1)
        if byte:
            sys.stdout.buffer.write(byte)
            sys.stdout.flush()
            logfile.write(byte)
            # logfile.flush()
        else:
            break
exit_status = proc.returncode

sleep.py

#!/usr/bin/env python3
import sys
import time
for i in range(10):
    print(i)
    sys.stdout.flush()
    time.sleep(1)

首先,我们可以进行一个非交互式的简单检查:

./main.py ./sleep.py

我们可以看到它实时地在标准输出上计数。

接下来,进行一个交互式测试,你可以运行:

./main.py bash

然后你输入的字符会立即在终端上显示出来,这对于交互式应用来说非常重要。这就是当你运行:

bash | tee logfile.txt

时发生的情况。

另外,如果你希望输出能立即显示在输出文件中,你也可以添加一个:

logfile.flush()

但是tee并不会这样做,我担心这会影响性能。你可以通过以下方式轻松测试这一点:

tail -f logfile.txt

相关问题:来自子进程命令的实时输出

在Ubuntu 18.04和Python 3.6.7上进行了测试。

18

要模拟这个命令:subprocess.call("command 2>&1 | tee -a logfile", shell=True),但不使用tee命令:

#!/usr/bin/env python2
from subprocess import Popen, PIPE, STDOUT

p = Popen("command", stdout=PIPE, stderr=STDOUT, bufsize=1)
with p.stdout, open('logfile', 'ab') as file:
    for line in iter(p.stdout.readline, b''):
        print line,  #NOTE: the comma prevents duplicate newlines (softspace hack)
        file.write(line)
p.wait()

如果输出有延迟,可能会遇到缓冲问题,可以查看这个链接了解解决方法:Python: 从subprocess.communicate()读取流式输入

下面是Python 3的版本:

#!/usr/bin/env python3
import sys
from subprocess import Popen, PIPE, STDOUT

with Popen("command", stdout=PIPE, stderr=STDOUT, bufsize=1) as p, \
     open('logfile', 'ab') as file:
    for line in p.stdout: # b'\n'-separated lines
        sys.stdout.buffer.write(line) # pass bytes as is
        file.write(line)
58

你可以用管道来读取程序的标准输出(stdout)中的数据,然后把这些数据写到你想要的地方。

import sys
import subprocess

logfile = open('logfile', 'w')
proc=subprocess.Popen(['cat', 'file'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
for line in proc.stdout:
    sys.stdout.write(line)
    logfile.write(line)
proc.wait()

更新

在Python 3中,universal_newlines这个参数控制着管道的使用方式。如果设置为False,那么管道读取的数据会返回bytes对象,这时候你可能需要把它解码(比如用line.decode('utf-8'))才能得到字符串。如果设置为True,Python会自动帮你解码。

在3.3版本中有所变化:当universal_newlinesTrue时,这个类会使用编码locale.getpreferredencoding(False),而不是locale.getpreferredencoding()。想了解更多这个变化的信息,可以查看io.TextIOWrapper类。

撰写回答