2014-09-09 13 views
6

Sto eseguendo uno script tramite il modulo di sottoprocesso di Python. Attualmente io uso:Visualizzazione dell'elaborazione di sottoprocesso su stdout e reindirizzamento

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

Quindi stampo il risultato sullo stdout. Tutto ciò va bene, ma poiché la sceneggiatura richiede molto tempo per essere completata, ho voluto anche l'output in tempo reale dallo script allo stdout. Il motivo per cui canalizzo l'output è perché voglio analizzarlo.

+0

correlato: [Python: leggere lo streaming di ingresso da 'subprocess.communicate()'] (http://stackoverflow.com/q/2715847/4279) – jfs

+0

related: [Sottoprocesso Python scarica l'output dei bambini su file e terminale?] (Http://stackoverflow.com/q/4984428/4279) – jfs

+0

Puoi provare a usare subprocess.call (['/ percorso/a/script']) se non hai bisogno di accedere a tutte le opzioni di livello inferiore di Popen. L'output dovrebbe eseguire lo streaming su stdout per impostazione predefinita. – Lukeclh

risposta

11

Per salva lo stdout del sottoprocesso su una variabile per ulteriore elaborazione e su 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 

Per salvare stdout sia del sottoprocesso e stderr è più complessa perché si dovrebbe 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() 
... 

dove teed_call() is define here.


Aggiornamento: ecco a simpler asyncio version.


vecchia versione:

Ecco una soluzione single-threaded in base a 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) 

Lo script viene eseguito '/path/to/script di comando e legge riga per riga sia il suo stdout & stderr contemporaneamente. Le righe vengono stampate nello stdout/stderr del genitore in modo corrispondente e salvate come estensioni per l'elaborazione futura. Per eseguire il read_and_display() coroutine, abbiamo bisogno di un ciclo di eventi:

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 e quindi restituisce l'intera uscita in una volta.

Hai provato qualcosa del genere, invece, dove leggi l'output del sottoprocesso riga per riga?

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

se il processo figlio genera un output sufficiente per riempire il buffer del pipe stderr del sistema operativo (65 KB sul mio computer), quindi si blocca. Dovresti anche consumare 'p.stderr' - contemporaneamente. A causa del bug di read-ahead, 'for line in p.stdout' verrà stampato a raffica. Potresti usare 'per line in iter (p.stdout.readline, b '')'. 'print line' stamperà double newlines. Potresti usare 'print line,' (nota: virgola), per evitarlo. – jfs

+0

Ottimo punto anche per il consumo di 'stderr'. Supponevo che alcune righe di buffering non sarebbero state un problema in un lungo flusso di dati, ma anche questo è qualcosa da considerare. –

+1

* "lo script impiega molto tempo per essere completato" * - significa che se lo script scrive progressi in stderr, allora * può * arrestarsi. – jfs

0

Il dottore Popen.communicate afferma chiaramente:

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

Quindi, se avete bisogno di uscita in tempo reale, è necessario utilizzare qualcosa di simile:

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 
Problemi correlati