Obtendo a saída em tempo real usando subprocesso
-
03-07-2019 - |
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*
)?
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()