2015-08-05 10 views
13

Ich habe einen Python-Subprozess, den ich versuche, Ausgabe und Fehler Streams aus zu lesen. Derzeit habe ich es funktioniert, aber ich kann nur lesen von stderr, nachdem ich das Lesen von stdout abgeschlossen habe. Hier ist, wie es aussieht:Python gelesen von Subprozess stdout und stderr getrennt, während die Reihenfolge beibehalten

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 

Wie Sie sehen können, die stderr for-Schleife kann nicht gestartet werden, bis die stdout Schleife abgeschlossen ist. Wie kann ich das ändern, um von beiden in der richtigen Reihenfolge lesen zu können?

Zur Klarstellung: Ich muß noch in der Lage sein zu sagen, ob eine Linie von stdout oder stderr kam, weil sie in meinem Code unterschiedlich behandelt werden.

+0

bezogen werden: [Befehl Ausführen und bekomme seine stdout, stderr separat in fast Echtzeit wie in einem Terminal] (http://stackoverflow.com/q/31926470/4279) – jfs

Antwort

2

Sie können stderr zu STDOUT direkt:

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

Das ist toll zu wissen, aber ich muss in der Lage sein, Standardzeilen von Fehlerzeilen zu unterscheiden weil die Schleife verschiedene Dinge basierend auf dem Ausgabetyp ausführt. Gibt es eine Möglichkeit, dies bei der Weiterleitung noch zu bestimmen? –

-1

Wie in der python tutorial

können Sie verwenden stderr=subprocess.STDOUT

In der häufig Frage Abschnitt in dem genannten Aufsatz gefragt oben Sie diese sehen können Absatz

stdin, stdout und stderr spe cify die Standard- des ausgeführten Programms Eingabe, Standard-Ausgabe und Standard-Fehler Datei behandelt, jeweils. Gültige Werte sind PIPE, ein vorhandener Dateideskriptor (eine positive Ganzzahl ), ein vorhandenes Dateiobjekt und None. PIPE gibt an, dass eine neue Pipe für das Kind erstellt werden soll. Mit den Standardeinstellungen Keine wird keine Umleitung erfolgen. Die Dateihandles des Kindes sind , die vom übergeordneten Objekt geerbt werden. Darüber hinaus kann stderr STDOUT sein, das angibt, dass die stderr-Daten aus dem unterordneten Prozess in demselben Dateihandle wie für stdout erfasst werden sollten.

+0

Wie ich Padraic erklärt habe, muss ich in der Lage sein, Standardzeilen von Fehlerzeilen zu unterscheiden, da die Schleife je nach Art der Ausgabe verschiedene Dinge tun wird. Gibt es eine Möglichkeit, dies bei der Weiterleitung noch zu bestimmen? –

+0

zur Zeit habe ich keine pythonische Lösung in meinen Gedanken, aber wir können aus dem Zitat nehmen, dass wir die stdout, stderr als Datei-Deskriptor setzen können Ich bin neugierig, wenn Sie die stdout und stderr zu zwei verschiedenen Dateien dann umleiten können eine weitere Schleife erstellen, um Zeile für Zeile zu lesen, während der Prozess läuft; dann dumpen Sie das Ergebnis in eine dritte Datei, die die Zeilen trennt, die von stdout mit [STDOU] am Anfang der Zeile gelesen werden. wieder ist dies keine pythonische Lösung; Dies ist eine Arbeit um die Probleme zu lösen. –

+0

Ich meine einen Code wie in diesem [link] (http://blog.endpoint.com/2015/01/getting-realtime-output-using-python.html) –

0

Ich schrieb etwas, um dies zu tun ein long time ago. Ich habe es noch nicht zu Python 3 portiert, aber es sollte nicht zu schwierig sein (Patches akzeptiert!)

Wenn Sie es eigenständig ausführen, werden Sie viele der verschiedenen Optionen sehen. In jedem Fall können Sie stdout von stderr unterscheiden.

10

Der Code in Ihrer Frage möglicherweise Deadlocks, wenn der Kindprozess produziert genügend Ausgabe auf stderr (~ 100 KB auf meinem Linux-Rechner).

Es gibt eine communicate() Methode, die getrennt von den beiden stdout und stderr lesen können:

from subprocess import Popen, PIPE 

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

Wenn Sie die Ströme lesen müssen, während das Kind Prozess dann ist die tragbare Lösung läuft noch Threads zu verwenden (nicht getestet):

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

Leider diese Antwort nicht die Reihenfolge, dass die Linien kommen von 'stdout' und' stderr'. Es ist sehr nah an dem, was ich brauche! Es ist nur wichtig für mich zu wissen, wann eine 'stderr'-Linie relativ zu einer' stdout'-Linie verlegt wird. –

+2

@LukeSapan: Ich sehe keine Möglichkeit, um die Reihenfolge * und * zu erhalten, um stdout/stderr separat zu erfassen. Sie können das eine oder andere leicht erreichen. Unter Unix könnten Sie eine Auswahlschleife versuchen, die den Effekt weniger offensichtlich macht. Es sieht aus wie [XY-Problem] (http://meta.stackexchange.com/a/66378/137096): Bearbeiten Sie Ihre Frage und geben Sie einen Kontext dafür, was Sie zu tun versuchen. – jfs

+0

@LukeSapan: Hier ist [die Antwort mit einer Auswahlschleife] (http://stackoverflow.com/a/31953436/4279). Wie ich sagte, wird es im allgemeinen Fall keine Ordnung bewahren, aber es könnte in einigen Fällen reichen. – jfs

3

Die Reihenfolge, in der ein Prozessdaten an verschiedene Rohre verloren nach dem Schreiben schreibt.

Es gibt keine Möglichkeit zu sagen, ob stdout vor stderr geschrieben wurde.

Sie können versuchen, Daten gleichzeitig aus mehreren Dateideskriptoren auf nicht blockierende Weise zu lesen, sobald Daten verfügbar sind, dies würde jedoch nur die Wahrscheinlichkeit minimieren, dass die Reihenfolge falsch ist.

sollte Dieses Programm dies demonstrieren:

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

und gibt so etwas wie dieses:

=> 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

nicht verwandt: benutze 'if not s:' anstelle von 'if len (s) == 0:' hier. Verwenden Sie 'while fds:' anstelle von 'while True: ... wenn fds == []: break'. Verwenden Sie 'results = collections.defaultdict (int); ...; Ergebnisse [Daten] + = 1 'anstelle von' Ergebnisse = {}; ...; Ergebnisse [Daten] = Ergebnisse [Daten] + 1 wenn Daten in Ergebnissen sonst 1 ' – jfs

+0

Oder verwenden Sie 'results = collections.Counter(); ...; Ergebnisse [Daten] + = 1; ...; Für Daten zählen Sie in results.most_common(): ' – jfs

+0

Sie könnten' data = b 'verwenden. join ([s für _, s in readfds (...)]) ' – jfs

-1

Nach doc Python

Popen.stdout Wenn das stdout Argument PIPE war Dieses Attribut ist ein Dateiobjekt, das die Ausgabe des untergeordneten Prozesses bereitstellt. Ansonsten ist es keine.

Popen.stderr Wenn das stderr-Argument PIPE war, ist dieses Attribut ein Dateiobjekt, das eine Fehlerausgabe vom untergeordneten Prozess bereitstellt. Ansonsten ist es keine.

Below Probe kann tun, was Sie

test.py

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

Ausgabe wollen

print "I'm stdout" 

raise Exception("I'm Error") 

:

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

Die ursprüngliche Frage (zuletzt ein Jahr zuvor bearbeitet) Ihre Antwort) macht deutlich, dass er stdout und stderr interleaved anfordert. –

+0

@DaveKnight du hast recht, ich habe die Antwort von PO missverstanden. – simomo