captura de stdout en tiempo real desde subproceso

Quiero subprocess.Popen() rsync.exe en Windows, e imprimir el stdout en Python.

Mi código funciona, pero no detecta el progreso hasta que se realiza una transferencia de archivos. Quiero imprimir el progreso de cada archivo en tiempo real.

El uso de Python 3.1 ahora desde que escuché debería ser mejor en el manejo de IO.

 import subprocess, time, os, sys cmd = "rsync.exe -vaz -P source/ dest/" p, line = True, 'start' p = subprocess.Popen(cmd, shell=True, bufsize=64, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE) for line in p.stdout: print(">>> " + str(line.rstrip())) p.stdout.flush() 

Algunas reglas de oro para el subprocess .

  • Nunca use shell=True . Invoca innecesariamente un proceso de shell adicional para llamar a su progtwig.
  • Al llamar a los procesos, los argumentos se pasan como listas. sys.argv en python es una lista, y también lo es argv en C. Así que le pasas una lista a Popen para que llame a los subprocesos, no a una cadena.
  • No redireccione stderr a un PIPE cuando no lo esté leyendo.
  • No redireccione la stdin cuando no esté escribiendo.

Ejemplo:

 import subprocess, time, os, sys cmd = ["rsync.exe", "-vaz", "-P", "source/" ,"dest/"] p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) for line in iter(p.stdout.readline, b''): print(">>> " + line.rstrip()) 

Dicho esto, es probable que rsync almacene en búfer su salida cuando detecta que está conectado a una tubería en lugar de a un terminal. Este es el comportamiento predeterminado: cuando están conectados a una tubería, los progtwigs deben eliminar explícitamente la salida estándar para obtener resultados en tiempo real, de lo contrario, la biblioteca C estándar se almacenará en búfer.

Para probar eso, intente ejecutar esto en su lugar:

 cmd = [sys.executable, 'test_out.py'] 

y crea un archivo test_out.py con el contenido:

 import sys import time print ("Hello") sys.stdout.flush() time.sleep(10) print ("World") 

La ejecución de ese subproceso debe darle “Hola” y esperar 10 segundos antes de dar “Mundo”. Si eso sucede con el código de python anterior y no con rsync , eso significa que rsync sí mismo es el búfer de salida, por lo que no tendrá suerte.

Una solución sería conectar directamente a una pty , usando algo como pexpect .

Sé que este es un tema antiguo, pero ahora hay una solución. Llame al rsync con la opción –outbuf = L. Ejemplo:

 cmd=['rsync', '-arzv','--backup','--outbuf=L','source/','dest'] p = subprocess.Popen(cmd, stdout=subprocess.PIPE) for line in iter(p.stdout.readline, b''): print '>>> {}'.format(line.rstrip()) 

En Linux, tuve el mismo problema de deshacerme del búfer. Finalmente utilicé “stdbuf -o0” (o, menos de lo esperado) para deshacerme del búfer PIPE.

 proc = Popen(['stdbuf', '-o0'] + cmd, stdout=PIPE, stderr=PIPE) stdout = proc.stdout 

Entonces podría usar select.select en stdout.

Consulte también https://unix.stackexchange.com/questions/25372/

 for line in p.stdout: ... 

Siempre bloquea hasta el siguiente salto de línea.

Para el comportamiento “en tiempo real” tienes que hacer algo como esto:

 while True: inchar = p.stdout.read(1) if inchar: #neither empty string nor None print(str(inchar), end='') #or end=None to flush immediately else: print('') #flush for implicit line-buffering break 

El ciclo while se deja cuando el proceso hijo cierra su salida estándar o sale. read()/read(-1) se bloquearía hasta que el proceso hijo cerrara su stdout o saliera.

Tu problema es:

 for line in p.stdout: print(">>> " + str(line.rstrip())) p.stdout.flush() 

El iterador en sí tiene un búfer adicional.

Intenta hacer así:

 while True: line = p.stdout.readline() if not line: break print line 

No puede hacer que stdout imprima sin búfer en una canalización (a menos que pueda reescribir el progtwig que imprime en stdout), así que aquí está mi solución:

