Leggi l'input di streaming da subprocess.communicate ()


85

Sto usando Python subprocess.communicate()per leggere lo stdout da un processo che viene eseguito per circa un minuto.

Come posso stampare ogni riga di quel processo stdoutin streaming, in modo da poter vedere l'output mentre viene generato, ma comunque bloccare il processo che termina prima di continuare?

subprocess.communicate() sembra dare tutto l'output in una volta.


Risposte:


46

Nota, penso che il metodo di JF Sebastian (sotto) sia migliore.


Ecco un semplice esempio (senza controllo degli errori):

import subprocess
proc = subprocess.Popen('ls',
                       shell=True,
                       stdout=subprocess.PIPE,
                       )
while proc.poll() is None:
    output = proc.stdout.readline()
    print output,

Se lsfinisce troppo velocemente, il ciclo while potrebbe terminare prima che tu abbia letto tutti i dati.

Puoi catturare il resto in stdout in questo modo:

output = proc.communicate()[0]
print output,

1
questo schema è vittima del problema di blocco del buffer a cui si riferisce il documento di python?
Heinrich Schmetterling

@ Heinrich, il problema del blocco del buffer non è qualcosa che capisco bene. Credo (solo per cercare su Google) che questo problema si verifica solo se non leggi da stdout (e stderr?) All'interno del ciclo while. Quindi penso che il codice sopra vada bene, ma non posso dirlo con certezza.
unutbu

