Question

J'essaie d'écrire un script wrapper pour un programme en ligne de commande (svnadmin verify) qui affichera un indicateur de progression intéressant pour l'opération. Cela nécessite que je sois en mesure de voir chaque ligne de sortie du programme enveloppé dès qu’elle est sortie.

Je pensais que je ne ferais qu'exécuter le programme avec subprocess.Popen, utiliser stdout=PIPE, puis lire chaque ligne au fur et à mesure de son entrée et agir en conséquence. Cependant, lorsque j’ai exécuté le code suivant, la sortie semblait tamponnée quelque part, ce qui l’a fait apparaître dans deux morceaux, lignes 1 à 332, puis 333 à 439 (dernière ligne de sortie)

from subprocess import Popen, PIPE, STDOUT

p = Popen('svnadmin verify /var/svn/repos/config', stdout = PIPE, 
        stderr = STDOUT, shell = True)
for line in p.stdout:
    print line.replace('\n', '')

Après avoir examiné un peu la documentation sur le sous-processus, j'ai découvert le paramètre bufsize sur Popen. J'ai donc essayé de définir bufsize sur 1 (mémoire tampon pour chaque ligne) et 0 (pas de mémoire tampon), mais aucune valeur ne semblait changer. la façon dont les lignes ont été livrées.

À ce stade, je commençais à saisir les paillettes. J'ai donc écrit la boucle de sortie suivante:

while True:
    try:
        print p.stdout.next().replace('\n', '')
    except StopIteration:
        break

mais a obtenu le même résultat.

Est-il possible d’obtenir la sortie du programme en temps réel d’un programme exécuté à l’aide de sous-processus? Existe-t-il une autre option dans Python compatible avec les versions ultérieures (pas exec*)?

Était-ce utile?

La solution

J'ai essayé cela, et pour une raison quelconque, alors que le code

for line in p.stdout:
  ...

tamponne agressivement, la variante

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

ne le fait pas. Apparemment, il s’agit d’un bogue connu: http://bugs.python.org/issue3907 (le problème est maintenant & "Fermé &"; à compter du 29 août 2018)

Autres conseils

p = subprocess.Popen(cmd, stdout=subprocess.PIPE, bufsize=1)
for line in iter(p.stdout.readline, b''):
    print line,
p.stdout.close()
p.wait()

Vous pouvez essayer ceci:

import subprocess
import sys

process = subprocess.Popen(
    cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)

while True:
    out = process.stdout.read(1)
    if out == '' and process.poll() != None:
        break
    if out != '':
        sys.stdout.write(out)
        sys.stdout.flush()

Si vous utilisez readline au lieu de read, il y aura des cas où le message d'entrée ne sera pas imprimé. Essayez-le avec une commande qui nécessite une entrée en ligne et voyez par vous-même.

Vous pouvez diriger la sortie du sous-processus directement vers les flux. Exemple simplifié:

subprocess.run(['ls'], stderr=sys.stderr, stdout=sys.stdout)

J'ai rencontré le même problème il y a quelque temps. Ma solution consistait à abandonner l'itération de la méthode read, qui sera renvoyée immédiatement même si votre sous-processus n'a pas fini de s'exécuter, etc.

Problème de sortie en temps réel résolu: J'ai rencontré un problème similaire en Python, tout en capturant la sortie en temps réel du programme c. J'ai ajouté & "; fflush (stdout) ; &"; dans mon code C. Cela a fonctionné pour moi. Voici le code du code

< < Programme C & Gt; & Gt;

#include <stdio.h>
void main()
{
    int count = 1;
    while (1)
    {
        printf(" Count  %d\n", count++);
        fflush(stdout);
        sleep(1);
    }
}

< < Programme Python & Gt; & Gt;

#!/usr/bin/python

import os, sys
import subprocess


procExe = subprocess.Popen(".//count", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)

while procExe.poll() is None:
    line = procExe.stdout.readline()
    print("Print:" + line)

< < SORTIE & Gt; & Gt; Impression: Comptez 1 Impression: Comptez 2 Imprimer: Comptez 3

J'espère que ça aide.

~ sairam

Vous pouvez utiliser un itérateur sur chaque octet de la sortie du sous-processus. Cela permet la mise à jour en ligne (les lignes se terminant par "\ r" écrasent la ligne de sortie précédente) à partir du sous-processus:

from subprocess import PIPE, Popen

command = ["my_command", "-my_arg"]

# Open pipe to subprocess
subprocess = Popen(command, stdout=PIPE, stderr=PIPE)


# read each byte of subprocess
while subprocess.poll() is None:
    for c in iter(lambda: subprocess.stdout.read(1) if subprocess.poll() is None else {}, b''):
        c = c.decode('ascii')
        sys.stdout.write(c)
sys.stdout.flush()

if subprocess.returncode != 0:
    raise Exception("The subprocess did not terminate correctly.")

