我必须从Python调用脚本并收集其输出。所以
p = subprocess.Popen ("script", shell = False, stdin = subprocess.PIPE, stdout = subprocess.PIPE)
out_lines = p.communicate ("n".join (in_lines)) [0]
。除了我想记录每个out_line
,以防万一最坏的情况发生(在子进程或主进程中)。
我有
- 无法控制
script
- 不想在我的 Python 中复制和修补
communicate()
源代码 - 不保证脚本为每个输入行返回一个输出行。
- 最好避免调用依赖于平台的 TEE 实用程序。
除了这四个可行但不方便的解决方案,还有什么我忽略的吗?也许是用日志记录包装器替换stdout = PIPE
之类的东西?
谢谢。我整个星期都会在这里。
你基本上有两个重叠的控制线程。
- 将输入发送到子流程。
- 在子流程可用时读取数据。
以独立于平台的方式执行此操作除了使用线程(或可能是选择循环)之外,不会为您提供太多选项。
您的相关代码似乎只对 stdout 感兴趣,因此您可以调用一个线程来读取 stdout 并将内容写入文件。
下面是一个示例:
import subprocess
import os
import threading
class LogThread(threading.Thread):
"""Thread which will read from `pipefd` and write all contents to
`fileobj` until `pipefd` is closed. Used as a context manager, this thread
will be automatically started, and joined on exit, usually when the
child process exits.
"""
def __init__(self, pipefd, fileobj):
self.pipefd = pipefd
self.fileobj = fileobj
super(LogThread, self).__init__()
self.setDaemon(1)
self.start()
def run(self):
while True:
line = self.pipefd.readline()
if not line:
break
self.fileobj.write(line)
self.fileobj.flush()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.join()
# Here's how to use the LogThread.
p = subprocess.Popen ("script", shell = False, stdin = subprocess.PIPE, stdout = subprocess.PIPE)
with open('logfile.txt', 'wt') as logfile:
with LogThread(p.stdout, logfile):
p.stdin.write("n".join(in_lines))
p.stdin.close()
这可能会复制Popen.communicate()
的一小部分,但它不是很多代码,并且与平台无关。
关于缓冲的说明:标准输出缓冲到非 tty 设备(例如管道)是正常的。 通常,stderr是没有缓冲的。 通常无法控制正在运行的应用程序是否缓冲其输出。 充其量,您可以猜测它如何确定是否使用缓冲,大多数应用程序调用isatty()
来确定它是否应该缓冲。 因此,在日志文件上设置缓冲 0 可能不是避免缓冲的正确解决方案。 如果缓冲为 0,则输出的每个字符都写入为单个write()
调用,效率非常低。 上述解决方案已修改为执行线路缓冲。
以下链接可能有用:https://unix.stackexchange.com/questions/25372/turn-off-buffering-in-pipe
subprocess.communicate
的操作依赖于平台检测。在 Windows 上,工作是使用线程完成的,只需使用文件包装器就足以用于日志记录目的。
然而,在Unix上,subprocess
使用select
,它依赖于获取文件描述符(file.fileno()
),所以这种技术不起作用。可以只创建另一个管道并在 python 中复制输出,但它涉及更多,并且由于您无论如何都在编写依赖于平台的代码,因此在 Unix 上,您通常可以使用 tee
命令来实现该目的。
知道了这一点,这里有一个满足你需求的平台相关示例:
import subprocess
import sys
class FileWrapperWithLog(object):
def __init__(self, file_object, filename):
self.f= file_object
self.log= open(filename, 'wb')
def read(self):
data= self.f.read()
self.log.write(data)
return data
def close(self):
return self.f.close()
FILENAME="my_file.log"
if sys.platform == "win32":
p= subprocess.Popen('dir', shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
p.stdout= FileWrapperWithLog( p.stdout, FILENAME )
else:
p= subprocess.Popen('ls | tee '+FILENAME, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
p.communicate()
另一种选择是猴子修补subprocess
,但这将是一个容易出错的过程,因为通信是一种复杂的方法,并且具有前面提到的依赖于平台的行为。
以下简单脚本说明了一种可以使用的方法(跨平台):
from subprocess import Popen, PIPE
import sys
import threading
def handle_line(line):
print(line) # or log it, or whatever
def reader(stream):
while True:
s = stream.readline()
if not s:
break
handle_line(s)
stream.close()
p = Popen(sys.argv[1].split(), stdout=PIPE, stderr=PIPE, stdin=PIPE)
# Get threads ready to read the subprocess output
out_reader = threading.Thread(target=reader, args=(p.stdout,))
err_reader = threading.Thread(target=reader, args=(p.stderr,))
out_reader.start()
err_reader.start()
# Provide the subprocess input
p.stdin.write("Hello, world!")
p.stdin.close()
# Wait for the child process to complete
p.wait()
# And for all its output to be consumed
out_reader.join()
err_reader.join()
print('Done.')
当使用回显其stdin
的程序运行时,例如cat
(或在Windows上,Gnu-Win32 cat.exe
),您应该得到:
Hello, world!
Done.
作为输出。这应该适用于更大的输出 - 我在 python-gnupg
中使用这种技术,我需要在行(从 stderr
)进入时处理它们,而不是在最后全部处理(这就是为什么我不能使用 communicate
)。
更新:有很多方法可以构建"OOP细节"-我并不特别觉得奥斯汀·菲利普斯的版本对我有用。但是,我已经展示了需要以最简单的方式采取的步骤,并且可以根据个人需求在此基础上进行构建。