web-dev-qa-db-fra.com

lire ligne par ligne du sous-processus stdout

Mon script python utilise un sous-processus pour appeler un utilitaire Linux très bruyant. Je souhaite stocker toutes les sorties dans un fichier journal et en montrer une partie à l'utilisateur. Je pensais que ce qui suit fonctionnerait, mais la sortie ne s'affiche pas dans mon application tant que l'utilitaire n'en a pas généré une quantité significative.

#fake_utility.py, just generates lots of output over time
import time
i = 0
while True:
   print hex(i)*512
   i += 1
   time.sleep(0.5)

#filters output
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
for line in proc.stdout:
   #the real code does filtering here
   print "test:", line.rstrip()

Le comportement que je souhaite vraiment est que le script de filtrage imprime chaque ligne telle qu'elle est reçue du sous-processus. Un peu comme ce que tee fait mais avec du code python.

Qu'est-ce que je rate? Est-ce seulement possible?


Mettre à jour:

Si une sys.stdout.flush() est ajoutée à fake_utility.py, le code a le comportement souhaité dans Python 3.1. J'utilise Python 2.6. Vous penseriez que l'utilisation de proc.stdout.xreadlines() fonctionnerait de la même façon que py3k, mais ce n'est pas le cas.


Mise à jour 2:

Voici le code de travail minimal.

#fake_utility.py, just generates lots of output over time
import sys, time
for i in range(10):
   print i
   sys.stdout.flush()
   time.sleep(0.5)

#display out put line by line
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
#works in python 3.0+
#for line in proc.stdout:
for line in iter(proc.stdout.readline,''):
   print line.rstrip()
195
deft_code

Cela fait longtemps que je n'ai pas travaillé pour la dernière fois avec Python, mais je pense que le problème vient de l'instruction for line in proc.stdout, qui lit l'intégralité de l'entrée avant de la parcourir. La solution consiste à utiliser readline() à la place:

#filters output
import subprocess
proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)
while True:
  line = proc.stdout.readline()
  if line != '':
    #the real code does filtering here
    print "test:", line.rstrip()
  else:
    break

Bien sûr, vous devez toujours gérer la mise en mémoire tampon du sous-processus.

Remarque: selon la documentation la solution avec un itérateur devrait être équivalente à utiliser readline(), à l'exception du tampon de lecture anticipée, mais (ou exactement à cause de cela), la modification proposée a produit des résultats différents pour moi (Python 2.5 sous Windows XP).

151
Rômulo Ceccon

Peu en retard à la fête, mais a été surpris de ne pas voir ce que je pense être la solution la plus simple ici:

import io
import subprocess

proc = subprocess.Popen(["prog", "arg"], stdout=subprocess.PIPE)
for line in io.TextIOWrapper(proc.stdout, encoding="utf-8"):  # or another encoding
    # do something with line
30
jbg

En effet, si vous avez trié l'itérateur, la mise en mémoire tampon pourrait maintenant être votre problème. Vous pourriez dire au python du sous-processus de ne pas mettre sa sortie en mémoire tampon.

proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE)

devient 

proc = subprocess.Popen(['python','-u', 'fake_utility.py'],stdout=subprocess.PIPE)

J'en ai eu besoin en appelant python à partir de python.

16
Steve Carter

Vous voulez passer ces paramètres supplémentaires à subprocess.Popen:

bufsize=1, universal_newlines=True

Ensuite, vous pouvez itérer comme dans votre exemple. (Testé avec Python 3.5)

11
user1747134

La modification suivante de la réponse de Rômulo fonctionne pour moi sur Python 2 et 3 (2.7.12 et 3.6.1):

import os
import subprocess

process = subprocess.Popen(command, stdout=subprocess.PIPE)
while True:
  line = process.stdout.readline()
  if line != '':
    os.write(1, line)
  else:
    break
1
mdh

Fonction permettant d'itérer simultanément stdout et stderr simultanément, en temps réel, ligne par ligne

Si vous devez obtenir le flux de sortie pour stdout et stderr simultanément, vous pouvez utiliser la fonction suivante.

La fonction utilise des files d'attente pour fusionner les deux canaux de Popen en un seul itérateur.

Nous créons ici la fonction read_popen_pipes():

from queue import Queue, Empty
from concurrent.futures import ThreadPoolExecutor


def enqueue_output(file, queue):
    for line in iter(file.readline, ''):
        queue.put(line)
    file.close()


def read_popen_pipes(p):

    with ThreadPoolExecutor(2) as pool:
        q_stdout, q_stderr = Queue(), Queue()

        pool.submit(enqueue_output, p.stdout, q_stdout)
        pool.submit(enqueue_output, p.stderr, q_stderr)

        while True:

            if p.poll() is not None and q_stdout.empty() and q_stderr.empty():
                break

            out_line = err_line = ''

            try:
                out_line = q_stdout.get_nowait()
                err_line = q_stderr.get_nowait()
            except Empty:
                pass

            yield (out_line, err_line)

read_popen_pipes() en cours d'utilisation:

import subprocess as sp


with sp.Popen(my_cmd, stdout=sp.PIPE, stderr=sp.PIPE, text=True) as p:

    for out_line, err_line in read_popen_pipes(p):

        # Do stuff with each line, e.g.:
        print(out_line, end='')
        print(err_line, end='')

    return p.poll() # return status-code
0
Rotareti

Vous pouvez également lire les lignes sans boucle. Fonctionne en python3.6.

import os
import subprocess

process = subprocess.Popen(command, stdout=subprocess.PIPE)
list_of_byte_strings = process.stdout.readlines()
0
Aiven

J'ai essayé cela avec python3 et cela a fonctionné, source

def output_reader(proc):
    for line in iter(proc.stdout.readline, b''):
        print('got line: {0}'.format(line.decode('utf-8')), end='')


def main():
    proc = subprocess.Popen(['python', 'fake_utility.py'],
                            stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT)

    t = threading.Thread(target=output_reader, args=(proc,))
    t.start()

    try:
        time.sleep(0.2)
        import time
        i = 0

        while True:
        print (hex(i)*512)
        i += 1
        time.sleep(0.5)
    finally:
        proc.terminate()
        try:
            proc.wait(timeout=0.2)
            print('== subprocess exited with rc =', proc.returncode)
        except subprocess.TimeoutExpired:
            print('subprocess did not terminate in time')
    t.join()
0
shakram02