tamaño - ¿Cómo replicar el comportamiento de tee en Python cuando se usa un subproceso?
stringvar tkinter python 3 (7)
Escribí una cosa que envuelve los comandos de shell en Python.
Principales ventajas:
- Este util captura siempre stdout / stderr
- Esta utilidad proporciona una opción para repetir stdout / stderr a stdout / stderr para el proceso
- Cuando se hace eco de stdout / stderr out / err no hay retraso
Clave desventaja:
- Solo funciona en bash / unix
fuente: https://gist.github.com/AndrewHoos/9f03c74988469b517a7a
Estoy buscando una solución Python que me permita guardar la salida de un comando en un archivo sin ocultarlo de la consola.
FYI: estoy preguntando acerca de tee (como la utilidad de línea de comando de Unix) y no la función con el mismo nombre del módulo de herramientas de Python
Detalles
- Solución Python (no llama a
tee
, no está disponible en Windows) - No necesito proporcionar ninguna entrada a stdin para el proceso llamado
- No tengo control sobre el programa llamado. Todo lo que sé es que dará salida a stdout y stderr y regresará con un código de salida.
- Para trabajar al llamar a programas externos (subproceso)
- Trabajar tanto para
stderr
como parastdout
- Poder diferenciar entre stdout y stderr porque es posible que desee mostrar solo uno de los a la consola o podría intentar generar stderr con un color diferente, lo que significa que
stderr = subprocess.STDOUT
no funcionará. - Salida en vivo (progresiva): el proceso puede ejecutarse durante mucho tiempo y no puedo esperar a que finalice.
- Código compatible con Python 3 (importante)
Referencias
Aquí hay algunas soluciones incompletas que encontré hasta ahora:
- http://devlishgenius.blogspot.com/2008/10/logging-in-real-time-in-python.html (mkfifo solo funciona en Unix)
- http://blog.kagesenshi.org/2008/02/teeing-python-subprocesspopen-output.html (no funciona en absoluto)
Diagrama http://blog.i18n.ro/wp-content/uploads/2010/06/Drawing_tee_py.png
Código actual (segundo intento)
#!/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)
Salida real
stderr 1
stdout 0
stderr 3
stdout 2
stderr 5
stdout 4
stderr 7
stdout 6
stderr 9
stdout 8
--done--
La salida esperada era tener las líneas ordenadas. Nota: no está permitido modificar el Popen para usar solo un PIPE porque en la vida real querré hacer cosas diferentes con stderr y stdout.
Además, incluso en el segundo caso no pude obtener en tiempo real, de hecho, todos los resultados se recibieron cuando finalizó el proceso. Por defecto, Popen no debe usar buffers (bufsize = 0).
Este es un puerto directo de tee
a 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
Estoy ejecutando Linux ahora mismo, pero esto debería funcionar en la mayoría de las plataformas.
Ahora, para la parte del subprocess
, no sé cómo desea "conectar" los stdin
, stdout
y stderr
del subproceso a sus stdin
, stdout
, stderr
y archivos de receptores, pero sé que puede hacer esto:
import subprocess
callee = subprocess.Popen( ["python", "-i"],
stdin = subprocess.PIPE,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE
)
Ahora puede acceder a los archivos normales callee.stdin
, callee.stdout
y callee.stderr
, permitiendo que la "solución" anterior funcione. Si desea obtener el callee.returncode
, deberá realizar una llamada adicional a callee.poll()
.
Tenga cuidado al escribir a callee.stdin
: si el proceso ha finalizado al hacerlo, puede haber un error (en Linux, me sale el error IOError: [Errno 32] Broken pipe
).
Finalmente tuve que implementar el comando tee()
en Python.
Puede obtenerlo aquí http://github.com/pycontribs/tendo/blob/master/tendo/tee.py
Actualmente te permite hacer cosas como:
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 única limitación actual es que no es capaz de diferenciar entre stderr
y stdout
, lo que significa que fusionará ambos.
Hay problemas / errores sutiles en Python relacionados con el subproceso. PIPE: http://bugs.python.org/issue1652
Aparentemente, esto se solucionó en python3 +, pero no en python 2.7 y anteriores. Para eso necesitas usar: code.google.com/p/python-subprocess32/
Prueba esto :
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)
Si no desea interactuar con el proceso, puede utilizar el módulo de subproceso sin problemas.
Ejemplo:
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
En su situación, puede escribir primero stdout / stderr en un archivo. También puede enviar argumentos a su proceso para comunicarse, aunque no pude averiguar cómo interactuar continuamente con el subproceso.
Veo que esta es una publicación bastante antigua, pero en caso de que alguien todavía esté buscando una manera de hacer esto:
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)