example español python subprocess stdout stderr

español - Python lee desde el subproceso stdout y stderr por separado mientras conserva el orden



subprocess python 3 example (7)

Tengo un subproceso de Python del que estoy tratando de leer la salida y las secuencias de error. Actualmente lo tengo funcionando, pero solo puedo leer desde stderr después de haber terminado de leer desde stdout . Así es como se ve:

process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout_iterator = iter(process.stdout.readline, b"") stderr_iterator = iter(process.stderr.readline, b"") for line in stdout_iterator: # Do stuff with line print line for line in stderr_iterator: # Do stuff with line print line

Como puede ver, el bucle stderr for no puede comenzar hasta que se complete el bucle stdout . ¿Cómo puedo modificar esto para poder leer de ambos en el orden correcto en que aparecen las líneas?

Para aclarar: todavía necesito saber si una línea vino de stdout o stderr porque se tratarán de manera diferente en mi código.


Aquí hay una solución basada en selectors , pero que conserva el orden y transmite caracteres de longitud variable (incluso caracteres únicos).

El truco es usar read1() , en lugar de read() .

import selectors import subprocess import sys p = subprocess.Popen( "python random_out.py", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) sel = selectors.DefaultSelector() sel.register(p.stdout, selectors.EVENT_READ) sel.register(p.stderr, selectors.EVENT_READ) while True: for key, _ in sel.select(): data = key.fileobj.read1().decode() if not data: exit() if key.fileobj is p.stdout: print(data, end="") else: print(data, end="", file=sys.stderr)

Si desea un programa de prueba, use esto.

import sys from time import sleep for i in range(10): print(f" x{i} ", file=sys.stderr, end="") sleep(0.1) print(f" y{i} ", end="") sleep(0.1)


El código en su pregunta puede llegar a un punto muerto si el proceso hijo produce suficiente salida en stderr (~ 100 KB en mi máquina Linux).

Hay un método de communicate() que permite leer tanto desde stdout como desde stderr por separado:

from subprocess import Popen, PIPE process = Popen(command, stdout=PIPE, stderr=PIPE) output, err = process.communicate()

Si necesita leer las transmisiones mientras el proceso secundario aún se está ejecutando, la solución portátil es usar hilos (no probados):

from subprocess import Popen, PIPE from threading import Thread from Queue import Queue # Python 2 def reader(pipe, queue): try: with pipe: for line in iter(pipe.readline, b''''): queue.put((pipe, line)) finally: queue.put(None) process = Popen(command, stdout=PIPE, stderr=PIPE, bufsize=1) q = Queue() Thread(target=reader, args=[process.stdout, q]).start() Thread(target=reader, args=[process.stderr, q]).start() for _ in range(2): for source, line in iter(q.get, None): print "%s: %s" % (source, line),

Ver:

  • Python: lea la entrada de transmisión de subprocess.communicate ()
  • Lectura sin bloqueo en un subproceso.PIPE en python
  • ¿El subproceso de Python obtiene la salida de los niños al archivo y al terminal?

El orden en que un proceso escribe datos en diferentes tuberías se pierde después de la escritura.

No hay forma de saber si stdout se ha escrito antes de stderr.

Puede intentar leer los datos simultáneamente de varios descriptores de archivos de manera no bloqueadora tan pronto como los datos estén disponibles, pero esto solo minimizaría la probabilidad de que el pedido sea incorrecto.

Este programa debería demostrar esto:

#!/usr/bin/env python # -*- coding: utf-8 -*- import os import select import subprocess testapps={ ''slow'': '''''' import os import time os.write(1, ''aaa'') time.sleep(0.01) os.write(2, ''bbb'') time.sleep(0.01) os.write(1, ''ccc'') '''''', ''fast'': '''''' import os os.write(1, ''aaa'') os.write(2, ''bbb'') os.write(1, ''ccc'') '''''', ''fast2'': '''''' import os os.write(1, ''aaa'') os.write(2, ''bbbbbbbbbbbbbbb'') os.write(1, ''ccc'') '''''' } def readfds(fds, maxread): while True: fdsin, _, _ = select.select(fds,[],[]) for fd in fdsin: s = os.read(fd, maxread) if len(s) == 0: fds.remove(fd) continue yield fd, s if fds == []: break def readfromapp(app, rounds=10, maxread=1024): f=open(''testapp.py'', ''w'') f.write(testapps[app]) f.close() results={} for i in range(0, rounds): p = subprocess.Popen([''python'', ''testapp.py''], stdout=subprocess.PIPE , stderr=subprocess.PIPE) data='''' for (fd, s) in readfds([p.stdout.fileno(), p.stderr.fileno()], maxread): data = data + s results[data] = results[data] + 1 if data in results else 1 print ''running %i rounds %s with maxread=%i'' % (rounds, app, maxread) results = sorted(results.items(), key=lambda (k,v): k, reverse=False) for data, count in results: print ''%03i x %s'' % (count, data) print print "=> if output is produced slowly this should work as whished" print " and should return: aaabbbccc" readfromapp(''slow'', rounds=100, maxread=1024) print print "=> now mostly aaacccbbb is returnd, not as it should be" readfromapp(''fast'', rounds=100, maxread=1024) print print "=> you could try to read data one by one, and return" print " e.g. a whole line only when LF is read" print " (b''s should be finished before c''s)" readfromapp(''fast'', rounds=100, maxread=1) print print "=> but even this won''t work ..." readfromapp(''fast2'', rounds=100, maxread=1)

