我希望在不调用tee
的情况下,在python子集中有类似cmd > >(tee -a {{ out.log }}) 2> >(tee -a {{ err.log }} >&2)
的效果。基本上,将stdout同时写入stdout和out.log文件,并将stderr同时写入stderr和err.log文件。我知道我可以使用循环来处理它。但由于我的代码中已经有很多Popen、subprocess.run调用,我不想重写整件事
subprocess.run(["ls", "-l"], stdout=some_magic_file_object(sys.stdout, 'out.log'), stderr=some_magic_file_object(sys.stderr, 'out.log') )
据我所知,没有简单的方法,但这里有一种方法:
import os
class Tee:
def __init__(self, *files, bufsize=1):
files = [x.fileno() if hasattr(x, 'fileno') else x for x in files]
read_fd, write_fd = os.pipe()
pid = os.fork()
if pid:
os.close(read_fd)
self._fileno = write_fd
self.child_pid = pid
return
os.close(write_fd)
while buf := os.read(read_fd, bufsize):
for f in files:
os.write(f, buf)
os._exit(0)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
def fileno(self):
return self._fileno
def close(self):
os.close(self._fileno)
os.waitpid(self.child_pid, 0)
此Tee
对象采用文件对象列表(即,对象要么是整数文件描述符,要么具有fileno
方法)。它创建了一个子进程,该进程从自己的fileno
(subprocess.run
将向其写入)中读取内容,并将该内容写入到它提供的所有文件中。
需要一些生命周期管理,因为它的文件描述符必须关闭,然后必须等待子进程。为此,您必须手动管理它,方法是调用Tee
对象的close
方法,或者将其用作上下文管理器,如下所示。
用法:
import subprocess
import sys
logfile = open('out.log', 'w')
stdout_magic_file_object = Tee(sys.stdout, logfile)
stderr_magic_file_object = Tee(sys.stderr, logfile)
# Use the file objects with as many subprocess calls as you'd like here
subprocess.run(["ls", "-l"], stdout=stdout_magic_file_object, stderr=stderr_magic_file_object)
# Close the files after you're done with them.
stdout_magic_file_object.close()
stderr_magic_file_object.close()
logfile.close()
更干净的方法是使用上下文管理器,如下所示。不过,这需要更多的重构,所以您可能更喜欢手动关闭文件。
import subprocess
import sys
with open('out.log', 'w') as logfile:
with Tee(sys.stdout, logfile) as stdout, Tee(sys.stderr, logfile) as stderr:
subprocess.run(["ls", "-l"], stdout=stdout, stderr=stderr)
这种方法的一个问题是,子进程会立即写入stdout,因此Python自己的输出经常会混淆在其中。您可以通过在临时文件和日志文件上使用Tee
,然后在Tee
上下文块退出后打印临时文件的内容(并删除它)来解决此问题。创建一个Tee
的子类来自动完成这项工作很简单,但使用它会有点麻烦,因为现在你需要退出上下文块(或者让它运行一些代码)来打印出子流程的输出。