leggere lo sottoprocesso stdout riga per riga


235

Il mio script Python utilizza un sottoprocesso per chiamare un'utilità linux molto rumorosa. Voglio archiviare tutto l'output in un file di registro e mostrarne un po 'all'utente. Ho pensato che avrebbe funzionato quanto segue, ma l'output non viene visualizzato nella mia applicazione fino a quando l'utilità non ha prodotto una quantità significativa di output.

#fake_utility.py, just generates lots of output over time
import time
i = 0
while True:
   print hex(i)*512
   i += 1
   time.sleep(0.5)

#filters output
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
for line in proc.stdout:
   #the real code does filtering here
   print "test:", line.rstrip()

Il comportamento che desidero davvero è che lo script del filtro stampi ogni riga man mano che viene ricevuto dal sottoprocesso. Sorta piace quello che teefa ma con il codice Python.

Cosa mi sto perdendo? È possibile?


Aggiornare:

Se un sys.stdout.flush()viene aggiunto a fake_utility.py, il codice ha il comportamento desiderato in python 3.1. Sto usando Python 2.6. proc.stdout.xreadlines()Penseresti che usare funzionerebbe allo stesso modo di py3k, ma non lo fa.


Aggiornamento 2:

Ecco il codice di lavoro minimo.

#fake_utility.py, just generates lots of output over time
import sys, time
for i in range(10):
   print i
   sys.stdout.flush()
   time.sleep(0.5)

#display out put line by line
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
#works in python 3.0+
#for line in proc.stdout:
for line in iter(proc.stdout.readline,''):
   print line.rstrip()

4
potresti usare print line,invece di print line.rstrip()(nota: virgola alla fine).
jfs


2
L'aggiornamento 2 afferma che funziona con Python 3.0+ ma utilizza la vecchia istruzione di stampa, quindi non funziona con Python 3.0+.
Rooky,

Nessuna delle risposte elencate qui ha funzionato per me, ma stackoverflow.com/questions/5411780/… ha funzionato!
scatola l'

Risposte:


179

È passato molto tempo dall'ultima volta che ho lavorato con Python, ma penso che il problema sia con l'affermazione for line in proc.stdout, che legge l'intero input prima di iterare su di esso. La soluzione è utilizzare readline()invece:

#filters output
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
while True:
  line = proc.stdout.readline()
  if not line:
    break
  #the real code does filtering here
  print "test:", line.rstrip()

Ovviamente devi ancora fare i conti con il buffering del sottoprocesso.

Nota: secondo la documentazione, la soluzione con un iteratore dovrebbe essere equivalente all'utilizzo readline(), ad eccezione del buffer read-ahead, ma (o proprio per questo) la modifica proposta ha prodotto risultati diversi per me (Python 2.5 su Windows XP).


11
per file.readline()vs. for line in filevedere bugs.python.org/issue3907 (in breve: funziona su Python3; utilizzare io.open()su Python 2.6+)
jfs

5
Il test più pitonico per un EOF, secondo i "Consigli di programmazione" in PEP 8 ( python.org/dev/peps/pep-0008 ), sarebbe 'se non linea:'.
Jason Mock,

14
@naxa: per tubi: for line in iter(proc.stdout.readline, ''):.
jfs

