如何使用Popen同时写入stdout和日志文件?
我正在使用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 个回答
逐字逐字地写入终端,适用于交互式应用
这种方法会立即将接收到的每一个字节写入标准输出,这样更能模拟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上进行了测试。
要模拟这个命令: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)
你可以用管道来读取程序的标准输出(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_newlines
为True
时,这个类会使用编码locale.getpreferredencoding(False)
,而不是locale.getpreferredencoding()
。想了解更多这个变化的信息,可以查看io.TextIOWrapper
类。