在进程运行时不断打印Subprocess输出

时间:2010-12-11 16:03:30

标签: python subprocess

要从我的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

13 个答案:

答案 0 :(得分:211)

您可以在命令输出后立即使用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="")

答案 1 :(得分:77)

好的,我设法在没有线程的情况下解决它(通过使用此问题的片段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)

答案 2 :(得分:50)

打印子流程'一旦在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()

答案 3 :(得分:5)

@tokland

尝试了您的代码并针对3.4和Windows进行了更正 dir.cmd是一个简单的dir命令,保存为cmd-file

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)

答案 4 :(得分:3)

对于尝试回答此问题的人来说,从Python脚本获取stdout请注意Python缓冲其stdout,因此可能需要一段时间才能看到stdout。

这可以通过在目标脚本中的每个stdout写入后添加以下内容来纠正:

sys.stdout.flush()

答案 5 :(得分:3)

在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

答案 6 :(得分:2)

如果有人想要同时使用线程同时阅读stdoutstderr,这就是我想出来的:

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()

的流程

答案 7 :(得分:2)

当您只想打印输出时,实际上有一种非常简单的方法:

import subprocess
import sys

def execute(command):
    subprocess.check_call(command, shell=True, stdout=sys.stdout, stderr=subprocess.STDOUT)

在这里,我们只是将子流程指向我们自己的stdout,并使用现有的成功或异常api。

答案 8 :(得分:1)

此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_date.py

#!/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

答案 9 :(得分:1)

要回答最初的问题,IMO最好的方法是将子进程stdout直接重定向到程序的stdout(可选地,可以对stderr执行相同的操作,如下例所示) )

p = Popen(cmd, stdout=sys.stdout, stderr=sys.stderr)
p.communicate()

答案 10 :(得分:0)

这至少在Python3.4中有效

import subprocess

process = subprocess.Popen(cmd_list, stdout=subprocess.PIPE)
for line in process.stdout:
    print(line.decode().strip())

答案 11 :(得分:0)

这里没有一个答案可以满足我的所有需求。

  1. 没有用于stdout的线程(也没有队列等)
  2. 无阻塞,因为我需要检查其他情况
  3. 根据需要使用PIPE做很多事情,例如流输出,写入日志文件并返回输出的字符串副本。

一些背景知识:我正在使用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消息稍后出现,并且一次全部出现。

答案 12 :(得分:-2)

在Python 3.6中,我使用了以下方法:

import subprocess

cmd = "command"
output = subprocess.call(cmd, shell=True)
print(process)