1
Questo in realtà soffre di un problema di blocco, alcuni anni fa non avevo fine al problema in cui readline si bloccava fino a quando non riceveva una nuova riga anche se il processo fosse terminato. Non ricordo la soluzione, ma penso che avesse qualcosa a che fare con le letture su un thread di lavoro e semplicemente il looping while proc.poll() is None: time.sleep(0)o qualcosa in tal senso. Fondamentalmente, devi assicurarti che l'output di nuova riga sia l'ultima cosa che il processo fa (perché non puoi dare all'interprete il tempo di ripetere il ciclo) o devi fare qualcosa di "stravagante".
dash-tom-bang

@Heinrich: Alex Martelli scrive di come evitare la situazione di stallo qui: stackoverflow.com/questions/1445627/...
unutbu

6
Il blocco del buffer è più semplice di quanto a volte sembri: blocchi padre in attesa che il figlio esca + blocchi figlio in attesa che il genitore legga e liberare spazio nel pipe di comunicazione che è pieno = deadlock. È così semplice. Più piccolo è il tubo più è probabile che accada.
MarcH

163

Per ottenere l'output del sottoprocesso riga per riga non appena il sottoprocesso svuota il suo buffer stdout:

#!/usr/bin/env python2
from subprocess import Popen, PIPE

p = Popen(["cmd", "arg1"], stdout=PIPE, bufsize=1)
with p.stdout:
    for line in iter(p.stdout.readline, b''):
        print line,
p.wait() # wait for the subprocess to exit

iter()viene utilizzato per leggere le righe non appena vengono scritte per aggirare il bug della lettura anticipata in Python 2 .

Se lo stdout del sottoprocesso utilizza un buffer di blocco invece di un buffer di riga in modalità non interattiva (che porta a un ritardo nell'output fino a quando il buffer del bambino non è pieno o scaricato esplicitamente dal figlio) allora potresti provare a forzare un output senza buffer usando pexpect, ptymoduli o unbuffer, stdbuf, scriptutilità , vedere D: Perché non basta usare un tubo (popen ())?


Ecco il codice Python 3:

#!/usr/bin/env python3
from subprocess import Popen, PIPE

with Popen(["cmd", "arg1"], stdout=PIPE, bufsize=1,
           universal_newlines=True) as p:
    for line in p.stdout:
        print(line, end='')

Nota: a differenza di Python 2 che restituisce le stringhe di sottoprocesso così come sono; Python 3 utilizza la modalità testo (l'output di cmd viene decodificato utilizzando la locale.getpreferredencoding(False)codifica).


cosa significa la b ""?
Aaron

4
b''è bytesletterale in Python 2.7 e Python 3.
jfs

2
@JinghaoShi: bufsize=1può fare la differenza se scrivi (usando p.stdin) anche nel sottoprocesso, ad esempio, può aiutare a evitare un deadlock durante uno pexpectscambio interattivo ( -like) - supponendo che non ci siano problemi di buffering nel processo figlio stesso. Se stai solo leggendo, come ho detto, la differenza è solo nelle prestazioni: se non è così, potresti fornire un esempio di codice completo minimo che lo mostri?
jfs

1
@ealeon: sì. Richiede tecniche in grado di leggere contemporaneamente stdout / stderr a meno che non si unisca stderr in stdout (passando stderr=subprocess.STDOUTa Popen()). Vedi anche, soluzioni di threading o asincio collegate lì.
jfs

2
@saulspatz se stdout=PIPEnon cattura l'output (lo vedi ancora sullo schermo), il tuo programma potrebbe invece stampare su stderr o direttamente sul terminale. Per unire stdout e stderr, passa stderr=subprocess.STDOUT(vedi il mio commento precedente). Per acquisire l'output stampato direttamente sulla tua tty, puoi usare pexpect, soluzioni pty. . Ecco un esempio di codice più complesso .
jfs

6

Credo che il modo più semplice per raccogliere l'output di un processo in streaming sia questo:

import sys
from subprocess import *
proc = Popen('ls', shell=True, stdout=PIPE)
while True:
    data = proc.stdout.readline()   # Alternatively proc.stdout.read(1024)
    if len(data) == 0:
        break
    sys.stdout.write(data)   # sys.stdout.buffer.write(data) on Python 3.x

Il readline()read() funzione o dovrebbe restituire solo una stringa vuota su EOF, dopo che il processo è terminato - altrimenti si bloccherà se non c'è niente da leggere ( readline()include il ritorno a capo, quindi su righe vuote, restituisce "\ n"). Questo evita la necessità di un finale imbarazzantecommunicate() chiamata dopo il ciclo.

Su file con righe molto lunghe read()può essere preferibile ridurre l'utilizzo massimo della memoria: il numero passato ad esso è arbitrario, ma escludendolo si ottiene la lettura dell'intero output della pipe in una volta, cosa probabilmente non desiderabile.


4
data = proc.stdout.read()si blocca finché non vengono letti tutti i dati. Potresti confonderlo con il fatto os.read(fd, maxsize)che può tornare prima (non appena i dati sono disponibili).
jfs

Hai ragione, mi sbagliavo. Tuttavia, se viene passato un ragionevole numero di byte come argomento read(), funziona bene, e allo stesso modo readline()funziona bene fintanto che la lunghezza massima della riga è ragionevole. Ho aggiornato di conseguenza la mia risposta.
D Coetzee


2

Se stai semplicemente cercando di trasmettere l'output in tempo reale, è difficile diventare più semplice di questo:

import subprocess

# This will raise a CalledProcessError if the program return a nonzero code.
# You can use call() instead if you don't care about that case.
subprocess.check_call(['ls', '-l'])

Vedere la documentazione per subprocess.check_call () .

Se hai bisogno di elaborare l'output, certo, eseguilo in loop. Ma se non lo fai, mantienilo semplice.

Modifica: JF Sebastian sottolinea sia che i valori predefiniti per i parametri stdout e stderr passano a sys.stdout e sys.stderr, e che questo fallirà se sys.stdout e sys.stderr sono stati sostituiti (ad esempio, per catturare l'output in test).


Non funzionerà se sys.stdouto sys.stderrvengono sostituiti con oggetti simili a file che non hanno un vero fileno (). Se sys.stdout, sys.stderrnon vengono sostituiti, allora è ancora più semplice: subprocess.check_call(args).
jfs

Grazie! Mi ero reso conto dei capricci di sostituire sys.stdout / stderr, ma in qualche modo non mi sono mai reso conto che se ometti gli argomenti, passa stdout e stderr nei posti giusti. Mi piace call()a check_call()meno che non voglia il file CalledProcessError.
Nate

python -mthis: "Gli errori non dovrebbero mai passare in silenzio. A meno che non siano esplicitamente messi a tacere." è per questo che il codice di esempio dovrebbe preferire check_call()sopra call().
jfs

Eh. Molti programmi che finisco call()per restituire codici di errore diversi da zero in condizioni di non errore, perché sono terribili. Quindi, nel nostro caso, un codice di errore diverso da zero non è in realtà un errore.
Nate

sì. Esistono programmi come quelli grepche possono restituire uno stato di uscita diverso da zero anche se non ci sono errori: sono eccezioni. Per impostazione predefinita, lo stato di uscita zero indica il successo.
jfs

1
myCommand="ls -l"
cmd=myCommand.split()
# "universal newline support" This will cause to interpret \n, \r\n and \r     equally, each as a newline.
p = subprocess.Popen(cmd, stderr=subprocess.PIPE, universal_newlines=True)
while True:    
    print(p.stderr.readline().rstrip('\r\n'))

1
è sempre bene spiegare cosa fa la tua soluzione solo per far capire meglio alle persone
DaFois

2
Dovresti considerare di usare shlex.split(myCommand)invece di myCommand.split(). Rispetta anche gli spazi negli argomenti citati.
UtahJarhead

0

Aggiunta di un'altra soluzione python3 con alcune piccole modifiche:

  1. Ti consente di catturare il codice di uscita del processo di shell (non sono stato in grado di ottenere il codice di uscita durante l'utilizzo del withcostrutto)
  2. Inoltre convoglia lo stderr in tempo reale
import subprocess
import sys
def subcall_stream(cmd, fail_on_error=True):
    # Run a shell command, streaming output to STDOUT in real time
    # Expects a list style command, e.g. `["docker", "pull", "ubuntu"]`
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1, universal_newlines=True)
    for line in p.stdout:
        sys.stdout.write(line)
    p.wait()
    exit_code = p.returncode
    if exit_code != 0 and fail_on_error:
        raise RuntimeError(f"Shell command failed with exit code {exit_code}. Command: `{cmd}`")
    return(exit_code)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.