3
@ Jan-PhilipGehrcke: si. 1. puoi usare for line in proc.stdoutsu Python 3 (non c'è il bug read-ahead) 2. '' != b''su Python 3 - non copiare e incollare il codice alla cieca - pensa a cosa fa e come funziona.
jfs,

2
@JFSebastian: certo, la iter(f.readline, b'')soluzione è piuttosto ovvia (e funziona anche su Python 2, se qualcuno è interessato). Il punto del mio commento non era quello di incolpare la tua soluzione (scusami se sembrava così, l'ho letto anche adesso!), Ma di descrivere l'entità dei sintomi, che sono piuttosto gravi in ​​questo caso (la maggior parte del Py2 / 3 problemi comportano eccezioni, mentre qui un ciclo ben educato si è trasformato in infinito, e la raccolta dei rifiuti lotta contro il diluvio di oggetti appena creati, producendo oscillazioni nell'uso della memoria con un lungo periodo e una grande ampiezza).
Dr. Jan-Philip Gehrcke,

45

Un po 'tardi alla festa, ma sono stato sorpreso di non vedere quella che penso sia la soluzione più semplice qui:

import io
import subprocess

proc = subprocess.Popen(["prog", "arg"], stdout=subprocess.PIPE)
for line in io.TextIOWrapper(proc.stdout, encoding="utf-8"):  # or another encoding
    # do something with line

(Ciò richiede Python 3.)


25
Vorrei usare questa risposta ma sto ottenendo: AttributeError: 'file' object has no attribute 'readable' py2.7
Dan Garthwaite,

3
Funziona con Python 3
matanster

Chiaramente questo codice non è valido per molteplici ragioni compatibilità py3 / py3 e rischio reale di ottenere ValueError: operazione di I / O su file chiuso
sorin

3
@sorin nessuna di queste cose lo rende "non valido". Se stai scrivendo una libreria che deve ancora supportare Python 2, non utilizzare questo codice. Ma molte persone hanno il lusso di poter utilizzare il software rilasciato più di recente di un decennio fa. Se provi a leggere su un file chiuso otterrai quell'eccezione indipendentemente dal fatto che tu lo usi TextIOWrappero meno. Puoi semplicemente gestire l'eccezione.
jbg

1
forse sei in ritardo alla festa, ma rispondi è aggiornato con la versione attuale di Python, ty
Dusan Gligoric,

20

Infatti, se hai risolto l'iteratore, il buffering potrebbe ora essere il tuo problema. Si potrebbe dire a Python nel processo secondario di non bufferizzare il suo output.

proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)

diventa

proc = subprocess.Popen(['python','-u', 'fake_utility.py'],stdout=subprocess.PIPE)

Ne ho avuto bisogno quando ho chiamato Python da Python.


14

Vuoi passare questi parametri extra a subprocess.Popen:

bufsize=1, universal_newlines=True

Quindi puoi iterare come nel tuo esempio. (Testato con Python 3.5)


2
@nicoulaj Dovrebbe funzionare se si utilizza il pacchetto subprocess32.
Quantum7,

4

Una funzione che consente di scorrere su entrambe stdoute stderrcontemporaneamente, in tempo reale, riga per riga

Nel caso in cui sia necessario ottenere il flusso di output per entrambi stdoute stderrallo stesso tempo, è possibile utilizzare la seguente funzione.

La funzione utilizza Code per unire entrambe le pipe Popen in un singolo iteratore.

Qui creiamo la funzione read_popen_pipes():

from queue import Queue, Empty
from concurrent.futures import ThreadPoolExecutor


def enqueue_output(file, queue):
    for line in iter(file.readline, ''):
        queue.put(line)
    file.close()


def read_popen_pipes(p):

    with ThreadPoolExecutor(2) as pool:
        q_stdout, q_stderr = Queue(), Queue()

        pool.submit(enqueue_output, p.stdout, q_stdout)
        pool.submit(enqueue_output, p.stderr, q_stderr)

        while True:

            if p.poll() is not None and q_stdout.empty() and q_stderr.empty():
                break

            out_line = err_line = ''

            try:
                out_line = q_stdout.get_nowait()
            except Empty:
                pass
            try:
                err_line = q_stderr.get_nowait()
            except Empty:
                pass

            yield (out_line, err_line)

read_popen_pipes() in uso:

import subprocess as sp


with sp.Popen(my_cmd, stdout=sp.PIPE, stderr=sp.PIPE, text=True) as p:

    for out_line, err_line in read_popen_pipes(p):

        # Do stuff with each line, e.g.:
        print(out_line, end='')
        print(err_line, end='')

    return p.poll() # return status-code

2

Puoi anche leggere le righe senza loop. Funziona in python3.6.

import os
import subprocess

process = subprocess.Popen(command, stdout=subprocess.PIPE)
list_of_byte_strings = process.stdout.readlines()

1
O per convertire in stringhe:list_of_strings = [x.decode('utf-8').rstrip('\n') for x in iter(process.stdout.readlines())]
ndtreviv,

1

L'ho provato con python3 e ha funzionato, fonte

def output_reader(proc):
    for line in iter(proc.stdout.readline, b''):
        print('got line: {0}'.format(line.decode('utf-8')), end='')


def main():
    proc = subprocess.Popen(['python', 'fake_utility.py'],
                            stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT)

    t = threading.Thread(target=output_reader, args=(proc,))
    t.start()

    try:
        time.sleep(0.2)
        import time
        i = 0

        while True:
        print (hex(i)*512)
        i += 1
        time.sleep(0.5)
    finally:
        proc.terminate()
        try:
            proc.wait(timeout=0.2)
            print('== subprocess exited with rc =', proc.returncode)
        except subprocess.TimeoutExpired:
            print('subprocess did not terminate in time')
    t.join()

1

La seguente modifica della risposta di Rômulo funziona per me su Python 2 e 3 (2.7.12 e 3.6.1):

import os
import subprocess

process = subprocess.Popen(command, stdout=subprocess.PIPE)
while True:
  line = process.stdout.readline()
  if line != '':
    os.write(1, line)
  else:
    break

0

Non so quando questo è stato aggiunto al modulo di sottoprocesso, ma con Python 3 dovresti andare bene usando proc.stdout.splitlines():

for line in proc.stdout.splitlines():
   print "stdout:", line
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.