J'utilise un script python comme pilote pour un code hydrodynamique. Quand vient le temps d'exécuter la simulation, j'utilise subprocess.Popen
pour exécuter le code, collecter la sortie de stdout et stderr dans un subprocess.PIPE
--- puis je peux imprimer (et enregistrer dans un fichier journal) les informations de sortie et vérifier les erreurs. . Le problème est que je n'ai aucune idée de la progression du code. Si je l'exécute directement à partir de la ligne de commande, cela me donne une sortie sur l'itération, à quelle heure, quelle est la prochaine étape de temps, etc.
Existe-t-il un moyen à la fois de stocker la sortie (pour la journalisation et la vérification des erreurs) et de produire également une sortie en direct?
La section pertinente de mon code:
ret_val = subprocess.Popen( run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True )
output, errors = ret_val.communicate()
log_file.write(output)
print output
if( ret_val.returncode ):
print "RUN failed\n\n%s\n\n" % (errors)
success = False
if( errors ): log_file.write("\n\n%s\n\n" % errors)
À l'origine, je diffusais le run_command
grâce tee
afin qu'une copie est allé directement au fichier journal, et le flux de sortie encore directement à la borne - mais cette façon , je ne peux pas stocker des erreurs (à mon knowlege).
Éditer:
Solution temporaire:
ret_val = subprocess.Popen( run_command, stdout=log_file, stderr=subprocess.PIPE, shell=True )
while not ret_val.poll():
log_file.flush()
puis, dans un autre terminal, exécutez tail -f log.txt
(st log_file = 'log.txt'
).
la source
Popen.poll
comme dans une question précédente de Stack Overflow .git
) ne le font que si leur sortie est un "périphérique tty" (testé via libcisatty()
). Dans ce cas, vous devrez peut-être ouvrir un pseudo-tty.flush
, et il est nécessaire de lire à partir du tube stderr si le sous-processus produit beaucoup de sortie stderr. Il n'y a pas assez de place dans un champ de commentaire pour expliquer cela ...Réponses:
Vous avez deux façons de faire cela, soit en créant un itérateur à partir des fonctions
read
oureadline
et faites:ou
Ou vous pouvez créer
reader
unwriter
fichier et un fichier. Passez lewriter
auPopen
et lisez lereader
De cette façon, vous aurez les données écrites dans le
test.log
ainsi que sur la sortie standard.Le seul avantage de l'approche fichier est que votre code ne se bloque pas. Ainsi, vous pouvez faire ce que vous voulez en attendant et lire quand vous le souhaitez de
reader
manière non bloquante. Lorsque vous utilisezPIPE
,read
et lesreadline
fonctions se bloquent jusqu'à ce qu'un caractère soit écrit dans le tuyau ou qu'une ligne soit écrite dans le tuyau respectivement.la source
iter(process.stdout.readline, b'')
(c'est-à-dire que la sentinelle transmise à iter doit être une chaîne binaire, puisqueb'' != ''
.for line in iter(process.stdout.readline, b''): sys.stdout.buffer.write(line)
process = subprocess.Popen(command, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) for line in iter(process.stdout.readline, b'') sys.stdout.write(line.decode(sys.stdout.encoding))
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ésstdin
,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 Pythonsys.stdout
, juste le stdout réel de Python ; voir la démo à la fin.int
valeur. Il s'agit d'un descripteur de fichier "brut" (au moins dans POSIX). (Note latérale:PIPE
etSTDOUT
sont en faitint
s en interne, mais sont des descripteurs "impossibles", -1 et -2.)fileno
méthode.Popen
trouvera le descripteur de ce flux en utilisantstream.fileno()
, puis procédera comme pour uneint
valeur.subprocess.PIPE
, indiquant que Python doit créer un tube.subprocess.STDOUT
(pourstderr
seulement): dites à Python d'utiliser le même descripteur que pourstdout
. Cela n'a de sens que si vous avez fourni uneNone
valeur (non- ) pourstdout
, et même dans ce cas, elle n'est nécessaire que si vous définissezstdout=subprocess.PIPE
. (Sinon, vous pouvez simplement fournir le même argument que vous avez fournistdout
, par exemplePopen(..., 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 expliciteNone
),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 nonPIPE
-int
un flux ou un fluxfileno()
- 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 laisserstdout
etstderr
aller non redirigé, ou aller à un descripteur de fichier. En tant que processus parent, votre programme Python doit simplement l'utiliserwrite()
pour envoyer des données dans le tube. Vous pouvez le faire vous-même, par exemple:ou vous pouvez transmettre les données stdin à
proc.communicate()
, qui effectue ensuite les opérationsstdin.write
ci-dessus. Il n'y a pas de sortie qui revient donccommunicate()
n'a qu'un seul autre vrai travail: il ferme également le tuyau pour vous. (Si vous n'appelez pas,proc.communicate()
vous devez appelerproc.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 partirstdin
etstderr
seul. Encore une fois, c'est facile: il suffit d'appelerproc.stdout.read()
(ou équivalent) jusqu'à ce qu'il n'y ait plus de sortie. Puisqu'ilproc.stdout()
s'agit d'un flux d'E / S Python normal, vous pouvez utiliser toutes les constructions normales dessus, comme:ou, encore une fois, vous pouvez utiliser
proc.communicate()
, ce qui fait simplement leread()
pour vous.Si vous souhaitez capturer uniquement
stderr
, cela fonctionne de la même manière qu'avecstdout
.Il y a encore une astuce avant que les choses ne deviennent difficiles. Supposons que vous souhaitiez capturer
stdout
, et également capturerstderr
mais sur le même tube que 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 dansproc.stdout
, et si vous appelezproc.communicate()
, le résultat stderr (deuxième valeur dans le tuple) seraNone
, 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: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 pourself.stdout
etself.stderr
, et le thread parent fournitself.stdin
les données d'entrée (puis ferme le canal).Sur POSIX, cela utilise
poll
si disponible, sinonselect
, 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'
versproc.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 lee
suspend, en attendant que son parent lise celaH
, 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. Leproc.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
etstderr
sur deux canaux différents (quelle que soit lastdin
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 longstderr
pendant que vous extrayez des donnéesstdout
, 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, nonsys.stdout
. Alors, voici un peu de code:Lors de l'exécution:
Notez que la première routine échouera si vous ajoutez
stdout=sys.stdout
, car unStringIO
objet n'en a pasfileno
. Le second omettrahello
si vous ajoutezstdout=sys.stdout
depuissys.stdout
a été redirigé versos.devnull
.(Si vous redirigez fichier descripteur-1 Python, le sous - processus va suivre cette redirection. L'
open(os.devnull, 'w')
appel produit un courant dontfileno()
est supérieur à 2.)la source
sys.stdout
) va vers le stdout de Python , pas vers le stdout du programme python (sys.
). Ce que j'admets est une ... distinction étrange. Y a-t-il une meilleure façon d'exprimer cela?asyncio
code qui implémente la "partie dure" (il gère plusieurs tubes simultanément) de manière portable . Vous pouvez le comparer au code qui utilise plusieurs threads (teed_call()
) pour faire de même .Nous pouvons également utiliser l'itérateur de fichier par défaut pour lire stdout au lieu d'utiliser la construction iter avec readline ().
la source
Si vous êtes en mesure d'utiliser des bibliothèques tierces, vous pourrez peut-être utiliser quelque chose comme
sarge
(divulgation: je suis son mainteneur). Cette bibliothèque permet un accès non bloquant aux flux de sortie des sous-processus - elle est superposée ausubprocess
module.la source
Solution 1: Connectez
stdout
ETstderr
simultanément en temps réelUne solution simple qui enregistre simultanément stdout ET stderr, ligne par ligne en temps réel dans un fichier journal.
Solution 2: Une fonction
read_popen_pipes()
qui vous permet d'itérer sur les deux tubes (stdout / stderr), simultanément en temps réella source
Une bonne solution, mais «lourde», consiste à utiliser Twisted - voir en bas.
Si vous êtes prêt à vivre avec seulement stdout, quelque chose de ce genre devrait fonctionner:
(Si vous utilisez read (), il essaie de lire le "fichier" entier, ce qui n'est pas utile, ce que nous pourrions vraiment utiliser ici, c'est quelque chose qui lit toutes les données qui se trouvent dans le tube en ce moment)
On pourrait également essayer d'aborder cela avec des threads, par exemple:
Maintenant, nous pourrions également ajouter stderr en ayant deux threads.
Notez cependant que la documentation de sous-processus décourage l'utilisation de ces fichiers directement et recommande d'utiliser
communicate()
(principalement concerné par les blocages qui, je pense, ne sont pas un problème ci-dessus) et les solutions sont un peu klunky donc il semble vraiment que le module de sous-processus n'est pas tout à fait à la hauteur le travail (voir aussi: http://www.python.org/dev/peps/pep-3145/ ) et nous devons regarder autre chose.Une solution plus complexe consiste à utiliser Twisted comme indiqué ici: https://twistedmatrix.com/documents/11.1.0/core/howto/process.html
La façon dont vous faites cela avec Twisted consiste à créer votre processus en utilisant
reactor.spawnprocess()
et en fournissant unProcessProtocol
qui traite ensuite la sortie de manière asynchrone. L'exemple de code Python Twisted est ici: https://twistedmatrix.com/documents/11.1.0/core/howto/listings/process/process.pyla source
read()
appel jusqu'à ce que le sous-processus se termine et que le processus parent reçoiveEOF
sur le tube.En plus de toutes ces réponses, une approche simple pourrait également être la suivante:
Faites une boucle dans le flux lisible tant qu'il est lisible et s'il obtient un résultat vide, arrêtez-le.
La clé ici est que
readline()
renvoie une ligne (avec\n
à la fin) tant qu'il y a une sortie et vide si c'est vraiment à la fin.J'espère que cela aide quelqu'un.
la source
Sur la base de tout ce qui précède, je suggère une version légèrement modifiée (python3):
None
Code:
la source
returncode
partie était cruciale dans mon cas.Il semble que la sortie tamponnée en ligne fonctionnera pour vous, auquel cas quelque chose comme ce qui suit pourrait convenir. (Attention: il n'a pas été testé.) Cela ne donnera la sortie standard du sous-processus qu'en temps réel. Si vous voulez avoir à la fois stderr et stdout en temps réel, vous devrez faire quelque chose de plus complexe avec
select
.la source
Pourquoi ne pas définir
stdout
directement sursys.stdout
? Et si vous avez également besoin de générer une sortie dans un journal, vous pouvez simplement remplacer la méthode d'écriture de f.la source
stdout
descripteur de fichier sur le descripteur de fichier de l'objet fichier transmis. La méthode write ne serait jamais appelée (du moins c'est ce que fait le sous-processus pour stderr, je suppose que c'est la même chose pour stdout).Toutes les solutions ci-dessus que j'ai essayées n'ont pas réussi à séparer la sortie stderr et stdout, (plusieurs tubes) ou bloquées à jamais lorsque le tampon du tube du système d'exploitation était plein, ce qui se produit lorsque la commande que vous exécutez sort trop rapidement (il y a un avertissement à ce sujet sur python poll () manuel du sous-processus). Le seul moyen fiable que j'ai trouvé était de sélectionner, mais c'est une solution uniquement posix:
la source
Similaire aux réponses précédentes, mais la solution suivante a fonctionné pour moi sur Windows utilisant Python3 pour fournir une méthode commune pour imprimer et se connecter en temps réel ( getting-realtime-output-using-python ):
la source
Je pense que la
subprocess.communicate
méthode est un peu trompeuse: elle remplit en fait les stdout et stderr que vous spécifiez dans le fichiersubprocess.Popen
.Pourtant, la lecture de la
subprocess.PIPE
que vous pouvez fournir auxsubprocess.Popen
s » stdout et stderr finira par remplir les paramètres des tampons de tuyaux OS et interblocage votre application (surtout si vous avez plusieurs processus / threads qui doivent utilisersubprocess
).Ma solution proposée est de fournir les fichiers stdout et stderr - et de lire le contenu des fichiers au lieu de lire depuis le blocage
PIPE
. Ces fichiers peuvent êtretempfile.NamedTemporaryFile()
- qui peuvent également être consultés pour la lecture pendant leur écriture parsubprocess.communicate
.Voici un exemple d'utilisation:
Et voici le code source qui est prêt à être utilisé avec autant de commentaires que je pourrais fournir pour expliquer ce qu'il fait:
Si vous utilisez python 2, s'il vous plaît assurez - vous d' abord installer la dernière version du subprocess32 paquet de pypi.
la source
Voici une classe que j'utilise dans l'un de mes projets. Il redirige la sortie d'un sous-processus vers le journal. Au début, j'ai essayé d'écraser simplement la méthode d'écriture, mais cela ne fonctionne pas car le sous-processus ne l'appellera jamais (la redirection se produit au niveau du descripteur de fichier). J'utilise donc mon propre tube, de la même manière que dans le sous-processus-module. Cela présente l'avantage d'encapsuler toute la logique de journalisation / d'impression dans l'adaptateur et vous pouvez simplement transmettre des instances de l'enregistreur à
Popen
:subprocess.Popen("/path/to/binary", stderr = LogAdapter("foo"))
Si vous n'avez pas besoin de journalisation mais que
print()
vous souhaitez simplement l'utiliser, vous pouvez évidemment supprimer de grandes parties du code et réduire la classe. Vous pouvez également étendre par une__enter__
et__exit__
méthode et appelerfinished
en__exit__
sorte que vous pouvez facilement l' utiliser comme contexte.la source
Aucune des solutions pythoniques n'a fonctionné pour moi. Il s'est avéré que
proc.stdout.read()
ou similaire peut bloquer pour toujours.Par conséquent, j'utilise
tee
comme ceci:Cette solution est pratique si vous utilisez déjà
shell=True
.${PIPESTATUS}
capture l'état de réussite de l'ensemble de la chaîne de commande (uniquement disponible dans Bash). Si j'omettais le&& exit ${PIPESTATUS}
, alors cela renverrait toujours zéro puisquetee
jamais échoue.unbuffer
peut être nécessaire pour imprimer chaque ligne immédiatement dans le terminal, au lieu d'attendre trop longtemps jusqu'à ce que le "tampon de tube" soit rempli. Cependant, le débuffeur avale le statut de sortie d'assert (SIG Abort) ...2>&1
enregistre également stderror dans le fichier.la source