python实时获取子进程输出_python - 使用子进程获取实时输出

本文讨论了在Python中使用`subprocess`模块实时获取子进程输出的问题。作者遇到输出被缓冲,导致无法即时显示的情况。通过尝试不同的方法,如设置`bufsize`、使用`iter()`和`readline()`,最终找到可行的解决方案,包括使用`Popen`与`stdout.readline()`结合,或者利用`subprocess.PIPE`和`sys.stdout.write()`进行实时输出。文章列举了多个示例代码供读者参考。
摘要由CSDN通过智能技术生成

python - 使用子进程获取实时输出

我正在尝试为命令行程序(svnadmin verify)编写一个包装器脚本,它将为操作显示一个很好的进度指示器。 这要求我能够在输出后立即查看包装程序的每一行输出。

我想我只是使用exec*执行程序,使用Popen,然后在进入时读取每一行并相应地对其进行操作。 但是,当我运行以下代码时,输出似乎在某处缓冲,导致它出现在两个块中,第1行到第332行,然后是333到439(输出的最后一行)

from subprocess import Popen, PIPE, STDOUT

p = Popen('svnadmin verify /var/svn/repos/config', stdout = PIPE,

stderr = STDOUT, shell = True)

for line in p.stdout:

print line.replace('\n', '')

稍微查看子进程的文档后,我发现exec*参数为Popen,所以我尝试将bufsize设置为1(缓冲每行)和0(无缓冲区),但这两个值似乎都没有改变行的传递方式。

此时我开始掌握吸管,所以我编写了以下输出循环:

while True:

try:

print p.stdout.next().replace('\n', '')

except StopIteration:

break

但得到了相同的结果。

是否有可能获得使用子进程执行的程序的“实时”程序输出? Python中是否有一些其他选项是向前兼容的(不是exec*)?

14个解决方案

72 votes

我尝试了这个,并且由于某种原因而在代码中

for line in p.stdout:

...

缓慢地缓冲,变种

while True:

line = p.stdout.readline()

if not line: break

...

才不是。 显然这是一个已知的错误:[http://bugs.python.org/issue3907](该问题现已截至2018年8月29日“已关闭”)

Dave answered 2019-07-12T11:02:22Z

34 votes

p = subprocess.Popen(cmd, stdout=subprocess.PIPE, bufsize=1)

for line in iter(p.stdout.readline, b''):

print line,

p.stdout.close()

p.wait()

Corey Goldberg answered 2019-07-12T11:02:39Z

18 votes

你可以试试这个:

import subprocess

import sys

process = subprocess.Popen(

cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE

)

while True:

out = process.stdout.read(1)

if out == '' and process.poll() != None:

break

if out != '':

sys.stdout.write(out)

sys.stdout.flush()

如果使用readline而不是read,则会出现一些未打印输入消息的情况。 尝试使用命令需要内联输入并亲自查看。

Nadia Alramli answered 2019-07-12T11:03:06Z

14 votes

您可以直接将子进程输出定向到流。 简化示例:

subprocess.run(['ls'], stderr=sys.stderr, stdout=sys.stdout)

Aidan Feldman answered 2019-07-12T11:03:30Z

3 votes

我曾经遇到过同样的问题。 我的解决方案是迭代迭代read方法,即使你的子进程没有完成执行等,它也将立即返回。

Eli Courtwright answered 2019-07-12T11:03:56Z

2 votes

实时输出问题已解决:我在Python中遇到过类似的问题,同时从c程序中捕获实时输出。 我添加了“fflush(stdout);” 在我的C代码中。 它对我有用。 这是剪辑代码

<< C程序>>

#include

void main()

{

int count = 1;

while (1)

{

printf(" Count %d\n", count++);

fflush(stdout);

sleep(1);

}

}

<< Python程序>>

#!/usr/bin/python

import os, sys

import subprocess

procExe = subprocess.Popen(".//count", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)

while procExe.poll() is None:

line = procExe.stdout.readline()

print("Print:" + line)

<<OUTPUT>>打印:计数1打印:计数2打印:计数3

希望能帮助到你。

〜sairam

sairam answered 2019-07-12T11:04:52Z

2 votes

您可以在子进程的输出中的每个字节上使用迭代器。 这允许从子进程进行内联更新(以'\ r'结尾的行覆盖前一个输出行):

from subprocess import PIPE, Popen

command = ["my_command", "-my_arg"]

# Open pipe to subprocess

subprocess = Popen(command, stdout=PIPE, stderr=PIPE)

# read each byte of subprocess

while subprocess.poll() is None:

for c in iter(lambda: subprocess.stdout.read(1) if subprocess.poll() is None else {}, b''):

c = c.decode('ascii')

sys.stdout.write(c)

sys.stdout.flush()

if subprocess.returncode != 0:

raise Exception("The subprocess did not terminate correctly.")

rhyno183 answered 2019-07-12T11:05:17Z

1 votes

将pexpect [[http://www.noah.org/wiki/Pexpect]]与非阻塞读取线一起使用将解决此问题。 它源于管道缓冲的事实,因此您的应用程序的输出被管道缓冲,因此在缓冲区填充或进程终止之前,您无法获得该输出。

Gabe answered 2019-07-12T11:05:41Z

1 votes

我用这个解决方案在子进程上获得实时输出。 一旦进程完成,该循环将停止,从而不需要break语句或可能的无限循环。

sub_process = subprocess.Popen(my_command, close_fds=True, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

while sub_process.poll() is None:

out = sub_process.stdout.read(1)

sys.stdout.write(out)

sys.stdout.flush()

Jason Hedlund answered 2019-07-12T11:06:07Z

1 votes

根据用例,您可能还希望在子进程本身中禁用缓冲。

如果子进程是Python进程,您可以在调用之前执行此操作:

os.environ["PYTHONUNBUFFERED"] = "1"

或者在stdbuf参数中将其传递给Popen。

否则,如果您使用的是Linux / Unix,则可以使用stdbuf工具。 例如。 喜欢:

cmd = ["stdbuf", "-oL"] + cmd

另请参见此处有关stdbuf或其他选项。

(另见这里的答案。)

Albert answered 2019-07-12T11:07:06Z

0 votes

完整解决方案

import contextlib

import subprocess

# Unix, Windows and old Macintosh end-of-line

newlines = ['\n', '\r\n', '\r']

def unbuffered(proc, stream='stdout'):

stream = getattr(proc, stream)

with contextlib.closing(stream):

while True:

out = []

last = stream.read(1)

# Don't loop forever

if last == '' and proc.poll() is not None:

break

while last not in newlines:

# Don't loop forever

if last == '' and proc.poll() is not None:

break

out.append(last)

last = stream.read(1)

out = ''.join(out)

yield out

def example():

cmd = ['ls', '-l', '/']

proc = subprocess.Popen(

cmd,

stdout=subprocess.PIPE,

stderr=subprocess.STDOUT,

# Make all end-of-lines '\n'

universal_newlines=True,

)

for line in unbuffered(proc):

print line

example()

Andres Restrepo answered 2019-07-12T11:07:26Z

0 votes

在这里找到了这种“即插即用”功能。 像魅力一样工作!

import subprocess

def myrun(cmd):

"""from http://blog.kagesenshi.org/2008/02/teeing-python-subprocesspopen-output.html

"""

p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

stdout = []

while True:

line = p.stdout.readline()

stdout.append(line)

print line,

if line == '' and p.poll() != None:

break

return ''.join(stdout)

Deena answered 2019-07-12T11:07:51Z

0 votes

这是我一直用来做的基本骨架。 它可以轻松实现超时,并能够处理不可避免的挂起过程。

import subprocess

import threading

import Queue

def t_read_stdout(process, queue):

"""Read from stdout"""

for output in iter(process.stdout.readline, b''):

queue.put(output)

return

process = subprocess.Popen(['dir'],

stdout=subprocess.PIPE,

stderr=subprocess.STDOUT,

bufsize=1,

cwd='C:\\',

shell=True)

queue = Queue.Queue()

t_stdout = threading.Thread(target=t_read_stdout, args=(process, queue))

t_stdout.daemon = True

t_stdout.start()

while process.poll() is None or not queue.empty():

try:

output = queue.get(timeout=.5)

except Queue.Empty:

continue

if not output:

continue

print(output),

t_stdout.join()

Badslacks answered 2019-07-12T11:08:18Z

0 votes

由Kevin McCarthy撰写的Python博客文章中的Streaming子进程stdin和带有asyncio的stdout显示了如何使用asyncio:

import asyncio

from asyncio.subprocess import PIPE

from asyncio import create_subprocess_exec

async def _read_stream(stream, callback):

while True:

line = await stream.readline()

if line:

callback(line)

else:

break

async def run(command):

process = await create_subprocess_exec(

*command, stdout=PIPE, stderr=PIPE

)

await asyncio.wait(

[

_read_stream(

process.stdout,

lambda x: print(

"STDOUT: {}".format(x.decode("UTF8"))

),

),

_read_stream(

process.stderr,

lambda x: print(

"STDERR: {}".format(x.decode("UTF8"))

),

),

]

)

await process.wait()

async def main():

await run("docker build -t my-docker-image:latest .")

if __name__ == "__main__":

loop = asyncio.get_event_loop()

loop.run_until_complete(main())

Pablo answered 2019-07-12T11:08:43Z

如果子进程输出是一个结构体(比如JSON、XML等格式),可以使用标准库中的 `json` 或 `xml` 模块来解析输出。具体来说,可以使用管道对象的 `read` 方法读取子进程输出,并将其传递给 `json.loads` 或 `xml.etree.ElementTree.fromstring` 函数进行解析。下面是一个使用 `json` 模块的示例代码: ```python import subprocess import json # 启动子进程,并将标准输出重定向到一个管道中 p = subprocess.Popen(['python', 'script.py'], stdout=subprocess.PIPE) # 读取子进程输出,并解析为JSON格式 output = p.stdout.read() data = json.loads(output) # 打印输出结果中的某个字段 print(data['field']) ``` 在上面的代码中,我们使用 `subprocess.Popen` 函数启动了一个 `script.py` 脚本的子进程,并将其标准输出重定向到一个管道中。然后,我们使用管道对象的 `read` 方法来读取子进程输出,并使用 `json.loads` 函数将其解析为一个 Python 字典。最后,我们打印输出结果中的某个字段。 如果子进程输出是 XML 格式,可以使用 `xml.etree.ElementTree.fromstring` 函数来解析。下面是一个示例代码: ```python import subprocess import xml.etree.ElementTree as ET # 启动子进程,并将标准输出重定向到一个管道中 p = subprocess.Popen(['python', 'script.py'], stdout=subprocess.PIPE) # 读取子进程输出,并解析为XML格式 output = p.stdout.read() root = ET.fromstring(output) # 打印输出结果中的某个元素 print(root.find('tag').text) ``` 在上面的代码中,我们使用 `subprocess.Popen` 函数启动了一个 `script.py` 脚本的子进程,并将其标准输出重定向到一个管道中。然后,我们使用管道对象的 `read` 方法来读取子进程输出,并使用 `xml.etree.ElementTree.fromstring` 函数将其解析为一个 Element 对象。最后,我们使用 `find` 方法找到输出结果中的某个元素,并打印其文本内容。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值