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

Ing*_*her 175 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)
Run Code Online (Sandbox Code Playgroud)

因此,当我启动一个类似的过程时Process.execute("mvn clean install"),我的程序会一直等到过程结束,然后我才能获得程序的完整输出.如果我正在运行需要一段时间才能完成的过程,这很烦人.

我可以让我的程序逐行写入进程输出,通过在循环结束之前轮询进程输出或其他内容吗?

**[编辑]抱歉,在发布此问题之前我没有很好地搜索.线程实际上是关键.在这里找到一个示例,说明如何执行此操作:** 来自线程的Python Subprocess.Popen

tok*_*and 236

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

  • 我已经尝试过这段代码(程序需要花费大量时间才能运行)并且可以确认它在收到行时输出行,而不是等待执行完成.这是优秀的答案imo. (24认同)
  • 注意:在Python 3中,您可以在popen.stdout中使用`for line:print(line.decode(),end ='')`.为了支持Python 2和3,使用bytes literal:`b''`否则`lines_iterator`永远不会在Python 3上结束. (9认同)
  • 它应该工作.要抛光它,你可以添加`bufsize = 1`(它可以提高Python 2的性能),显式关闭`popen.stdout`管道(不等待垃圾收集来处理它),并引发`subprocess. CalledProcessError`(如`check_call()`,`check_output()`do).在Python 2和3上,`print`语句是不同的:你可以使用softspace hack`print line,`(注意:逗号)来避免像你的代码那样加倍所有换行并在Python 3上传递`universal_newlines = True`,获取文本而不是字节 - [相关答案](http://stackoverflow.com/a/17698359/4279). (5认同)
  • @binzhang这不是错误,默认情况下,stdout在Python脚本上也是缓冲的(对许多Unix工具也是如此).尝试`execute(["python"," - u","child_thread.py"])`.更多信息:http://stackoverflow.com/questions/14258500/python-significance-of-u-option (5认同)
  • 这种方法的问题在于,如果进程暂停一段时间而不向stdout写入任何内容,则不再需要读取输入.您将需要一个循环来检查该过程是否已完成.我在python 2.7上使用subprocess32尝试了这个 (3认同)
  • 您还应该在构建 Popen 时设置 `stderr=subprocess.STDOUT` 以确保您不会错过任何错误消息 (2认同)
  • 我很困惑......如果进程发送大量数据而没有任何换行符,这是否仍然会受到管道上潜在阻塞的根本问题的困扰?也就是说,Python 文档建议使用 communicate() 而不是直接从管道读取的确切原因是什么? (2认同)

Ing*_*her 81

好吧,我设法解决它没有线程(任何建议为什么使用线程会更好被赞赏)通过使用此问题的片段在运行时拦截子进程的stdout

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)
Run Code Online (Sandbox Code Playgroud)

  • @DavidCharles我认为你正在寻找的是`stdout = subprocess.PIPE,stderr = subprocess.STDOUT`这捕获了stderr,我相信(但我没有测试过)它也捕获了stdin. (7认同)
  • 合并ifischer和tokland的代码效果很好(我必须将`print line,`更改为`sys.stdout.write(nextline); sys.stdout.flush()`.否则,它会打印出每两行.然后再次,这是使用IPython的Notebook接口,所以可能还有其他事情发生 - 无论如何,显式调用`flush()`都有效. (3认同)
  • 先生,你是我的生命保护者!非常奇怪,这种东西不是在图书馆本身内置的.因为如果我写cliapp,我想要显示所有在循环处理的东西.. s'rsly .. (3认同)
  • 是否可以修改此解决方案以不断打印*输出和错误?如果我将`stderr = subprocess.STDOUT`更改为`stderr = subprocess.PIPE`然后从循环中调用`process.stderr.readline()`,我似乎与在此处被警告的非常僵局相冲突`subprocess`模块的文档. (2认同)

jfs*_*jfs 59

一旦在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)
Run Code Online (Sandbox Code Playgroud)

注意:您不需要p.poll()- 当到达eof时,循环结束.并且您不需要iter(p.stdout.readline, '')- 预读错误在Python 3中得到修复.