y genera algo como esto:

=> if output is produced slowly this should work as whished and should return: aaabbbccc running 100 rounds slow with maxread=1024 100 x aaabbbccc => now mostly aaacccbbb is returnd, not as it should be running 100 rounds fast with maxread=1024 006 x aaabbbccc 094 x aaacccbbb => you could try to read data one by one, and return e.g. a whole line only when LF is read (b''s should be finished before c''s) running 100 rounds fast with maxread=1 003 x aaabbbccc 003 x aababcbcc 094 x abababccc => but even this won''t work ... running 100 rounds fast2 with maxread=1 003 x aaabbbbbbbbbbbbbbbccc 001 x aaacbcbcbbbbbbbbbbbbb 008 x aababcbcbcbbbbbbbbbbb 088 x abababcbcbcbbbbbbbbbb


Escribí algo para hacer esto hace mucho tiempo . Todavía no lo he portado a Python 3, pero no debería ser demasiado difícil (¡se aceptan parches!)

Si lo ejecuta de forma independiente, verá muchas de las diferentes opciones. En cualquier caso, le permite distinguir stdout de stderr.


Esto funciona para mí (en Windows): https://github.com/waszil/subpiper

from subpiper import subpiper def my_stdout_callback(line: str): print(f''STDOUT: {line}'') def my_stderr_callback(line: str): print(f''STDERR: {line}'') my_additional_path_list = [r''c:/important_location''] retcode = subpiper(cmd=''echo magic'', stdout_callback=my_stdout_callback, stderr_callback=my_stderr_callback, add_path_list=my_additional_path_list)


Sé que esta pregunta es muy antigua, pero esta respuesta puede ayudar a otros que se topan con esta página para buscar una solución para una situación similar, así que la estoy publicando de todos modos.

He creado un fragmento de python simple que fusionará cualquier cantidad de tuberías en una sola. Por supuesto, como se indicó anteriormente, el orden no se puede garantizar, pero esto es lo más cercano que creo que puede obtener en Python.

Genera un hilo para cada una de las tuberías, las lee línea por línea y las coloca en una Cola (que es FIFO). El hilo principal recorre la cola, produciendo cada línea.

import threading, queue def merge_pipes(**named_pipes): r'''''' Merges multiple pipes from subprocess.Popen (maybe other sources as well). The keyword argument keys will be used in the output to identify the source of the line. Example: p = subprocess.Popen([''some'', ''call''], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) outputs = {''out'': log.info, ''err'': log.warn} for name, line in merge_pipes(out=p.stdout, err=p.stderr): outputs[name](line) This will output stdout to the info logger, and stderr to the warning logger '''''' # Constants. Could also be placed outside of the method. I just put them here # so the method is fully self-contained PIPE_OPENED=1 PIPE_OUTPUT=2 PIPE_CLOSED=3 # Create a queue where the pipes will be read into output = queue.Queue() # This method is the run body for the threads that are instatiated below # This could be easily rewritten to be outside of the merge_pipes method, # but to make it fully self-contained I put it here def pipe_reader(name, pipe): r""" reads a single pipe into the queue """ output.put( ( PIPE_OPENED, name, ) ) try: for line in iter(pipe.readline,''''): output.put( ( PIPE_OUTPUT, name, line.rstrip(), ) ) finally: output.put( ( PIPE_CLOSED, name, ) ) # Start a reader for each pipe for name, pipe in named_pipes.items(): t=threading.Thread(target=pipe_reader, args=(name, pipe, )) t.daemon = True t.start() # Use a counter to determine how many pipes are left open. # If all are closed, we can return pipe_count = 0 # Read the queue in order, blocking if there''s no data for data in iter(output.get,''''): code=data[0] if code == PIPE_OPENED: pipe_count += 1 elif code == PIPE_CLOSED: pipe_count -= 1 elif code == PIPE_OUTPUT: yield data[1:] if pipe_count == 0: return


Según el documento de Python

Popen.stdout Si el argumento stdout era PIPE, este atributo es un objeto de archivo que proporciona resultados del proceso secundario. De lo contrario, es Ninguno.

Popen.stderr Si el argumento stderr era PIPE, este atributo es un objeto de archivo que proporciona una salida de error del proceso secundario. De lo contrario, es Ninguno.

Debajo de la muestra puedes hacer lo que quieras

prueba.py

print "I''m stdout" raise Exception("I''m Error")

impresora.py

import subprocess p = subprocess.Popen([''python'', ''test.py''], stdout=subprocess.PIPE, stderr=subprocess.PIPE) print "Normal" std_lines = p.stdout.readlines() for line in std_lines: print line.rstrip() print "Error" stderr_lines = p.stderr.readlines() for line in stderr_lines: print line.rstrip()

Salida:

Normal I''m stdout Error Traceback (most recent call last): File "test.py", line 3, in <module> raise Exception("I''m Error") Exception: I''m Error