小能豆

使用子进程获取实时输出

javascript

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

我认为我只需使用subprocess.Popen执行程序stdout=PIPE,然后读取每一行并相应地执行操作即可。但是,当我运行以下代码时,输出似乎被缓冲在某处,导致它出现在两个块中,即第 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', '')

在查看了有关子进程的文档之后,我发现了参数bufsizePopen因此我尝试将 bufsize 设置为 1(缓冲每行)和 0(无缓冲),但这两个值似乎都没有改变行传递的方式。

这时我开始抓住救命稻草,因此我写了以下输出循环:

while True:
    try:
        print p.stdout.next().replace('\n', '')
    except StopIteration:
        break

但结果是一样的。

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


阅读 34

收藏
2024-07-22

共1个答案

小能豆

为了在Python中获取命令行程序(如svnadmin verify)的实时输出,可以尝试以下几种方法来确保标准输出没有被缓冲。

方法1:通过Python subprocess模块并使用 select 模块

使用 select 模块来实时读取子进程的输出:

import subprocess
import select

cmd = 'svnadmin verify /var/svn/repos/config'
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True, bufsize=1, universal_newlines=True)

while True:
    reads = [p.stdout.fileno()]
    ret = select.select(reads, [], [])

    if p.stdout.fileno() in ret[0]:
        output = p.stdout.readline()
        if output:
            print(output.strip())
        else:
            break

p.stdout.close()
p.wait()

方法2:使用 subprocess 模块并手动刷新输出缓冲区

将输出手动刷新以确保实时输出:

import subprocess
import sys

cmd = 'svnadmin verify /var/svn/repos/config'
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True, universal_newlines=True)

while True:
    line = p.stdout.readline()
    if line:
        print(line.strip())
        sys.stdout.flush()
    else:
        break

p.stdout.close()
p.wait()

方法3:设置子进程的环境变量

有时候子进程会自己做缓冲,特别是当标准输出不是一个终端时。可以通过设置环境变量来禁用这种缓冲行为。

import os
import subprocess

cmd = 'svnadmin verify /var/svn/repos/config'
env = os.environ.copy()
env['PYTHONUNBUFFERED'] = '1'  # 对于Python子进程
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True, env=env, universal_newlines=True)

while True:
    line = p.stdout.readline()
    if line:
        print(line.strip())
    else:
        break

p.stdout.close()
p.wait()

总结

通过上述方法,可以实时获取子进程的输出。select 模块方法更加通用,可以处理不同类型的子进程输出。手动刷新输出缓冲区可以确保及时显示。设置环境变量可以防止子进程自己做缓冲。根据需要选择合适的方法,以便实现实时输出和进度指示。

2024-07-22