Python `tee` 子进程标准输出
有没有办法在Python中实现类似于UNIX命令行中tee
的功能?我正在使用一种典型的分叉/执行模式,我希望子进程的标准输出(stdout)能够同时出现在日志文件和父进程的标准输出上,而且不需要任何缓冲。
比如在这段Python代码中,子进程的标准输出最终只出现在日志文件里,而没有出现在父进程的标准输出上。
pid = os.fork()
logFile = open(path,"w")
if pid == 0:
os.dup2(logFile.fileno(),1)
os.execv(cmd)
编辑:我不想使用subprocess模块。我正在处理一些复杂的子进程操作,需要手动调用fork
。
5 个回答
哦,你啊。在我看到你例子最后一行提到的execv()之前,我已经准备好了一个不错的答案。真是让人失望。最初的想法是把每个子进程的标准输出替换成这个博客文章中的tee类实例,然后把输出分成原来的标准输出和日志文件:
http://www.shallowsky.com/blog/programming/python-tee.html
但是,由于你使用了execv(),子进程中的tee实例会被覆盖,所以这个方法行不通。
很遗憾,我找不到一个现成的解决方案来解决你的问题。最接近的办法是启动一个实际的tee程序作为子进程;如果你想要更跨平台一点,可以用Python写一个简单的替代品。
首先,要知道在编写tee替代品时:tee其实是个很简单的程序。在我见过的所有真正的C语言实现中,它并没有比这个复杂多少:
while((character = read()) != EOF) {
/* Write to all of the output streams in here, then write to stdout. */
}
不幸的是,你不能简单地把两个流连接在一起。这会非常有用(这样一个流的输入就能自动转发到另一个流),但我们没有这样的便利,必须自己编写代码。所以,Eli和我的答案会非常相似。不同之处在于,我的答案中,Python的'tee'会通过管道在一个单独的进程中运行;这样,父线程仍然可以继续工作!
(记得也要复制博客文章中的tee类。)
import os, sys
# Open it for writing in binary mode.
logFile=open("bar", "bw")
# Verbose names, but I wanted to get the point across.
# These are file descriptors, i.e. integers.
parentSideOfPipe, childSideOfPipe = os.pipe()
# 'Tee' subprocess.
pid = os.fork()
if pid == 0:
while True:
char = os.read(parentSideOfPipe, 1)
logFile.write(char)
os.write(1, char)
# Actual command
pid = os.fork()
if pid == 0:
os.dup2(childSideOfPipe, 1)
os.execv(cmd)
如果这不是你想要的,我很抱歉,但这是我能找到的最佳解决方案。
祝你项目顺利!
下面的内容中,SOMEPATH
是指子程序的路径,这个路径的格式是适合用在 subprocess.Popen
里的(可以查看它的文档了解更多)。
import sys, subprocess
f = open('logfile.txt', 'w')
proc = subprocess.Popen(SOMEPATH, stdout=subprocess.PIPE)
while True:
out = proc.stdout.read(1)
if out == '' and proc.poll() != None:
break
if out != '':
# CR workaround since chars are read one by one, and Windows interprets
# both CR and LF as end of lines. Linux only has LF
if out != '\r': f.write(out)
sys.stdout.write(out)
sys.stdout.flush()
这里有一个不使用 subprocess
模块的可行方案。虽然你可以在使用 exec*
函数的同时,利用 subprocess
来处理 tee 进程(只需使用 stdin=subprocess.PIPE
,然后把描述符复制到你的 stdout)。
import os, time, sys
pr, pw = os.pipe()
pid = os.fork()
if pid == 0:
os.close(pw)
os.dup2(pr, sys.stdin.fileno())
os.close(pr)
os.execv('/usr/bin/tee', ['tee', 'log.txt'])
else:
os.close(pr)
os.dup2(pw, sys.stdout.fileno())
os.close(pw)
pid2 = os.fork()
if pid2 == 0:
# Replace with your custom process call
os.execv('/usr/bin/yes', ['yes'])
else:
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
pass
需要注意的是,tee
命令内部实际上做的和 Ben 在他的回答中提到的一样:读取输入,然后循环处理输出文件描述符并写入它们。它可能更高效,因为它的实现经过了优化,并且是用 C 语言编写的,但你会面临不同管道的开销(我不确定哪种方案更高效,但在我看来,把一个自定义的类文件对象重新分配给 stdout
是一个更优雅的解决方案)。
一些额外的资源: