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