2014-09-09 8 views
6

Ich führe ein Skript über Pythons Subprozessmodul aus. Derzeit verwende ich:Anzeige der Subprozessausgabe zu stdout und Weiterleitung

p = subprocess.Popen('/path/to/script', stdout=subprocess.PIPE, stderr=subprocess.PIPE) 
result = p.communicate() 

ich dann das Ergebnis an die stdout drucken. Das ist alles in Ordnung, aber da das Skript eine lange Zeit braucht, um zu vervollständigen, wollte ich Echtzeit-Ausgabe von dem Skript, um ebenso gut. Der Grund, warum ich die Ausgabe pipe, ist, weil ich es analysieren möchte.

+0

verwandten: [Python: Eingang lesen Streaming von 'subprocess.communicate()'] (http://stackoverflow.com/q/2715847/4279) – jfs

+0

verwandt: [Python-Subprozess erhalten Kinderausgabe zu Datei und Terminal?] (Http://Stackoverflow.com/q/4984428/4279) – jfs

+0

Sie könnten versuchen, mit subprocess.call (['/ Pfad/zu/Skript']) wenn Sie nicht auf alle untergeordneten Optionen von Popen zugreifen müssen. Die Ausgabe sollte standardmäßig nach stdout streamen. – Lukeclh

Antwort

11

zu Speichern Sie den Subprozess 'stdout in eine Variable zur weiteren Verarbeitung und zu display it while the child process is running as it arrives:

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

with Popen('/path/to/script', stdout=PIPE, bufsize=1, 
      universal_newlines=True) as p, StringIO() as buf: 
    for line in p.stdout: 
     print(line, end='') 
     buf.write(line) 
    output = buf.getvalue() 
rc = p.returncode 

Um beide subprocess der stdout zu speichern und stderr ist komplexer, weil Sie sollten consume both streams concurrently to avoid a deadlock:

stdout_buf, stderr_buf = StringIO(), StringIO() 
rc = teed_call('/path/to/script', stdout=stdout_buf, stderr=stderr_buf, 
       universal_newlines=True) 
output = stdout_buf.getvalue() 
... 

wo teed_call() is define here.


Update: hier a simpler asyncio version.


Alte Version:

Hier ist eine Single-Threaded-Lösung auf Basis von child_process.py example from tulip:

import asyncio 
import sys 
from asyncio.subprocess import PIPE 

@asyncio.coroutine 
def read_and_display(*cmd): 
    """Read cmd's stdout, stderr while displaying them as they arrive.""" 
    # start process 
    process = yield from asyncio.create_subprocess_exec(*cmd, 
      stdout=PIPE, stderr=PIPE) 

    # read child's stdout/stderr concurrently 
    stdout, stderr = [], [] # stderr, stdout buffers 
    tasks = { 
     asyncio.Task(process.stdout.readline()): (
      stdout, process.stdout, sys.stdout.buffer), 
     asyncio.Task(process.stderr.readline()): (
      stderr, process.stderr, sys.stderr.buffer)} 
    while tasks: 
     done, pending = yield from asyncio.wait(tasks, 
       return_when=asyncio.FIRST_COMPLETED) 
     assert done 
     for future in done: 
      buf, stream, display = tasks.pop(future) 
      line = future.result() 
      if line: # not EOF 
       buf.append(line) # save for later 
       display.write(line) # display in terminal 
       # schedule to read the next line 
       tasks[asyncio.Task(stream.readline())] = buf, stream, display 

    # wait for the process to exit 
    rc = yield from process.wait() 
    return rc, b''.join(stdout), b''.join(stderr) 

Das Skript läuft '/path/to/script Befehl und liest Zeile für Zeile sowohl seine stdout & gleichzeitig stderr. Die Zeilen werden entsprechend in stout/stderr des Elterns ausgegeben und als Bytestrings für die zukünftige Verarbeitung gespeichert. Um die read_and_display() Koroutine laufen, brauchen wir eine Ereignisschleife:

import os 

if os.name == 'nt': 
    loop = asyncio.ProactorEventLoop() # for subprocess' pipes on Windows 
    asyncio.set_event_loop(loop) 
else: 
    loop = asyncio.get_event_loop() 
try: 
    rc, *output = loop.run_until_complete(read_and_display("/path/to/script")) 
    if rc: 
     sys.exit("child failed with '{}' exit code".format(rc)) 
finally: 
    loop.close() 
1

p.communicate() waits for the subprocess to complete und gibt dann seine gesamte Ausgabe auf einmal zurück.

Haben Sie stattdessen so etwas probiert, wo Sie Zeile für Zeile die Unterprozessausgabe gelesen haben?

p = subprocess.Popen('/path/to/script', stdout=subprocess.PIPE, stderr=subprocess.PIPE) 
for line in p.stdout: 
    # do something with this individual line 
    print line 
+1

Wenn der untergeordnete Prozess genug Ausgabe erzeugt, um OS stderr Pipe-Puffer (65K auf meinem Computer) zu füllen, dann hängt es. Sie sollten 'p.stderr' auch gleichzeitig konsumieren. Aufgrund des Read-ahead-Fehlers wird "für Zeile in p.stdout" in Bursts gedruckt. Sie könnten statt dessen '' '' '' '' '' '' '' '' '' '' '' '' '' 'stattdessen für Zeile in iter (p.stdout.readline, b). 'print line' druckt doppelte Zeilenumbrüche. Sie können 'print line,' (Anmerkung: Komma) verwenden, um dies zu vermeiden. – jfs

+0

Toller Punkt über 'stderr' auch zu verbrauchen. Ich ging davon aus, dass ein paar Zeilen Pufferung in einem langen Datenstrom kein Problem darstellen würden, aber das ist auch etwas, das man beachten sollte. –

+1

* "Das Skript benötigt eine lange Zeit bis zur Fertigstellung" * - es bedeutet, dass, wenn das Skript den Fortschritt in stderr schreibt, es * anhalten kann. – jfs

0

Die Popen.communicate doc eindeutig fest:

Note: The data read is buffered in memory, so do not use this method if the data size is large or unlimited. 

https://docs.python.org/2/library/subprocess.html#subprocess.Popen.communicate

Also, wenn Sie Echtzeit-Ausgabe benötigen, können Sie so etwas wie dieses verwenden müssen:

stream_p = subprocess.Popen('/path/to/script', stdout=subprocess.PIPE, stderr=subprocess.PIPE) 

while stream_line in stream_p: 
    #Parse it the way you want 
    print stream_line