Redirigir stdout a sterr, que no está en búfer. ' 1>&2' debería hacerlo. Abra el proceso de la siguiente manera: myproc = subprocess.Popen(' 1>&2', stderr=subprocess.PIPE)
No puede distinguir entre stdout o stderr, pero obtiene todos los resultados inmediatamente.

Espero que esto ayude a cualquiera a abordar este problema.

Cambie el stdout del proceso rsync para que sea sin búfer.

 p = subprocess.Popen(cmd, shell=True, bufsize=0, # 0=unbuffered, 1=line-buffered, else buffer-size stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE) 

Para evitar el almacenamiento en caché de la salida es posible que desee probar pexpect,

 child = pexpect.spawn(launchcmd,args,timeout=None) while True: try: child.expect('\n') print(child.before) except pexpect.EOF: break 

PD : Sé que esta pregunta es bastante antigua y sigue brindando la solución que funcionó para mí.

PPS : obtuve esta respuesta de otra pregunta

  p = subprocess.Popen(command, bufsize=0, universal_newlines=True) 

Estoy escribiendo una GUI para rsync en python, y tengo los mismos problemas. Este problema me ha preocupado por varios días hasta que encuentro esto en pyDoc.

Si universal_newlines es True, los objetos de archivo stdout y stderr se abren como archivos de texto en el modo de líneas nuevas universales. Las líneas pueden ser terminadas por cualquiera de ‘\ n’, la convención de fin de línea de Unix, ‘\ r’, la antigua convención de Macintosh o ‘\ r \ n’, la convención de Windows. Todas estas representaciones externas son vistas como ‘\ n’ por el progtwig Python.

Parece que rsync generará ‘\ r’ cuando la traducción esté en curso.

Según el caso de uso, es posible que también desee desactivar el almacenamiento en búfer en el propio subproceso.

Si el subproceso será un proceso de Python, puede hacer esto antes de la llamada:

 os.environ["PYTHONUNBUFFERED"] = "1" 

O alternativamente, pase esto en el argumento env a Popen .

De lo contrario, si está en Linux / Unix, puede usar la herramienta stdbuf . Por ejemplo, como

 cmd = ["stdbuf", "-oL"] + cmd 

Véase también aquí sobre stdbuf u otras opciones.

Me he dado cuenta de que no se menciona el uso de un archivo temporal como intermedio. Lo siguiente soluciona los problemas de almacenamiento en búfer al generar un archivo temporal y le permite analizar los datos provenientes de rsync sin conectarse a un pty. Probé lo siguiente en una caja de Linux, y la salida de rsync tiende a diferir entre plataformas, por lo que las expresiones regulares para analizar la salida pueden variar:

 import subprocess, time, tempfile, re pipe_output, file_name = tempfile.TemporaryFile() cmd = ["rsync", "-vaz", "-P", "/src/" ,"/dest"] p = subprocess.Popen(cmd, stdout=pipe_output, stderr=subprocess.STDOUT) while p.poll() is None: # p.poll() returns None while the program is still running # sleep for 1 second time.sleep(1) last_line = open(file_name).readlines() # it's possible that it hasn't output yet, so continue if len(last_line) == 0: continue last_line = last_line[-1] # Matching to "[bytes downloaded] number% [speed] number:number:number" match_it = re.match(".* ([0-9]*)%.* ([0-9]*:[0-9]*:[0-9]*).*", last_line) if not match_it: continue # in this case, the percentage is stored in match_it.group(1), # time in match_it.group(2). We could do something with it here... 

En Python 3, aquí hay una solución, que toma un comando de la línea de comandos y entrega cadenas decodificadas en tiempo real a medida que se reciben.

Receptor ( receiver.py ):

 import subprocess import sys cmd = sys.argv[1:] p = subprocess.Popen(cmd, stdout=subprocess.PIPE) for line in p.stdout: print("received: {}".format(line.rstrip().decode("utf-8"))) 

Ejemplo de progtwig simple que podría generar una salida en tiempo real ( dummy_out.py ):

 import time import sys for i in range(5): print("hello {}".format(i)) sys.stdout.flush() time.sleep(1) 

Salida:

 $python receiver.py python dummy_out.py received: hello 0 received: hello 1 received: hello 2 received: hello 3 received: hello 4