Riepilogo (o versione "tl; dr"): è facile quando ce n'è al massimo uno subprocess.PIPE , altrimenti è difficile.
Potrebbe essere il momento di spiegare un po 'come subprocess.Popen funziona.
(Avvertenza: questo è per Python 2.x, sebbene 3.x sia simile; e sono piuttosto confuso sulla variante di Windows. Capisco molto meglio le cose POSIX.)
La Popenfunzione deve gestire da zero a tre flussi I / O, un po 'contemporaneamente. Questi sono indicati stdin, stdoutestderr come al solito.
Puoi fornire:
None, indicando che non si desidera reindirizzare lo stream. Invece erediterà questi come al solito. Notare che sui sistemi POSIX, almeno, questo non significa che utilizzerà Python's sys.stdout, solo lo stdout effettivo di Python ; vedi la demo alla fine.
- Un
intvalore. Questo è un descrittore di file "grezzo" (almeno in POSIX). (Nota a margine: PIPEe in STDOUTrealtà lo sonoint internamente, ma sono descrittori "impossibili", -1 e -2.)
- Un flusso - davvero, qualsiasi oggetto con un
filenometodo. Popentroverà il descrittore per quello stream, usando stream.fileno(), e quindi procederà come per un intvalore.
subprocess.PIPE, indicando che Python dovrebbe creare una pipe.
subprocess.STDOUT( stderrsolo per ): dire a Python di usare lo stesso descrittore di stdout. Questo ha senso solo se hai fornito un Nonevalore (non ) per stdout, e anche allora, è necessario solo se impostato stdout=subprocess.PIPE. (Altrimenti puoi semplicemente fornire lo stesso argomento che hai fornitostdout , ad es Popen(..., stdout=stream, stderr=stream).)
I casi più semplici (senza tubi)
Se non reindirizzi nulla (lasci tutti e tre come Nonevalore predefinito o fornisci esplicito None), Pipeè abbastanza facile. Deve solo svincolare il sottoprocesso e lasciarlo funzionare. Oppure, se si reindirizza a un non PIPE—un into di un flusso fileno()— è ancora facile, poiché il sistema operativo fa tutto il lavoro. Python deve semplicemente escludere il sottoprocesso, collegando lo stdin, lo stdout e / o lo stderr ai descrittori di file forniti.
Il caso ancora semplice: una pipa
Se reindirizzi solo uno stream, le Pipecose sono ancora abbastanza facili. Scegliamo uno stream alla volta e guardiamo.
Supponiamo che tu voglia fornirne alcuni stdin, ma lascialo andare stdoute stderrnon reindirizzato, oppure vai a un descrittore di file. Come processo principale, il tuo programma Python deve semplicemente utilizzare write()per inviare i dati nella pipe. Puoi farlo da solo, ad esempio:
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
proc.stdin.write('here, have some data\n') # etc
oppure puoi passare i dati stdin a proc.communicate(), che quindi fa quanto stdin.writesopra indicato. Non viene restituito alcun output, quindi communicate()ha solo un altro vero lavoro: chiude anche il tubo per te. (Se non si chiama, proc.communicate()è necessario chiamare proc.stdin.close()per chiudere la pipe, in modo che il sottoprocesso sappia che non ci sono più dati in arrivo.)
Supponiamo che tu voglia catturare stdoutma partire stdine stderrsolo. Ancora una volta, è facile: basta chiamare proc.stdout.read()(o equivalente) fino a quando non c'è più output. Poiché proc.stdout()è un normale flusso di I / O Python, puoi usare tutti i normali costrutti su di esso, come:
for line in proc.stdout:
o, ancora una volta, puoi usare proc.communicate(), il che fa semplicemente read()per te.
Se vuoi catturare solo stderr, funziona come con stdout.
C'è ancora un trucco prima che le cose diventino difficili. Supponiamo che tu voglia catturare stdout, e anche catturare stderrma sulla stessa pipe di stdout:
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
In questo caso, subprocess"trucchi"! Bene, deve farlo, quindi non è davvero un imbroglio: avvia il sottoprocesso con sia il suo stdout che il suo stderr diretti nel (singolo) descrittore di pipe che ritorna al suo processo genitore (Python). Sul lato genitore, c'è di nuovo un solo descrittore di pipe per leggere l'output. Tutto l'output "stderr" appare in proc.stdout, e se chiami proc.communicate(), il risultato stderr (secondo valore nella tupla) sarà None, non una stringa.
I casi difficili: due o più tubi
Tutti i problemi sorgono quando si desidera utilizzare almeno due pipe. In effetti, il subprocesscodice stesso ha questo bit:
def communicate(self, input=None):
...
# Optimization: If we are only using one pipe, or no pipe at
# all, using select() or threads is unnecessary.
if [self.stdin, self.stdout, self.stderr].count(None) >= 2:
Ma, ahimè, qui abbiamo realizzato almeno due, e forse tre, pipe diverse, quindi i count(None)rendimenti sono 1 o 0. Dobbiamo fare le cose nel modo più duro.
Su Windows, questo utilizza threading.Threadper accumulare risultati per self.stdoute self.stderre fa in modo che il thread principale fornisca i self.stdindati di input (e quindi chiuda la pipe).
Su POSIX, questo utilizza pollse disponibile, altrimenti select, per accumulare output e fornire input stdin. Tutto ciò viene eseguito nel processo / thread (singolo) padre.
Thread o poll / select sono necessari qui per evitare deadlock. Supponiamo, ad esempio, di aver reindirizzato tutti e tre i flussi a tre tubi separati. Supponiamo inoltre che esista un piccolo limite alla quantità di dati che possono essere inseriti in una pipe prima che il processo di scrittura venga sospeso, in attesa che il processo di lettura "ripulisca" la pipe dall'altra estremità. Impostiamo quel piccolo limite su un singolo byte, solo a scopo illustrativo. (Ecco come funzionano le cose, tranne per il fatto che il limite è molto più grande di un byte.)
Se il processo parent (Python) tenta di scrivere più byte — diciamo 'go\n'a proc.stdin, il primo byte entra e quindi il secondo fa sospendere il processo Python, in attesa che il sottoprocesso legga il primo byte, svuotando la pipe.
Nel frattempo, supponiamo che il sottoprocesso decida di stampare un amichevole "Ciao! Non farti prendere dal panico!" saluto. La Hva nella sua pipe stdout, ma la efa sospendere, aspettando che il suo genitore la leggaH , svuotando la pipe stdout.
Ora siamo bloccati: il processo Python è addormentato, in attesa di finire dicendo "vai", e anche il sottoprocesso è addormentato, in attesa di finire dicendo "Ciao! Non farti prendere dal panico!".
Il subprocess.Popencodice evita questo problema con threading o select / poll. Quando i byte possono oltrepassare le pipe, vanno. Quando non possono, solo un thread (non l'intero processo) deve dormire - o, nel caso di select / poll, il processo Python attende simultaneamente "può scrivere" o "dati disponibili", scrive nello stdin del processo solo quando c'è spazio e legge il suo stdout e / o stderr solo quando i dati sono pronti. Il proc.communicate()codice (in realtà _communicatedove vengono gestiti i casi pelosi) restituisce una volta che tutti i dati stdin (se presenti) sono stati inviati e tutti i dati stdout e / o stderr sono stati accumulati.
Se si desidera leggere entrambi stdoute stderrsu due pipe diverse (indipendentemente dal stdinreindirizzamento), è necessario evitare anche il deadlock. Lo scenario di deadlock qui è diverso - si verifica quando il sottoprocesso scrive qualcosa da molto tempo stderrmentre si stanno estraendo dati stdouto viceversa - ma è ancora lì.
The Demo
Ho promesso di dimostrare che, non reindirizzato, Python subprocesses scrive allo stdout sottostante, no sys.stdout. Quindi, ecco un po 'di codice:
from cStringIO import StringIO
import os
import subprocess
import sys
def show1():
print 'start show1'
save = sys.stdout
sys.stdout = StringIO()
print 'sys.stdout being buffered'
proc = subprocess.Popen(['echo', 'hello'])
proc.wait()
in_stdout = sys.stdout.getvalue()
sys.stdout = save
print 'in buffer:', in_stdout
def show2():
print 'start show2'
save = sys.stdout
sys.stdout = open(os.devnull, 'w')
print 'after redirect sys.stdout'
proc = subprocess.Popen(['echo', 'hello'])
proc.wait()
sys.stdout = save
show1()
show2()
Quando eseguito:
$ python out.py
start show1
hello
in buffer: sys.stdout being buffered
start show2
hello
Nota che la prima routine fallirà se aggiungi stdout=sys.stdout, poiché un StringIOoggetto non ha fileno. Il secondo ometterà il hellose si aggiunge da stdout=sys.stdoutquando sys.stdoutè stato reindirizzato a os.devnull.
(Se si reindirizza di Python descrittori di file-1, il sottoprocesso sarà seguire quel reindirizzamento. La open(os.devnull, 'w')chiamata produce un flusso di cui fileno()è maggiore di 2)
Popen.pollcome in una precedente domanda Stack Overflow .