2015-08-05 17 views
13

Ho un sottoprocesso python che sto provando a leggere output e flussi di errore da. Attualmente ho funzionato, ma sono in grado di leggere solo da stderr dopo aver letto da stdout. Ecco come si presenta:Python legge da sottoprocesso stdout e stderr separatamente pur mantenendo l'ordine

process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 
stdout_iterator = iter(process.stdout.readline, b"") 
stderr_iterator = iter(process.stderr.readline, b"") 

for line in stdout_iterator: 
    # Do stuff with line 
    print line 

for line in stderr_iterator: 
    # Do stuff with line 
    print line 

Come si può vedere, la stderr ciclo for non può iniziare fino a quando il ciclo stdout completa. Come posso modificare questo per poter leggere da entrambi nell'ordine corretto in cui arrivano le righe?

per chiarire: ho ancora bisogno di essere in grado di dire se una linea è venuto da stdout o stderr perché saranno trattati in modo diverso nel mio codice.

+0

correlati: [comando Eseguie ottenere il suo stdout, stderr separatamente in tempo quasi reale come in un terminale] (http://stackoverflow.com/q/31926470/4279) – jfs

risposta

2

È possibile indirizzare stderr a STDOUT:

process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 
+6

Questo è bello sapere, ma ho bisogno di essere in grado di differenziare le linee standard dalle linee di errore perché il ciclo farà cose diverse in base al tipo di output. C'è un modo per determinarlo ancora quando lo si reindirizza? –

-1

Come spiegato nel python tutorial

È possibile utilizzare stderr=subprocess.STDOUT

Nella frequenti sezione di domande nel tutorial di cui sopra si può vedere questo paragrafo

stdin, stdout e stderr spe cificare rispettivamente l'input standard del programma , l'output standard e gli handle di file di errore standard. I valori validi sono PIPE, un descrittore di file esistente (un numero intero positivo ), un oggetto file esistente e Nessuno. PIPE indica che deve essere creata una nuova tubazione per il bambino. Con le impostazioni predefinite di Nessuna, si verificherà alcun reindirizzamento; gli handle del file del figlio saranno ereditati dal genitore. Inoltre, stderr può essere STDOUT, che indica che i dati di stderr dal processo figlio devono essere catturati nello stesso handle di file di stdout.

+0

Come ho spiegato a Padraic, devo essere in grado di differenziare le linee standard dalle righe di errore perché il ciclo farà cose diverse in base al tipo di output. C'è un modo per determinarlo ancora quando lo si reindirizza? –

+0

attualmente non ho una soluzione pitonica nella mia mente, ma possiamo prendere dalla citazione che possiamo mettere lo stdout, stderr come descrittore di file sono curioso di sapere se è possibile reindirizzare lo stdout e lo stderr in due file diversi allora fare un altro ciclo per leggere una riga per riga mentre il processo è in esecuzione; quindi scaricare il risultato in un terzo file che separa le righe lette dallo stdout con [STDOU] all'inizio della riga. ancora una volta questa non è una soluzione pitonica; questa è una soluzione per risolvere i problemi. –

+0

voglio dire un codice come in questo [collegamento] (http://blog.endpoint.com/2015/01/getting-realtime-output-using-python.html) –

0

Ho scritto qualcosa per fare questo a long time ago. Non l'ho ancora trasferito su Python 3, ma non dovrebbe essere troppo difficile (patch accettate!)

Se lo esegui da solo, vedrai molte delle diverse opzioni. In ogni caso, ti permette di distinguere lo stdout da stderr.

10

Il codice nella domanda potrebbe bloccarsi se il processo figlio produce abbastanza output su stderr (~ 100 KB sul mio computer Linux).

C'è un metodo communicate() che permette di leggere sia da stdout e stderr separatamente:

from subprocess import Popen, PIPE 

process = Popen(command, stdout=PIPE, stderr=PIPE) 
output, err = process.communicate() 

Se avete bisogno di leggere i flussi mentre il processo figlio è ancora in esecuzione, allora la soluzione portatile è quello di utilizzare le discussioni (non testato):

from subprocess import Popen, PIPE 
from threading import Thread 
from Queue import Queue # Python 2 

def reader(pipe, queue): 
    try: 
     with pipe: 
      for line in iter(pipe.readline, b''): 
       queue.put((pipe, line)) 
    finally: 
     queue.put(None) 

process = Popen(command, stdout=PIPE, stderr=PIPE, bufsize=1) 
q = Queue() 
Thread(target=reader, args=[process.stdout, q]).start() 
Thread(target=reader, args=[process.stderr, q]).start() 
for _ in range(2): 
    for source, line in iter(q.get, None): 
     print "%s: %s" % (source, line), 

See:

+0

Purtroppo questa risposta non conserva l'ordine che il le linee provengono da 'stdout' e' stderr'. È molto vicino a quello che mi serve comunque! Per me è solo importante sapere quando una linea 'stderr' è collegata a una linea' stdout'. –

+2

@LukeSapan: Non vedo alcun modo per mantenere l'ordine * e * per acquisire separatamente stdout/stderr. Puoi ottenere facilmente l'uno o l'altro. Su Unix puoi provare un ciclo di selezione che può rendere l'effetto meno evidente. Comincia a sembrare [problema XY] (http://meta.stackexchange.com/a/66378/137096): modifica la tua domanda e fornisci un contesto su ciò che stai cercando di fare. – jfs

+0

@LukeSapan: ecco [la risposta con un ciclo di selezione] (http://stackoverflow.com/a/31953436/4279). Come ho detto, non conserverà l'ordine nel caso generale, ma potrebbe essere sufficiente in alcuni casi. – jfs

3

L'ordine in cui un processo scrive i dati a diversi tubi è perso dopo scrittura.

Non c'è modo di sapere se lo stdout è stato scritto prima di stderr.

È possibile provare a leggere i dati contemporaneamente da più descrittori di file in modo non bloccante non appena i dati sono disponibili, ma ciò minimizzerebbe solo la probabilità che l'ordine non sia corretto.

Questo programma dovrebbe dimostrare questo:

#!/usr/bin/env python 
# -*- coding: utf-8 -*- 

import os 
import select 
import subprocess 

testapps={ 
    'slow': ''' 
import os 
import time 
os.write(1, 'aaa') 
time.sleep(0.01) 
os.write(2, 'bbb') 
time.sleep(0.01) 
os.write(1, 'ccc') 
''', 
    'fast': ''' 
import os 
os.write(1, 'aaa') 
os.write(2, 'bbb') 
os.write(1, 'ccc') 
''', 
    'fast2': ''' 
import os 
os.write(1, 'aaa') 
os.write(2, 'bbbbbbbbbbbbbbb') 
os.write(1, 'ccc') 
''' 
} 

def readfds(fds, maxread): 
    while True: 
     fdsin, _, _ = select.select(fds,[],[]) 
     for fd in fdsin: 
      s = os.read(fd, maxread) 
      if len(s) == 0: 
       fds.remove(fd) 
       continue 
      yield fd, s 
     if fds == []: 
      break 

def readfromapp(app, rounds=10, maxread=1024): 
    f=open('testapp.py', 'w') 
    f.write(testapps[app]) 
    f.close() 

    results={} 
    for i in range(0, rounds): 
     p = subprocess.Popen(['python', 'testapp.py'], stdout=subprocess.PIPE 
                , stderr=subprocess.PIPE) 
     data='' 
     for (fd, s) in readfds([p.stdout.fileno(), p.stderr.fileno()], maxread): 
      data = data + s 
     results[data] = results[data] + 1 if data in results else 1 

    print 'running %i rounds %s with maxread=%i' % (rounds, app, maxread) 
    results = sorted(results.items(), key=lambda (k,v): k, reverse=False) 
    for data, count in results: 
     print '%03i x %s' % (count, data) 


print 
print "=> if output is produced slowly this should work as whished" 
print " and should return: aaabbbccc" 
readfromapp('slow', rounds=100, maxread=1024) 

print 
print "=> now mostly aaacccbbb is returnd, not as it should be" 
readfromapp('fast', rounds=100, maxread=1024) 

print 
print "=> you could try to read data one by one, and return" 
print " e.g. a whole line only when LF is read" 
print " (b's should be finished before c's)" 
readfromapp('fast', rounds=100, maxread=1) 

print 
print "=> but even this won't work ..." 
readfromapp('fast2', rounds=100, maxread=1) 

ed emette qualcosa di simile:

=> if output is produced slowly this should work as whished 
    and should return: aaabbbccc 
running 100 rounds slow with maxread=1024 
100 x aaabbbccc 

=> now mostly aaacccbbb is returnd, not as it should be 
running 100 rounds fast with maxread=1024 
006 x aaabbbccc 
094 x aaacccbbb 

=> you could try to read data one by one, and return 
    e.g. a whole line only when LF is read 
    (b's should be finished before c's) 
running 100 rounds fast with maxread=1 
003 x aaabbbccc 
003 x aababcbcc 
094 x abababccc 

=> but even this won't work ... 
running 100 rounds fast2 with maxread=1 
003 x aaabbbbbbbbbbbbbbbccc 
001 x aaacbcbcbbbbbbbbbbbbb 
008 x aababcbcbcbbbbbbbbbbb 
088 x abababcbcbcbbbbbbbbbb 
+0

non correlato: utilizzare 'se non s:' invece di 'if len (s) == 0:' qui. Usa 'while fds:' invece di 'while True: ... if fds == []: break'. Usa 'results = collections.defaultdict (int); ...; risultati [dati] + = 1' invece di 'risultati = {}; ...; risultati [dati] = risultati [dati] + 1 se dati in altri risultati 1' – jfs

+0

Oppure utilizzare 'results = collections.Counter(); ...; Risultati [dati] + = 1; ...; per i dati, conta in results.most_common(): ' – jfs

+0

potresti usare' data = b '' join ([s per _, s in readfds (...)]) ' – jfs

-1

Secondo doc di pitone

Popen.stdout Se l'argomento è stato stdout TUBO , questo attributo è un oggetto file che fornisce l'output dal processo figlio. Altrimenti, è Nessuno.

Popen.stderr Se l'argomento stderr era PIPE, questo attributo è un oggetto file che fornisce l'output dell'errore dal processo figlio. Altrimenti, è Nessuno.

Qui di seguito campione può fare ciò che si vuole

test.py

print "I'm stdout" 

raise Exception("I'm Error") 

printer.py

import subprocess 

p = subprocess.Popen(['python', 'test.py'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 

print "Normal" 
std_lines = p.stdout.readlines() 
for line in std_lines: 
    print line.rstrip() 

print "Error" 
stderr_lines = p.stderr.readlines() 
for line in stderr_lines: 
    print line.rstrip() 

uscita:

Normal 
I'm stdout 

Error 
Traceback (most recent call last): 
    File "test.py", line 3, in <module> 
    raise Exception("I'm Error") 
Exception: I'm Error 
+0

La domanda originale (ultima modifica un anno prima la tua risposta) chiarisce che sta chiedendo che stdout e stderr siano interfogliati. –

+0

@DaveKnight hai ragione, ho frainteso la risposta di PO. – simomo