Comment répliquer le comportement de tee en Python lors de l'utilisation de subprocess?
je suis à la recherche d'une solution Python qui me permettra de sauvegarder la sortie d'une commande dans un fichier sans la cacher de la console.
FYI: je demande à propos de té (comme utilitaire de ligne de commande Unix) et non la fonction du même nom du module Python intertools.
Détails
- Python solution (ne pas appeler
tee
, il n'est pas disponible sous Windows) - je n'ai pas besoin de fournir d'informations sur stdin pour les appelés du processus
- Je n'ai aucun contrôle sur le programme appelé. Tout ce que je sais c'est qu'il va sortir quelque chose à stdout et stderr et revenir avec un code de sortie.
- lors de l'appel de programmes externes (sous-processus)
- De
stderr
etstdout
- être capable de faire la différence entre stdout et stderr parce que je veux peut - être afficher un seul de la console ou je pourrais essayer de sortir stderr en utilisant une couleur différente- ceci signifie que
stderr = subprocess.STDOUT
ne fonctionne pas. - sortie Live (progressive) - le processus peut fonctionner pendant une longue période, Et je ne suis pas en mesure d'attendre qu'il se termine.
- code compatible Python 3 (important)
Références
voici quelques solutions incomplètes que j'ai trouvées jusqu'à présent:
- http://devlishgenius.blogspot.com/2008/10/logging-in-real-time-in-python.html (mkfifo ne fonctionne que sur les Unix)
- http://blog.kagesenshi.org/2008/02/teeing-python-subprocesspopen-output.html (ne fonctionne pas)
Diagramme http://blog.i18n.ro/wp-content/uploads/2010/06/Drawing_tee_py.png
code Actuel (deuxième essai)
#!/usr/bin/python
from __future__ import print_function
import sys, os, time, subprocess, io, threading
cmd = "python -E test_output.py"
from threading import Thread
class StreamThread ( Thread ):
def __init__(self, buffer):
Thread.__init__(self)
self.buffer = buffer
def run ( self ):
while 1:
line = self.buffer.readline()
print(line,end="")
sys.stdout.flush()
if line == '':
break
proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdoutThread = StreamThread(io.TextIOWrapper(proc.stdout))
stderrThread = StreamThread(io.TextIOWrapper(proc.stderr))
stdoutThread.start()
stderrThread.start()
proc.communicate()
stdoutThread.join()
stderrThread.join()
print("--done--")
#### test_output.py ####
#!/usr/bin/python
from __future__ import print_function
import sys, os, time
for i in range(0, 10):
if i%2:
print("stderr %s" % i, file=sys.stderr)
else:
print("stdout %s" % i, file=sys.stdout)
time.sleep(0.1)
La production réelle
stderr 1
stdout 0
stderr 3
stdout 2
stderr 5
stdout 4
stderr 7
stdout 6
stderr 9
stdout 8
--done--
L'extrant prévu était d'avoir les lignes ordonnées. Remarque, modifier le Popen pour utiliser un seul tuyau n'est pas autorisé parce que dans la vraie vie je vais envie de faire des choses différentes avec stderr et stdout.
même dans le second cas, je n'ai pas été capable d'obtenir des résultats en temps réel comme dehors, en fait tous les résultats ont été reçus à la fin du processus. Par défaut, Popen ne doit pas utiliser de tampon (bufsize=0).
7 réponses
je vois que c'est un vieux post, mais juste au cas où quelqu'un est toujours à la recherche d'un moyen de faire ceci:
proc = subprocess.Popen(["ping", "localhost"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
with open("logfile.txt", "w") as log_file:
while proc.poll() is None:
line = proc.stderr.readline()
if line:
print "err: " + line.strip()
log_file.write(line)
line = proc.stdout.readline()
if line:
print "out: " + line.strip()
log_file.write(line)
finalement j'ai dû mettre en oeuvre tee()
commande en Python moi-même.
Vous pouvez l'obtenir à partir d'ici http://github.com/pycontribs/tendo/blob/master/tendo/tee.py
Actuellement, il vous permet de faire des choses comme:
tee("python --v") # works just like os.system()
tee("python --v", "log.txt") # file names
tee("python --v", file_handle)
import logging
tee("python --v", logging.info) # receive a method
La seule limitation est qu'il n'est pas capable de différencier entre stderr
et stdout
, ce qui signifie qu'il va fusionner les deux.
ceci est un port simple de tee
à Python.
import sys
sinks = sys.argv[1:]
sinks = [open(sink, "w") for sink in sinks]
sinks.append(sys.stderr)
while True:
input = sys.stdin.read(1024)
if input:
for sink in sinks:
sink.write(input)
else:
break
J'utilise Linux en ce moment, mais cela devrait fonctionner sur la plupart des plateformes.
Maintenant, pour le subprocess
partie, je ne sais pas comment vous voulez "fil de fer" le sous-processus stdin
,stdout
et stderr
pour votre stdin
,stdout
,stderr
et le fichier de lavabos, mais je sais que vous pouvez faire ceci:
import subprocess
callee = subprocess.Popen( ["python", "-i"],
stdin = subprocess.PIPE,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE
)
Maintenant vous pouvez accéder callee.stdin
, callee.stdout
et callee.stderr
comme les fichiers normaux, permettant à la "solution" ci-dessus de fonctionner. Si vous voulez obtenir le callee.returncode
, vous aurez besoin de faire un appel supplémentaire à callee.poll()
.
soyez prudent avec l'écriture de callee.stdin
: si le processus s'est terminé quand vous faites cela, une erreur peut être monté (sur Linux, j'obtiens IOError: [Errno 32] Broken pipe
).
si vous ne voulez pas interagir avec le processus, vous pouvez utiliser le module subprocess très bien.
Exemple:
tester.py
import os
import sys
for file in os.listdir('.'):
print file
sys.stderr.write("Oh noes, a shrubbery!")
sys.stderr.flush()
sys.stderr.close()
testing.py
import subprocess
p = subprocess.Popen(['python', 'tester.py'], stdout=subprocess.PIPE,
stdin=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
print stdout, stderr
Dans votre situation, vous pouvez simplement écrire stdout/stderr dans un fichier en premier. Vous pouvez envoyer des arguments à votre processus avec communicate aussi bien, bien que je n'ai pas été en mesure de comprendre comment interagir continuellement avec le sous-processus.
il y a des problèmes/bogues subtils en python liés au sous-processus.TUYAU: http://bugs.python.org/issue1652
apparemment ceci a été corrigé dans python3+, mais pas dans python 2.7 et plus ancien. Pour cela, vous devez utiliser: code.google.com/p/python-subprocess32/
essaye ceci :
import sys
class tee-function :
def __init__(self, _var1, _var2) :
self.var1 = _var1
self.var2 = _var2
def __del__(self) :
if self.var1 != sys.stdout and self.var1 != sys.stderr :
self.var1.close()
if self.var2 != sys.stdout and self.var2 != sys.stderr :
self.var2.close()
def write(self, text) :
self.var1.write(text)
self.var2.write(text)
def flush(self) :
self.var1.flush()
self.var2.flush()
stderrsav = sys.stderr
out = open(log, "w")
sys.stderr = tee-function(stderrsav, out)
j'ai écrit une chose qui enveloppe les commandes shell en Python.
avantages:
- C'util capture stdout/stderr toujours
- C'util fournit une option pour echo stdout/stderr vers stdout/stderr pour le processus
- lorsque l'écho stdout/stderr Le out/err il n'y a pas de délai
inconvénient:
- fonctionne seulement sur bash / unix
source: https://gist.github.com/AndrewHoos/9f03c74988469b517a7a