要从我的Python脚本启动程序,我使用以下方法:
def execute(command):
process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
output = process.communicate()[0]
exitCode = process.returncode
if (exitCode == 0):
return output
else:
raise ProcessException(command, exitCode, output)
因此,当我启动像Process.execute("mvn clean install")
这样的进程时,我的程序会一直等到进程完成,然后才能获得程序的完整输出。如果我正在运行需要一段时间才能完成的过程,这很烦人。
我可以让我的程序逐行写入进程输出,通过在循环结束之前轮询进程输出或其他内容吗?
** [编辑]抱歉,在发布此问题之前我没有很好地搜索。线程实际上是关键。在这里找到一个例子,展示了如何做到这一点:** Python Subprocess.Popen from a thread
您可以在命令输出后立即使用iter处理行:lines = iter(fd.readline, "")
。这是一个显示典型用例的完整示例(感谢@jfs帮助):
from __future__ import print_function # Only Python 2.x
import subprocess
def execute(cmd):
popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True)
for stdout_line in iter(popen.stdout.readline, ""):
yield stdout_line
popen.stdout.close()
return_code = popen.wait()
if return_code:
raise subprocess.CalledProcessError(return_code, cmd)
# Example
for path in execute(["locate", "a"]):
print(path, end="")
这至少在Python3.4中起作用
import subprocess
process = subprocess.Popen(cmd_list, stdout=subprocess.PIPE)
for line in process.stdout:
print(line.decode().strip())
这里的答案都没有解决我的所有需求。
一点背景:我使用ThreadPoolExecutor来管理一个线程池,每个线程启动一个子进程并运行它们并发。 (在Python2.7中,但这应该适用于较新的3.x)。我不想仅仅为了输出收集而使用线程,因为我想尽可能多地使用其他东西(20个进程的池将使用40个线程来运行; 1个用于进程线程,1个用于stdout ...还有更多,如果你想要stderr我猜)
我正在剥离很多异常,所以这是基于生产中的代码。希望我没有在复制和粘贴中破坏它。此外,非常欢迎反馈!
import time
import fcntl
import subprocess
import time
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
# Make stdout non-blocking when using read/readline
proc_stdout = proc.stdout
fl = fcntl.fcntl(proc_stdout, fcntl.F_GETFL)
fcntl.fcntl(proc_stdout, fcntl.F_SETFL, fl | os.O_NONBLOCK)
def handle_stdout(proc_stream, my_buffer, echo_streams=True, log_file=None):
"""A little inline function to handle the stdout business. """
# fcntl makes readline non-blocking so it raises an IOError when empty
try:
for s in iter(proc_stream.readline, ''): # replace '' with b'' for Python 3
my_buffer.append(s)
if echo_streams:
sys.stdout.write(s)
if log_file:
log_file.write(s)
except IOError:
pass
# The main loop while subprocess is running
stdout_parts = []
while proc.poll() is None:
handle_stdout(proc_stdout, stdout_parts)
# ...Check for other things here...
# For example, check a multiprocessor.Value('b') to proc.kill()
time.sleep(0.01)
# Not sure if this is needed, but run it again just to be sure we got it all?
handle_stdout(proc_stdout, stdout_parts)
stdout_str = "".join(stdout_parts) # Just to demo
我确定这里会增加开销,但在我的情况下并不是一个问题。功能上它做我需要的。我唯一没有解决的是为什么这对于日志消息非常有效,但是我看到一些print
消息稍后出现并且一下子出现。
在Python 3.6中我使用了这个:
import subprocess
cmd = "command"
output = subprocess.call(cmd, shell=True)
print(process)
好吧,我设法解决它没有线程(任何建议为什么使用线程会更好被赞赏)通过使用此问题的片段Intercepting stdout of a subprocess while it is running
def execute(command):
process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
# Poll process for new output until finished
while True:
nextline = process.stdout.readline()
if nextline == '' and process.poll() is not None:
break
sys.stdout.write(nextline)
sys.stdout.flush()
output = process.communicate()[0]
exitCode = process.returncode
if (exitCode == 0):
return output
else:
raise ProcessException(command, exitCode, output)
一旦在Python 3中刷新stdout缓冲区,就逐行打印子进程'输出:
from subprocess import Popen, PIPE, CalledProcessError
with Popen(cmd, stdout=PIPE, bufsize=1, universal_newlines=True) as p:
for line in p.stdout:
print(line, end='') # process line here
if p.returncode != 0:
raise CalledProcessError(p.returncode, p.args)
注意:你不需要p.poll()
- 当达到eof时循环结束。并且您不需要iter(p.stdout.readline, '')
- 预读错误在Python 3中得到修复。
另见Python: read streaming input from subprocess.communicate()。
@tokland
尝试了你的代码并更正了3.4和windows dir.cmd是一个简单的dir命令,保存为cmd文件
import subprocess
c = "dir.cmd"
def execute(command):
popen = subprocess.Popen(command, stdout=subprocess.PIPE,bufsize=1)
lines_iterator = iter(popen.stdout.readline, b"")
while popen.poll() is None:
for line in lines_iterator:
nline = line.rstrip()
print(nline.decode("latin"), end = "\r\n",flush =True) # yield line
execute(c)
对于任何尝试回答这个问题的人从Python脚本获取stdout注意到Python缓冲了它的标准输出,因此可能需要一段时间才能看到标准输出。
这可以通过在目标脚本中的每个stdout写入后添加以下内容来纠正:
sys.stdout.flush()
在Python> = 3.5中使用subprocess.run
为我工作:
import subprocess
cmd = 'echo foo; sleep 1; echo foo; sleep 2; echo foo'
subprocess.run(cmd, shell=True)
(在执行期间获得输出也可以在没有shell=True
的情况下工作)https://docs.python.org/3/library/subprocess.html#subprocess.run
要回答原始问题,IMO最好的方法是将子进程stdout
直接重定向到程序的stdout
(可选,stderr
也可以这样做,如下例所示)
p = Popen(cmd, stdout=sys.stdout, stderr=sys.stderr)
p.communicate()
如果有人想要使用线程同时读取stdout
和stderr
,这就是我提出的:
import threading
import subprocess
import Queue
class AsyncLineReader(threading.Thread):
def __init__(self, fd, outputQueue):
threading.Thread.__init__(self)
assert isinstance(outputQueue, Queue.Queue)
assert callable(fd.readline)
self.fd = fd
self.outputQueue = outputQueue
def run(self):
map(self.outputQueue.put, iter(self.fd.readline, ''))
def eof(self):
return not self.is_alive() and self.outputQueue.empty()
@classmethod
def getForFd(cls, fd, start=True):
queue = Queue.Queue()
reader = cls(fd, queue)
if start:
reader.start()
return reader, queue
process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(stdoutReader, stdoutQueue) = AsyncLineReader.getForFd(process.stdout)
(stderrReader, stderrQueue) = AsyncLineReader.getForFd(process.stderr)
# Keep checking queues until there is no more output.
while not stdoutReader.eof() or not stderrReader.eof():
# Process all available lines from the stdout Queue.
while not stdoutQueue.empty():
line = stdoutQueue.get()
print 'Received stdout: ' + repr(line)
# Do stuff with stdout line.
# Process all available lines from the stderr Queue.
while not stderrQueue.empty():
line = stderrQueue.get()
print 'Received stderr: ' + repr(line)
# Do stuff with stderr line.
# Sleep for a short time to avoid excessive CPU use while waiting for data.
sleep(0.05)
print "Waiting for async readers to finish..."
stdoutReader.join()
stderrReader.join()
# Close subprocess' file descriptors.
process.stdout.close()
process.stderr.close()
print "Waiting for process to exit..."
returnCode = process.wait()
if returnCode != 0:
raise subprocess.CalledProcessError(returnCode, command)
我只是想分享这个,因为我最终在这个问题上尝试做类似的事情,但没有一个答案解决了我的问题。希望它可以帮助某人!
请注意,在我的用例中,外部进程会杀死我们Popen()
的进程。
此PoC不断读取进程的输出,并可在需要时访问。只保留最后一个结果,丢弃所有其他输出,从而防止PIPE内存不足:
import subprocess
import time
import threading
import Queue
class FlushPipe(object):
def __init__(self):
self.command = ['python', './print_date.py']
self.process = None
self.process_output = Queue.LifoQueue(0)
self.capture_output = threading.Thread(target=self.output_reader)
def output_reader(self):
for line in iter(self.process.stdout.readline, b''):
self.process_output.put_nowait(line)
def start_process(self):
self.process = subprocess.Popen(self.command,
stdout=subprocess.PIPE)
self.capture_output.start()
def get_output_for_processing(self):
line = self.process_output.get()
print ">>>" + line
if __name__ == "__main__":
flush_pipe = FlushPipe()
flush_pipe.start_process()
now = time.time()
while time.time() - now < 10:
flush_pipe.get_output_for_processing()
time.sleep(2.5)
flush_pipe.capture_output.join(timeout=0.001)
flush_pipe.process.kill()
print_达特.朋友
#!/usr/bin/env python
import time
if __name__ == "__main__":
while True:
print str(time.time())
time.sleep(0.01)
输出:你可以清楚地看到,只有~2.5s间隔的输出之间没有任何内容。
>>>1520535158.51
>>>1520535161.01
>>>1520535163.51
>>>1520535166.01