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 Popen
funzione deve gestire da zero a tre flussi I / O, un po 'contemporaneamente. Questi sono indicati stdin
, stdout
estderr
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
int
valore. Questo è un descrittore di file "grezzo" (almeno in POSIX). (Nota a margine: PIPE
e in STDOUT
realtà lo sonoint
internamente, ma sono descrittori "impossibili", -1 e -2.)
- Un flusso - davvero, qualsiasi oggetto con un
fileno
metodo. Popen
troverà il descrittore per quello stream, usando stream.fileno()
, e quindi procederà come per un int
valore.
subprocess.PIPE
, indicando che Python dovrebbe creare una pipe.
subprocess.STDOUT
( stderr
solo per ): dire a Python di usare lo stesso descrittore di stdout
. Questo ha senso solo se hai fornito un None
valore (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 None
valore 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 int
o 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 Pipe
cose sono ancora abbastanza facili. Scegliamo uno stream alla volta e guardiamo.
Supponiamo che tu voglia fornirne alcuni stdin
, ma lascialo andare stdout
e stderr
non 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.write
sopra 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 stdout
ma partire stdin
e stderr
solo. 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 stderr
ma 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 subprocess
codice 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.Thread
per accumulare risultati per self.stdout
e self.stderr
e fa in modo che il thread principale fornisca i self.stdin
dati di input (e quindi chiuda la pipe).
Su POSIX, questo utilizza poll
se 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 H
va nella sua pipe stdout, ma la e
fa 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.Popen
codice 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à _communicate
dove 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 stdout
e stderr
su due pipe diverse (indipendentemente dal stdin
reindirizzamento), è necessario evitare anche il deadlock. Lo scenario di deadlock qui è diverso - si verifica quando il sottoprocesso scrive qualcosa da molto tempo stderr
mentre si stanno estraendo dati stdout
o viceversa - ma è ancora lì.
The Demo
Ho promesso di dimostrare che, non reindirizzato, Python subprocess
es 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 StringIO
oggetto non ha fileno
. Il secondo ometterà il hello
se si aggiunge da stdout=sys.stdout
quando 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.poll
come in una precedente domanda Stack Overflow .