Constantly print Subprocess output while process is running
要从我的python脚本启动程序,我使用以下方法:
1 2 3 4 5 6 7 8 9 | 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) |
因此,当我启动一个类似于
我可以让我的程序一行一行地写进程输出吗?可以在进程输出在循环中完成之前对其进行轮询吗?
** [编辑]抱歉,我在发布这个问题之前没有很好地搜索。线程实际上是关键。在此处找到一个示例,演示如何执行此操作:**线程中的python subprocess.popen
当命令输出行时,可以使用ITER来处理它们:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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="") |
好吧,我通过使用这个问题中的一个片段,在子进程运行时截取stdout,成功地解决了没有线程的问题(感谢您对为什么使用线程更好的任何建议)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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缓冲区后,立即逐行打印其输出:
1 2 3 4 5 6 7 8 | 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) |
注意:您不需要
另请参见python:read streaming input from subprocess.communication()。
托克兰
尝试了您的代码,并针对3.4和Windows进行了更正dir.cmd是一个简单的dir命令,另存为cmd文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 =" ",flush =True) # yield line execute(c) |
对于任何试图从python脚本获取stdout这个问题的答案的人,请注意python缓冲其stdout,因此可能需要一段时间才能看到stdout。
这可以通过在目标脚本中的每个stdout写入之后添加以下内容来纠正:
1 | sys.stdout.flush() |
在python中>=3.5使用
1 2 3 4 | import subprocess cmd = 'echo foo; sleep 1; echo foo; sleep 2; echo foo' subprocess.run(cmd, shell=True) |
(在执行过程中获取输出也可以在没有
如果有人想在使用线程的同时读取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | 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) |
我只想和大家分享这个问题,因为我最终在这个问题上试图做一些类似的事情,但是没有一个答案解决了我的问题。希望它能帮助别人!
注意,在我的用例中,一个外部进程终止了我们所使用的cx1〔5〕进程。
这个POC不断地从一个进程中读取输出,并且可以在需要时访问。只保留最后一个结果,所有其他输出都将被丢弃,从而防止管道从内存中增长:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 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() |
PrimtTyDAT.Py
1 2 3 4 5 6 7 | #!/usr/bin/env python import time if __name__ =="__main__": while True: print str(time.time()) time.sleep(0.01) |
输出:您可以清楚地看到只有~2.5s间隔的输出,两者之间没有任何内容。
1 2 3 4 | >>>1520535158.51 >>>1520535161.01 >>>1520535163.51 >>>1520535166.01 |
这里的答案都不能满足我所有的需要。
一点背景知识:我使用一个线程池执行器来管理一个线程池,每个线程都启动一个子进程并并发地运行它们。(在python2.7中,但这也适用于更新的3.x)。我不想将线程仅用于输出收集,因为我希望尽可能多的线程可用于其他用途(20个进程的池将使用40个线程仅用于运行;1个用于进程线程,1个用于stdout…如果需要stderr,我猜还有更多)
我剥离了很多异常,所以这是基于在生产中工作的代码。希望我没有在复制和粘贴中破坏它。另外,非常欢迎反馈!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | 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 |
我相信这里有额外的开销,但在我的情况下这不是一个问题。在功能上,它可以满足我的需要。我唯一没有解决的问题是为什么这对日志消息非常有效,但是我看到一些
为了回答最初的问题,IMO最好的方法就是将子进程
1 2 | p = Popen(cmd, stdout=sys.stdout, stderr=sys.stderr) p.communicate() |
这至少在python3.4中有效。
1 2 3 4 5 | import subprocess process = subprocess.Popen(cmd_list, stdout=subprocess.PIPE) for line in process.stdout: print(line.decode().strip()) |
在Python3.6中,我使用了:
1 2 3 4 5 | import subprocess cmd ="command" output = subprocess.call(cmd, shell=True) print(process) |