web-dev-qa-db-fra.com

Lire l'entrée de streaming depuis subprocess.communicate ()

J'utilise subprocess.communicate() de Python pour lire stdout à partir d'un processus qui s'exécute pendant environ une minute.

Comment puis-je imprimer chaque ligne du stdout de ce processus en continu, de façon à pouvoir voir la sortie telle qu'elle est générée, mais toujours bloquer la fin du processus avant de continuer?

subprocess.communicate() apparaît pour donner toutes les sorties à la fois.

72

Veuillez noter, je pense la méthode de J.F. Sebastian (ci-dessous) est mieux.


Voici un exemple simple (sans vérification des erreurs):

import subprocess
proc = subprocess.Popen('ls',
                       Shell=True,
                       stdout=subprocess.PIPE,
                       )
while proc.poll() is None:
    output = proc.stdout.readline()
    print output,

Si ls se termine trop rapidement, alors la boucle while peut se terminer avant d'avoir lu toutes les données.

Vous pouvez attraper le reste en stdout de cette façon:

output = proc.communicate()[0]
print output,
41
unutbu

Pour obtenir la sortie du sous-processus ligne par ligne dès que le sous-processus vide sa mémoire tampon standard:

#!/usr/bin/env python2
from subprocess import Popen, PIPE

p = Popen(["cmd", "arg1"], stdout=PIPE, bufsize=1)
with p.stdout:
    for line in iter(p.stdout.readline, b''):
        print line,
p.wait() # wait for the subprocess to exit

iter() est utilisé pour lire les lignes dès qu'elles sont écrites pour contourner le bug de lecture anticipée dans Python 2 .

Si la sortie standard du sous-processus utilise une mise en mémoire tampon de bloc au lieu d'une mise en mémoire tampon de ligne en mode non interactif (ce qui entraîne un retard dans la sortie jusqu'à ce que la mémoire tampon de l'enfant soit pleine ou purgée explicitement par l'enfant), vous pouvez essayer de forcer une sortie sans tampon à l'aide de pexpect, pty modules ou unbuffer, stdbuf, script utilitaires , voir Q: Pourquoi ne pas simplement utiliser un tuyau (popen ())?


Voici Python 3:

#!/usr/bin/env python3
from subprocess import Popen, PIPE

with Popen(["cmd", "arg1"], stdout=PIPE, bufsize=1,
           universal_newlines=True) as p:
    for line in p.stdout:
        print(line, end='')

Remarque: Contrairement à Python 2 qui génère les sous-tests des sous-processus tels quels; Python 3 utilise le mode texte (la sortie de cmd est décodée à l'aide de l'encodage locale.getpreferredencoding(False)) ).

136
jfs

Je crois que la façon la plus simple de collecter la sortie d'un processus en mode streaming est la suivante:

import sys
from subprocess import *
proc = Popen('ls', Shell=True, stdout=PIPE)
while True:
    data = proc.stdout.readline()   # Alternatively proc.stdout.read(1024)
    if len(data) == 0:
        break
    sys.stdout.write(data)   # sys.stdout.buffer.write(data) on Python 3.x

La fonction readline() ou read() ne doit renvoyer une chaîne vide que sur EOF, une fois le processus terminé - sinon elle se bloquera s'il n'y a rien à lire (readline() inclut la nouvelle ligne, donc sur les lignes vides, elle retourne "\ n"). Cela évite la nécessité d'un appel final communicate() maladroit après la boucle.

Sur les fichiers avec de très longues lignes, read() peut être préférable pour réduire l'utilisation maximale de la mémoire - le nombre qui lui est transmis est arbitraire, mais son exclusion entraîne la lecture de la sortie entière du tube à la fois, ce qui n'est probablement pas souhaitable.

5
D Coetzee

Si vous souhaitez une approche non bloquante, n'utilisez pas process.communicate(). Si vous définissez l'argument subprocess.Popen()stdout sur PIPE, vous pouvez lire dans process.stdout Et vérifier si le processus s'exécute toujours à l'aide de process.poll().

3
Lukáš Lalinský

Si vous essayez simplement de transmettre la sortie en temps réel, il est difficile d'être plus simple:

import subprocess

# This will raise a CalledProcessError if the program return a nonzero code.
# You can use call() instead if you don't care about that case.
subprocess.check_call(['ls', '-l'])

Voir les documents pour subprocess.check_call () .

Si vous avez besoin de traiter la sortie, bien sûr, bouclez dessus. Mais si vous ne le faites pas, restez simple.

Edit: JF Sebastian souligne à la fois que les valeurs par défaut des paramètres stdout et stderr sont transmises à sys.stdout et sys. stderr, et que cela échouera si sys.stdout et sys.stderr ont été remplacés (par exemple, pour capturer la sortie dans les tests).

2
Nate
myCommand="ls -l"
cmd=myCommand.split()
# "universal newline support" This will cause to interpret \n, \r\n and \r     equally, each as a newline.
p = subprocess.Popen(cmd, stderr=subprocess.PIPE, universal_newlines=True)
while True:    
    print(p.stderr.readline().rstrip('\r\n'))
1
Petr J