Selon le cas d'utilisation, vous pouvez également désactiver la mise en mémoire tampon dans le sous-processus lui-même.

Si le sous-processus est un processus Python, vous pouvez le faire avant l'appel:

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

Ou passez-le dans l'argument env à Popen.

Sinon, si vous êtes sous Linux / Unix, vous pouvez utiliser l'outil stdbuf. Par exemple. comme:

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

Voir aussi ici sur <=> ou d'autres options.

(Voir aussi ici pour la même réponse.)

Le diffusion en continu sous-processus stdin et stdout avec asyncio en Python , publié sur le blog par Kevin McCarthy montre comment faire. avec asyncio:

import asyncio
from asyncio.subprocess import PIPE
from asyncio import create_subprocess_exec


async def _read_stream(stream, callback):
    while True:
        line = await stream.readline()
        if line:
            callback(line)
        else:
            break


async def run(command):
    process = await create_subprocess_exec(
        *command, stdout=PIPE, stderr=PIPE
    )

    await asyncio.wait(
        [
            _read_stream(
                process.stdout,
                lambda x: print(
                    "STDOUT: {}".format(x.decode("UTF8"))
                ),
            ),
            _read_stream(
                process.stderr,
                lambda x: print(
                    "STDERR: {}".format(x.decode("UTF8"))
                ),
            ),
        ]
    )

    await process.wait()


async def main():
    await run("docker build -t my-docker-image:latest .")


if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

Utilisation de pexpect [ http://www.noah.org/wiki/Pexpect ] avec des lignes de lecture non bloquantes résoudra ce problème. Cela provient du fait que les tubes sont mis en mémoire tampon et que la sortie de votre application est donc tamponnée par le tuyau. Par conséquent, vous ne pouvez pas accéder à cette sortie tant que la mémoire tampon n'est pas remplie ou que le processus n'est plus disponible.

J'ai utilisé cette solution pour obtenir une sortie en temps réel sur un sous-processus. Cette boucle s’arrêtera dès que le processus sera terminé, ce qui laissera de côté la nécessité d’une instruction break ou d’une boucle infinie possible.

sub_process = subprocess.Popen(my_command, close_fds=True, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

while sub_process.poll() is None:
    out = sub_process.stdout.read(1)
    sys.stdout.write(out)
    sys.stdout.flush()

Vous avez trouvé ce " plug-and-play " fonction ici . Travaillé comme un charme!

import subprocess

def myrun(cmd):
    """from http://blog.kagesenshi.org/2008/02/teeing-python-subprocesspopen-output.html
    """
    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    stdout = []
    while True:
        line = p.stdout.readline()
        stdout.append(line)
        print line,
        if line == '' and p.poll() != None:
            break
    return ''.join(stdout)

Solution complète:

import contextlib
import subprocess

# Unix, Windows and old Macintosh end-of-line
newlines = ['\n', '\r\n', '\r']
def unbuffered(proc, stream='stdout'):
    stream = getattr(proc, stream)
    with contextlib.closing(stream):
        while True:
            out = []
            last = stream.read(1)
            # Don't loop forever
            if last == '' and proc.poll() is not None:
                break
            while last not in newlines:
                # Don't loop forever
                if last == '' and proc.poll() is not None:
                    break
                out.append(last)
                last = stream.read(1)
            out = ''.join(out)
            yield out

def example():
    cmd = ['ls', '-l', '/']
    proc = subprocess.Popen(
        cmd,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        # Make all end-of-lines '\n'
        universal_newlines=True,
    )
    for line in unbuffered(proc):
        print line

example()

C’est le squelette de base que j’utilise toujours pour cela. Il facilite l’implémentation des délais d’exécution et permet de faire face aux inévitables processus de suspension.

import subprocess
import threading
import Queue

def t_read_stdout(process, queue):
    """Read from stdout"""

    for output in iter(process.stdout.readline, b''):
        queue.put(output)

    return

process = subprocess.Popen(['dir'],
                           stdout=subprocess.PIPE,
                           stderr=subprocess.STDOUT,
                           bufsize=1,
                           cwd='C:\\',
                           shell=True)

queue = Queue.Queue()
t_stdout = threading.Thread(target=t_read_stdout, args=(process, queue))
t_stdout.daemon = True
t_stdout.start()

while process.poll() is None or not queue.empty():
    try:
        output = queue.get(timeout=.5)

    except Queue.Empty:
        continue

    if not output:
        continue

    print(output),

t_stdout.join()

(Cette solution a été testée avec Python 2.7.15)

Vous avez juste besoin de sys.stdout.flush () après chaque ligne en lecture / écriture:

while proc.poll() is None:
    line = proc.stdout.readline()
    sys.stdout.write(line)
    # or print(line.strip()), you still need to force the flush.
    sys.stdout.flush()
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top