2009-04-29 9 views
89

Sto provando a scrivere uno script wrapper per un programma della riga di comando (svnadmin verify) che visualizzerà un indicatore di progresso piacevole per l'operazione. Ciò richiede che sia in grado di vedere ogni riga di output dal programma avvolto non appena viene emesso.Ottenere l'output in tempo reale utilizzando il sottoprocesso

Ho pensato che avrei appena eseguito il programma utilizzando subprocess.Popen, utilizzare stdout=PIPE, quindi leggere ogni riga come è entrata e agire di conseguenza. Tuttavia, quando ho eseguito il seguente codice, l'uscita sembrava essere tamponato da qualche parte, facendolo apparire in due pezzi, righe da 1 a 332, poi 333 per 439 (l'ultima riga di output)

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

Dopo aver guardato alla documentazione relativa al sottoprocesso un po ', ho scoperto il parametro bufsize su Popen, quindi ho provato a impostare bufsize su 1 (buffer su ciascuna riga) e su 0 (nessun buffer), ma nessuno dei due valori sembrava cambiare il modo in cui venivano consegnate le linee.

A questo punto stavo iniziando a cogliere per cannucce, così ho scritto il seguente ciclo di uscita:

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

ma ho ottenuto lo stesso risultato.

È possibile ottenere l'output del programma "in tempo reale" di un programma eseguito utilizzando il sottoprocesso? C'è qualche altra opzione in Python compatibile con il futuro (non exec*)?

+0

Hai provato a omettere 'sydout = PIPE' in modo che il sottoprocesso scrive direttamente nella tua console, ignorando il processo padre? –

+3

Il fatto è che voglio leggere l'output. Se viene emesso direttamente sulla console, come potrei farlo? Inoltre, non voglio che l'utente veda l'output del programma avvolto, solo il mio output. –

+0

Allora perché un display "in tempo reale"? Non capisco il caso d'uso. –

risposta

62

Ho provato questo, e per qualche ragione, mentre il codice

for line in p.stdout: 
    ... 

buffer in modo aggressivo, la variante

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

no. Apparentemente si tratta di un bug noto: http://bugs.python.org/issue3907

+0

Questo non è il solo pasticcio nelle vecchie implementazioni IO Python. Questo è il motivo per cui Py2.6 e Py3k hanno finito con una libreria IO completamente nuova. –

+3

Questo codice verrà interrotto se il sottoprocesso restituisce una riga vuota. Una soluzione migliore sarebbe quella di usare 'while p.poll() è None' invece di' while True', e rimuovere 'if not line' – exhuma

+17

Ancora meglio, usare' for line in iter (p.stdout.readline, " "):' –

16

si può provare questo:

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 si usa readline invece di leggere, ci saranno alcuni casi in cui il messaggio di input non viene stampato. Provalo con un comando richiede un input inline e guarda tu stesso.

+0

Sì, l'uso di readline() interromperà la stampa (anche con chiamata sys.stdout.flush()) –

+2

Si suppone che questo si blocchi per un tempo indefinito? Vorrei che una determinata soluzione includesse anche il codice boilerplate per la modifica del ciclo quando viene eseguito il sottoprocesso iniziale. Mi dispiace, non importa quante volte ci guardo dentro, il sottoprocesso eccetera è qualcosa che non riesco proprio a far funzionare. – ThorSummoner

+1

Perché testare per '' quando in Python possiamo usare solo se non lo sono? –

3

Mi sono imbattuto nello stesso problema da un po 'di tempo fa. La mia soluzione era quella di abbandonare l'iterazione per il metodo read, che restituirà immediatamente anche se il sottoprocesso non è terminato l'esecuzione, ecc

1

L'utilizzo di pexpect [http://www.noah.org/wiki/Pexpect] con righe non bloccanti risolverà questo problema. Deriva dal fatto che le pipe sono bufferizzate, e quindi l'output della tua app viene bufferizzato dalla pipe, quindi non puoi arrivare a quell'output fino a quando il buffer non si riempie o il processo non muore.

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

grazie amico questo funziona bene per me. – zulucoda

+4

Downvoted per nessuna spiegazione. – nbro

+1

@nbro probabilmente perché 'p.stdout.close()' non è chiaro. –

0

Soluzione 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() 
+1

Dato che stai usando 'universal_newlines = True' sulla chiamata' Popen() ', probabilmente non hai bisogno di metterli da parte anche tu - questo è il punto principale dell'opzione. – martineau

+1

sembra inutile complicato. Non risolve i problemi di buffering. Vedi [link nella mia risposta] (http://stackoverflow.com/a/17698359/4279). – jfs

+0

Questo è l'unico modo in cui posso ottenere l'output progressivo di rsync in tempo reale (- outbuf = L)! grazie – Mohammadhzp

1

Ho usato questa soluzione per ottenere l'output in tempo reale su un sottoprocesso. Questo ciclo si interromperà non appena il processo termina lasciando fuori la necessità di un'istruzione break o di un loop infinito possibile.

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

è possibile che questo esca dal ciclo senza che il buffer stdout sia vuoto? – jayjay

1

Tempo reale Problema risolto in uscita: ho incontrato problema simile in Python, durante la cattura la vera uscita tempo dall'avviamento del programma c. Ho aggiunto "fflush (stdout);" nel mio codice C. Ha funzionato per me. Ecco l'elemento di cattura il codice

< < C Programma >>

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

< < Python Programma >>

#!/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) 

< < USCITA >> Print: Count 1 Stampa: Conte 2 Stampa: Conta 3

Spero che aiuti.

~ Sairam

0

trovato questa funzione "plug-and-play" here. Ha funzionato come un fascino!

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) 
+1

L'aggiunta di 'stderr = subprocess.STDOUT' in realtà aiuta molto nell'acquisizione dei dati di streaming. Sto svalutando. – khan

+1

La carne principale qui sembra provenire dalla [risposta accettata] (https://stackoverflow.com/a/803421/874188) – tripleee

1

È possibile utilizzare un iteratore su ciascun byte nell'output del sottoprocesso. Ciò consente aggiornamento in linea (linee che terminano con '\ r' sovrascrivere precedente linea di uscita) dal sottoprocesso:

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

È lo scheletro di base che uso sempre per questo. Semplifica l'implementazione dei timeout ed è in grado di gestire i processi sospesi inevitabili.

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

È possibile indirizzare direttamente l'output del sottoprocesso ai flussi. Esempio semplificato:

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