Pergunta

Eu estou tentando escrever um script para um programa de linha de comando (svnadmin verificar) que irá exibir um indicador de progresso agradável para a operação. Isso exige que eu seja capaz de ver cada linha de saída do programa envolveu assim que ele é emitido.

Eu percebi que eu tinha acabado de executar o programa usando subprocess.Popen, uso stdout=PIPE, em seguida, ler cada linha como ele entrou e agir sobre ele em conformidade. No entanto, quando eu corri o seguinte código, a saída parecia ser tamponado em algum lugar, fazendo-a aparecer em dois pedaços, linhas de 1 a 332, em seguida, 333 através de 439 (a última linha de saída)

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', '')

Depois de olhar para a documentação sobre subprocess um pouco, descobri o parâmetro bufsize para Popen, então eu tentei definindo bufsize a 1 (tampão cada linha) e 0 (sem buffer), mas nenhum valor parecia mudar a forma como as linhas estavam sendo entregues.

Neste ponto, eu estava começando a entender para palhas, então eu escrevi o seguinte loop de saída:

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

mas obteve o mesmo resultado.

É possível obter 'em tempo real' saída do programa de um programa executado usando subprocesso? Existe alguma outra opção no Python que é frente-compatível (não exec*)?

Foi útil?

Solução

Eu tentei isso, e por algum motivo enquanto o código

for line in p.stdout:
  ...

buffers de forma agressiva, a variante

while True:
  line = p.stdout.readline()
  if not line: break
  ...

não. Aparentemente este é um bug conhecido: http://bugs.python.org/issue3907 (A questão é agora "Fechado" a partir de 29 de agosto de 2018)

Outras dicas

p = subprocess.Popen(cmd, stdout=subprocess.PIPE, bufsize=1)
for line in iter(p.stdout.readline, b''):
    print line,
p.stdout.close()
p.wait()

Você pode tentar o seguinte:

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

Se você usa readline em vez de ler, haverá alguns casos em que a mensagem de entrada não é impresso. Experimente-o com um comando à requer uma entrada incorporada e veja por si mesmo.

Você pode direcionar a saída subprocess aos fluxos diretamente. Exemplo simplificado:

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

Eu corri para o mesmo problema algum tempo atrás. Minha solução foi a abandonar a iteração para o método read, que retornará imediatamente, mesmo se o seu subprocesso não está terminado a execução, etc.

Tempo real Issue saída resolvido: Eu fiz encontrou problema semelhante no Python, ao capturar a saída em tempo real a partir de programa c. Eu adicionei " fflush (stdout) "; no meu código C. Ela trabalhou para mim. Aqui está a cortar o código

<< C Programa >>

#include <stdio.h>
void main()
{
    int count = 1;
    while (1)
    {
        printf(" Count  %d\n", count++);
        fflush(stdout);
        sleep(1);
    }
}

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

<< >> SAÍDA Imprimir: Contagem 1 Imprimir: Contagem 2 Imprimir: Conde 3

Hope isso ajuda.

~ sairam

Você pode usar um iterador sobre cada byte na saída do subprocesso. Isso permite atualização em linha (linhas terminando com '\ r' sobrescrever linha de saída anterior) do subprocesso:

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.")

Dependendo do caso de uso, você pode também deseja desativar o buffer no próprio subprocess.

Se o subprocesso será um processo Python, você poderia fazer isso antes da chamada:

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

Ou, alternativamente passar esta no argumento env para Popen.

Caso contrário, se você estiver em Linux / Unix, você pode usar a ferramenta stdbuf. Por exemplo. como:

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

Veja também aqui sobre stdbuf ou outras opções.

(Veja também aqui para a mesma resposta.)

O Transmissão subprocess stdin e stdout com asyncio em Python post no blog de espectáculos Kevin McCarthy como fazê -lo com 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())

Usando pexpect [ http://www.noah.org/wiki/Pexpect ] com non-blocking readlines irá resolver este problema. Ela decorre do fato de que os tubos são tamponados, e assim a saída do seu aplicativo está sendo tamponado pelo tubo, portanto, você não pode chegar a esse saída até que os preenchimentos de buffer ou as matrizes de processo.

Eu usei esta solução para obter uma saída em tempo real em um subprocesso. Este ciclo irá parar logo que os concluída processo deixando de fora a necessidade de uma instrução break ou possível loop infinito.

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

Encontrado esta função "plug-and-play" aqui . Trabalhou como um encanto!

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)

Solução completa:

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

Este é o esqueleto básico que eu sempre uso para isso. Isso torna mais fácil de implementar limites de tempo e é capaz de lidar com os processos de suspensão inevitáveis.

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

(Esta solução foi testada com Python 2.7.15)
Você só precisa sys.stdout.flush () após cada linha de leitura / gravação:

while proc.poll() is None:
    line = proc.stdout.readline()
    sys.stdout.write(line)
    # or print(line.strip()), you still need to force the flush.
    sys.stdout.flush()
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top