Executive Summary (ou version "tl; dr"): c'est facile quand il y en a au plus un subprocess.PIPE
, sinon c'est dur.
Il est peut-être temps d'expliquer un peu comment subprocess.Popen
ça marche.
(Attention: c'est pour Python 2.x, bien que 3.x soit similaire; et je suis assez flou sur la variante Windows. Je comprends beaucoup mieux les choses POSIX.)
La Popen
fonction doit traiter de zéro à trois flux d'E / S, un peu simultanément. Ceux - ci sont indiqués stdin
, stdout
etstderr
comme d' habitude.
Vous pouvez fournir:
None
, indiquant que vous ne souhaitez pas rediriger le flux. Il en héritera comme d'habitude à la place. Notez qu'au moins sur les systèmes POSIX, cela ne signifie pas qu'il utilisera Python sys.stdout
, juste le stdout réel de Python ; voir la démo à la fin.
- Une
int
valeur. Il s'agit d'un descripteur de fichier "brut" (au moins dans POSIX). (Note latérale: PIPE
et STDOUT
sont en fait int
s en interne, mais sont des descripteurs "impossibles", -1 et -2.)
- Un flux - vraiment, n'importe quel objet avec une
fileno
méthode. Popen
trouvera le descripteur de ce flux en utilisant stream.fileno()
, puis procédera comme pour une int
valeur.
subprocess.PIPE
, indiquant que Python doit créer un tube.
subprocess.STDOUT
(pour stderr
seulement): dites à Python d'utiliser le même descripteur que pour stdout
. Cela n'a de sens que si vous avez fourni une None
valeur (non- ) pour stdout
, et même dans ce cas, elle n'est nécessaire que si vous définissez stdout=subprocess.PIPE
. (Sinon, vous pouvez simplement fournir le même argument que vous avez fourni stdout
, par exemple Popen(..., stdout=stream, stderr=stream)
.)
Les cas les plus simples (pas de tuyaux)
Si vous ne redirigez rien (laissez les trois comme None
valeur par défaut ou fournissez explicite None
), Pipe
c'est assez facile. Il a juste besoin de faire tourner le sous-processus et de le laisser s'exécuter. Ou, si vous redirigez vers un non PIPE
- int
un flux ou un flux fileno()
- c'est toujours facile, car le système d'exploitation fait tout le travail. Python a juste besoin de faire tourner le sous-processus, en connectant son stdin, stdout et / ou stderr aux descripteurs de fichier fournis.
Le cas toujours facile: un tuyau
Si vous ne redirigez qu'un seul flux, les Pipe
choses restent assez faciles. Choisissons un flux à la fois et regardons.
Supposons que vous voulez fournir quelques - uns stdin
, mais laisser stdout
et stderr
aller non redirigé, ou aller à un descripteur de fichier. En tant que processus parent, votre programme Python doit simplement l'utiliser write()
pour envoyer des données dans le tube. Vous pouvez le faire vous-même, par exemple:
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
proc.stdin.write('here, have some data\n') # etc
ou vous pouvez transmettre les données stdin à proc.communicate()
, qui effectue ensuite les opérations stdin.write
ci-dessus. Il n'y a pas de sortie qui revient donc communicate()
n'a qu'un seul autre vrai travail: il ferme également le tuyau pour vous. (Si vous n'appelez pas, proc.communicate()
vous devez appeler proc.stdin.close()
pour fermer le canal, afin que le sous-processus sache qu'il n'y a plus de données qui transitent.)
Supposons que vous vouliez capturer stdout
mais partir stdin
et stderr
seul. Encore une fois, c'est facile: il suffit d'appeler proc.stdout.read()
(ou équivalent) jusqu'à ce qu'il n'y ait plus de sortie. Puisqu'il proc.stdout()
s'agit d'un flux d'E / S Python normal, vous pouvez utiliser toutes les constructions normales dessus, comme:
for line in proc.stdout:
ou, encore une fois, vous pouvez utiliser proc.communicate()
, ce qui fait simplement le read()
pour vous.
Si vous souhaitez capturer uniquement stderr
, cela fonctionne de la même manière qu'avec stdout
.
Il y a encore une astuce avant que les choses ne deviennent difficiles. Supposons que vous souhaitiez capturer stdout
, et également capturer stderr
mais sur le même tube que stdout:
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
Dans ce cas, subprocess
"triche"! Eh bien, il doit le faire, donc ce n'est pas vraiment de la triche: il démarre le sous-processus avec à la fois son stdout et son stderr dirigés dans le (unique) descripteur de tube qui renvoie à son processus parent (Python). Du côté parent, il n'y a là encore qu'un seul descripteur de tube pour lire la sortie. Toute la sortie "stderr" apparaît dans proc.stdout
, et si vous appelez proc.communicate()
, le résultat stderr (deuxième valeur dans le tuple) sera None
, pas une chaîne.
Les étuis rigides: deux ou plusieurs tuyaux
Les problèmes surviennent tous lorsque vous souhaitez utiliser au moins deux tuyaux. En fait, le subprocess
code lui-même a ce 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:
Mais, hélas, ici nous avons fait au moins deux, et peut-être trois, tuyaux différents, donc les count(None)
retours sont 1 ou 0. Nous devons faire les choses à la dure.
Sous Windows, cela permet threading.Thread
d'accumuler les résultats pour self.stdout
et self.stderr
, et le thread parent fournit self.stdin
les données d'entrée (puis ferme le canal).
Sur POSIX, cela utilise poll
si disponible, sinon select
, pour accumuler la sortie et fournir une entrée stdin. Tout cela s'exécute dans le processus / thread parent (unique).
Les threads ou poll / select sont nécessaires ici pour éviter les blocages. Supposons, par exemple, que nous ayons redirigé les trois flux vers trois canaux distincts. Supposons en outre qu'il y ait une petite limite sur la quantité de données pouvant être insérées dans un tube avant que le processus d'écriture ne soit suspendu, en attendant que le processus de lecture "nettoie" le tube de l'autre extrémité. Définissons cette petite limite à un seul octet, juste à titre d'illustration. (C'est en fait ainsi que les choses fonctionnent, sauf que la limite est beaucoup plus grande qu'un octet.)
Si le processus parent (Python) essaie d'écrire plusieurs octets - disons, 'go\n'
vers proc.stdin
, le premier octet entre, puis le second entraîne la suspension du processus Python, attendant que le sous-processus lise le premier octet, vidant le tube.
En attendant, supposons que le sous-processus décide d'imprimer un "Hello! Don't Panic!" salutation. Le H
va dans son tube stdout, mais le e
suspend, en attendant que son parent lise cela H
, vidant le tube stdout.
Maintenant nous sommes bloqués: le processus Python est endormi, attendant de finir de dire "allez", et le sous-processus est également endormi, attendant de finir de dire "Bonjour! Ne paniquez pas!".
Le subprocess.Popen
code évite ce problème avec threading-or-select / poll. Lorsque les octets peuvent traverser les tuyaux, ils disparaissent. Quand ils ne peuvent pas, seul un thread (pas tout le processus) doit dormir - ou, dans le cas de select / poll, le processus Python attend simultanément "can write" ou "data available", écrit dans le stdin du processus seulement quand il y a de la place, et lit son stdout et / ou stderr seulement lorsque les données sont prêtes. Le proc.communicate()
code (en fait _communicate
où les cas poilus sont traités) retourne une fois que toutes les données stdin (le cas échéant) ont été envoyées et que toutes les données stdout et / ou stderr ont été accumulées.
Si vous souhaitez lire les deux stdout
et stderr
sur deux canaux différents (quelle que soit la stdin
redirection), vous devrez également éviter les interblocages. Le scénario de blocage ici est différent - il se produit lorsque le sous-processus écrit quelque chose de long stderr
pendant que vous extrayez des données stdout
, ou vice versa - mais il est toujours là.
La démo
J'ai promis de démontrer que, non redirigé, Python subprocess
écrit dans le stdout sous-jacent, non sys.stdout
. Alors, voici un peu de code:
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()
Lors de l'exécution:
$ python out.py
start show1
hello
in buffer: sys.stdout being buffered
start show2
hello
Notez que la première routine échouera si vous ajoutez stdout=sys.stdout
, car un StringIO
objet n'en a pas fileno
. Le second omettra hello
si vous ajoutez stdout=sys.stdout
depuis sys.stdout
a été redirigé vers os.devnull
.
(Si vous redirigez fichier descripteur-1 Python, le sous - processus va suivre cette redirection. L' open(os.devnull, 'w')
appel produit un courant dont fileno()
est supérieur à 2.)
Popen.poll
comme dans une question précédente de Stack Overflow .