如何使用 Popen 同时写入标准输出和日志文件?
Posted
技术标签:
【中文标题】如何使用 Popen 同时写入标准输出和日志文件?【英文标题】:How to write to stdout AND to log file simultaneously with Popen? 【发布时间】:2013-03-10 05:42:12 【问题描述】:我正在使用 Popen 调用一个不断将其标准输出和标准错误写入日志文件的 shell 脚本。有没有办法同时连续输出日志文件(到屏幕上),或者让shell脚本同时写入日志文件和stdout?
我基本上想在 Python 中做这样的事情:
cat file 2>&1 | tee -a logfile #"cat file" will be replaced with some script
同样,这会将 stderr/stdout 一起传送到 tee,然后将其写入 stdout 和我的日志文件。
我知道如何在 Python 中将 stdout 和 stderr 写入日志文件。我被困的地方是如何将这些复制回屏幕:
subprocess.Popen("cat file", shell=True, stdout=logfile, stderr=logfile)
当然,我可以做这样的事情,但是有没有办法在没有 tee 和 shell 文件描述符重定向的情况下做到这一点?:
subprocess.Popen("cat file 2>&1 | tee -a logfile", shell=True)
【问题讨论】:
相关:Python subprocess get children's output to file and terminal? 【参考方案1】:您可以使用管道从程序的标准输出中读取数据并将其写入您想要的所有位置:
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 类。
【讨论】:
您还可以创建一个类似对象的文件来封装此功能,然后在调用Popen
时使用它来代替stdout
/stderr
。
@sr2222 - 我也喜欢这个想法......除了现在我想起来......,它们是操作系统管道,而不是 python 对象,那么它甚至可以工作吗?
@imagineerThis - 代码读取标准输出,直到它关闭,然后等待程序退出。你在等待之前阅读,这样你就不会冒着管道填满和挂起程序的风险。您在读取后等待最终程序退出并返回代码。如果你不等待,你会得到一个僵尸进程(至少在 linux 上)。
您可能需要iter(proc.stdout.readline, '')
(由于预读缓冲区的错误)并在子进程刷新行后立即将bufsize=1
添加到打印行。致电 proc.stdout.close()
以避免 fd 泄漏。
@tdelaney:不,它不是固定的。试试脚本:import time; print(1); time.sleep(1); print(2)
。在脚本退出之前,您的版本不会打印 1
。我的评论中的flush
一词指的是您无法直接控制的子进程 中的缓冲区。如果孩子没有刷新其标准输出,那么输出将被延迟。可以使用pexpect
, pty
modules 或stdbuf
, unbuffer
, script
commands 修复它。【参考方案2】:
在不调用tee
命令的情况下模拟:subprocess.call("command 2>&1 | tee -a logfile", shell=True)
:
#!/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: read streaming input from 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)
【讨论】:
你应该提一下,完成后你可以在p.returncode中找到返回码。 @kdubs:与问题无关。为什么你认为我“应该提到”它? 虽然我同意他没有要求,但似乎应该检查退货状态。我希望在这里找到它。似乎会使答案完整。也许“应该”很强烈。 @kdubs 我同意检查退出状态是个好主意(这就是为什么存在subprocess.check_call()
、subprocess.check_output()
函数可以为您执行此操作)。我本可以添加if p.wait() != 0: raise subprocess.CalledProcessError(p.returncode, "command")
,但它会分散重点:如何在 Python 中模拟tee
实用程序。
Python 3 以上版本:执行后在屏幕上打印不实时【参考方案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
睡眠.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
另外,如果您希望输出立即显示在 ouptut 文件中,那么您还可以添加:
logfile.flush()
但是tee
不这样做,我担心它会影响性能。您可以通过以下方式轻松测试:
tail -f logfile.txt
相关问题:live output from subprocess command
在 Ubuntu 18.04、Python 3.6.7 上测试。
【讨论】:
以上是关于如何使用 Popen 同时写入标准输出和日志文件?的主要内容,如果未能解决你的问题,请参考以下文章
将标准输出从 subprocess.Popen 保存到文件,并将更多内容写入文件
subprocess.Popen communication()写入控制台,但不写入日志文件
python popen.communicate() 与多个标准输入写入