另请参阅Python:从subprocess.communicate()读取流输入.

  • @Codename:如果您想使用“>”,则运行“python -u your-script.py > some-file”。注意:我上面提到的“-u”选项(不需要使用“sys.stdout.flush()”)。 (5认同)
  • 我必须添加sys.stdout.flush()以立即获得打印. (3认同)
  • @Codename:你不应该在父文件中使用`sys.stdout.flush()` - 如果没有重定向到文件/管道,stdout是行缓冲的,因此打印`line`会自动刷新缓冲区.您也不需要在子项中使用`sys.stdout.flush()` - 而是传递`-u`命令行选项. (3认同)
  • 这个解决方案对我有用.上面给出的解决方案就是为我打印空白行. (2认同)

ari*_*ing 19

当您只想print输出时,实际上有一种非常简单的方法可以做到这一点:

import subprocess
import sys

def execute(command):
    subprocess.check_call(command, stdout=sys.stdout, stderr=subprocess.STDOUT)
Run Code Online (Sandbox Code Playgroud)

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

  • 对于 Python 3.6,此解决方案比 @tokland 的解决方案更简单、更清晰。我注意到 shell=True 参数不是必需的。 (3认同)
  • 你能解释一下 sys.stdout 和 subprocess.STDOUT 之间有什么区别吗? (3认同)
  • 当然,@RonSerruya。[sys.stdout](https://docs.python.org/3/library/sys.html#sys.stdout) 是一个允许正常写入操作的 File 对象。[subprocess.STDOUT](https://docs.python.org/3/library/subprocess.html#subprocess.STDOUT) 是一个特殊值,显式用于将 stderr 重定向到与 stdout 相同的输出。从概念上讲,您是说您希望两个提要都到达同一位置,而不是两次传递相同的值。 (3认同)

小智 7

在Python> = 3.5中使用subprocess.run对我有效:

import subprocess

cmd = 'echo foo; sleep 1; echo foo; sleep 2; echo foo'
subprocess.run(cmd, shell=True)
Run Code Online (Sandbox Code Playgroud)

(在执行期间获取输出也可以在没有的情况下使用shell=Truehttps://docs.python.org/3/library/subprocess.html#subprocess.run

  • 这不是“执行期间”。`subprocess.run()` 调用仅在子进程完成运行时返回。 (2认同)

use*_*351 6

对于尝试回答这个问题以从 Python 脚本中获取标准输出的任何人,请注意 Python 缓冲其标准输出,因此可能需要一段时间才能看到标准输出。

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

sys.stdout.flush()
Run Code Online (Sandbox Code Playgroud)

  • @triplee 在几种情况下,将 Python 作为 Python 的子进程运行是合适的。我有许多 python 批处理脚本,我希望每天按顺序运行。这些可以由启动执行的主 Python 脚本进行编排,并在子脚本失败时通过电子邮件发送给我。每个脚本都与另一个脚本沙箱化 - 没有命名冲突。我不是并行化,所以多处理和线程不相关。 (3认同)

小智 5

@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)
Run Code Online (Sandbox Code Playgroud)

  • 你可以[简化代码](http://stackoverflow.com/a/28319191/4279).`iter()`和`end ='\ r \n'`是不必要的.默认情况下,Python使用通用换行模式,即在打印过程中任何''\n'`都会转换为''\ r \n'.`'latin'`可能是一个错误的编码,你可以使用`universal_newlines = True`来获取Python 3中的文本输出(使用locale的首选编码解码).不要停在`.poll()`上,可能会有缓存的未读数据.如果Python脚本在控制台中运行,那么它的输出是行缓冲的; 你可以使用`-u`选项强制进行行缓冲 - 这里你不需要`flush = True`. (3认同)

Wil*_*ill 5

如果有人想从两个读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)
Run Code Online (Sandbox Code Playgroud)

我只是想分享这个,因为我最终在这个问题上尝试做类似的事情,但没有一个答案解决了我的问题。希望它可以帮助某人!

请注意,在我的用例中,外部进程会杀死我们Popen().


All*_*leo 5

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

p = Popen(cmd, stdout=sys.stdout, stderr=sys.stderr)
p.communicate()
Run Code Online (Sandbox Code Playgroud)

  • 不为 `stdout` 指定任何内容,`stderr` 用更少的代码做同样的事情。虽然我认为*显式比隐式更好。* (4认同)

归档时间:

查看次数:

155982 次

最近记录:

6 年,2 月 前