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 Popenfonction doit traiter de zéro à trois flux d'E / S, un peu simultanément. Ceux - ci sont indiqués stdin, stdoutetstderr 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
intvaleur. Il s'agit d'un descripteur de fichier "brut" (au moins dans POSIX). (Note latérale: PIPEet STDOUTsont en fait ints en interne, mais sont des descripteurs "impossibles", -1 et -2.)
- Un flux - vraiment, n'importe quel objet avec une
filenométhode. Popentrouvera le descripteur de ce flux en utilisant stream.fileno(), puis procédera comme pour une intvaleur.
subprocess.PIPE, indiquant que Python doit créer un tube.
subprocess.STDOUT(pour stderrseulement): dites à Python d'utiliser le même descripteur que pour stdout. Cela n'a de sens que si vous avez fourni une Nonevaleur (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 Nonevaleur par défaut ou fournissez explicite None), Pipec'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- intun 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 Pipechoses restent assez faciles. Choisissons un flux à la fois et regardons.
Supposons que vous voulez fournir quelques - uns stdin, mais laisser stdoutet stderraller 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.writeci-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 stdoutmais partir stdinet stderrseul. 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 stderrmais 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 subprocesscode 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.Threadd'accumuler les résultats pour self.stdoutet self.stderr, et le thread parent fournit self.stdinles données d'entrée (puis ferme le canal).
Sur POSIX, cela utilise pollsi 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 Hva dans son tube stdout, mais le esuspend, 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.Popencode é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 _communicateoù 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 stdoutet stderrsur deux canaux différents (quelle que soit la stdinredirection), 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 stderrpendant 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 StringIOobjet n'en a pas fileno. Le second omettra hellosi vous ajoutez stdout=sys.stdoutdepuis sys.stdouta é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.pollcomme dans une question précédente de Stack Overflow .