web-dev-qa-db-fra.com

Imprimer constamment le résultat du sous-processus pendant l'exécution du processus

Pour lancer des programmes à partir de mes scripts Python, j'utilise la méthode suivante:

def execute(command):
    process = subprocess.Popen(command, Shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    output = process.communicate()[0]
    exitCode = process.returncode

    if (exitCode == 0):
        return output
    else:
        raise ProcessException(command, exitCode, output)

Ainsi, lorsque je lance un processus tel que Process.execute("mvn clean install"), mon programme attend que le processus soit terminé, et ce n’est qu’alors que j’obtiens la sortie complète de mon programme. C’est ennuyeux si j’exécute un processus qui prend un certain temps.

Puis-je laisser mon programme écrire ligne par ligne la sortie du processus, en interrogeant la sortie du processus avant qu'elle ne se termine en boucle?

** [EDIT] Désolé, je n'ai pas très bien cherché avant de poster cette question. Le filetage est en réalité la clé. Trouvez un exemple ici qui montre comment faire: ** Python Subprocess.Popen à partir d'un thread

142
ifischer

Vous pouvez utiliser iter pour traiter les lignes dès que la commande les génère: lines = iter(fd.readline, ""). Voici un exemple complet illustrant un cas d'utilisation typique (merci à @jfs pour son aide):

from __future__ import print_function # Only Python 2.x
import subprocess

def execute(cmd):
    popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True)
    for stdout_line in iter(popen.stdout.readline, ""):
        yield stdout_line 
    popen.stdout.close()
    return_code = popen.wait()
    if return_code:
        raise subprocess.CalledProcessError(return_code, cmd)

# Example
for path in execute(["locate", "a"]):
    print(path, end="")
203
tokland

Ok, j’ai réussi à le résoudre sans thread (nous vous serions reconnaissants de bien vouloir utiliser des threads) en utilisant un extrait de cette question Interception de la sortie standard d’un sous-processus en cours d’exécution

def execute(command):
    process = subprocess.Popen(command, Shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

    # Poll process for new output until finished
    while True:
        nextline = process.stdout.readline()
        if nextline == '' and process.poll() is not None:
            break
        sys.stdout.write(nextline)
        sys.stdout.flush()

    output = process.communicate()[0]
    exitCode = process.returncode

    if (exitCode == 0):
        return output
    else:
        raise ProcessException(command, exitCode, output)
76
ifischer

Pour imprimer ligne par ligne la sortie du sous-processus dès que son tampon stdout est vidé en Python 3:

from subprocess import Popen, PIPE, CalledProcessError

with Popen(cmd, stdout=PIPE, bufsize=1, universal_newlines=True) as p:
    for line in p.stdout:
        print(line, end='') # process line here

if p.returncode != 0:
    raise CalledProcessError(p.returncode, p.args)

Remarque: vous n'avez pas besoin de p.poll() - la boucle se termine lorsque eof est atteint. Et vous n'avez pas besoin de iter(p.stdout.readline, '') - le bogue de lecture anticipée est corrigé dans Python 3.

Voir aussi, Python: lit les entrées en flux continu de subprocess.communicate () .

47
jfs

@tokland

essayé votre code et corrigé pour 3.4 et windows dir.cmd est une simple commande dir, enregistrée en fichier cmd

import subprocess
c = "dir.cmd"

def execute(command):
    popen = subprocess.Popen(command, stdout=subprocess.PIPE,bufsize=1)
    lines_iterator = iter(popen.stdout.readline, b"")
    while popen.poll() is None:
        for line in lines_iterator:
            nline = line.rstrip()
            print(nline.decode("latin"), end = "\r\n",flush =True) # yield line

execute(c)
5
user3759376

Si vous essayez de répondre à cette question pour obtenir la sortie standard d'un script Python, notez que Python met sa mémoire tampon dans la mémoire tampon, ce qui peut prendre un certain temps avant de voir la sortie standard.

Ceci peut être corrigé en ajoutant ce qui suit après chaque écriture stdout dans le script cible:

sys.stdout.flush()
3
user1379351

En Python> = 3.5, utiliser subprocess.run fonctionne pour moi:

import subprocess

cmd = 'echo foo; sleep 1; echo foo; sleep 2; echo foo'
subprocess.run(cmd, Shell=True)

(obtenir la sortie pendant l'exécution fonctionne également sans Shell=True) https://docs.python.org/3/library/subprocess.html#subprocess.run

2
user7017793

Au cas où quelqu'un voudrait lire à la fois stdout et stderr en même temps en utilisant des threads, voici ce que j'ai proposé:

import threading
import subprocess
import Queue

class AsyncLineReader(threading.Thread):
    def __init__(self, fd, outputQueue):
        threading.Thread.__init__(self)

        assert isinstance(outputQueue, Queue.Queue)
        assert callable(fd.readline)

        self.fd = fd
        self.outputQueue = outputQueue

    def run(self):
        map(self.outputQueue.put, iter(self.fd.readline, ''))

    def eof(self):
        return not self.is_alive() and self.outputQueue.empty()

    @classmethod
    def getForFd(cls, fd, start=True):
        queue = Queue.Queue()
        reader = cls(fd, queue)

        if start:
            reader.start()

        return reader, queue


process = subprocess.Popen(command, Shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(stdoutReader, stdoutQueue) = AsyncLineReader.getForFd(process.stdout)
(stderrReader, stderrQueue) = AsyncLineReader.getForFd(process.stderr)

# Keep checking queues until there is no more output.
while not stdoutReader.eof() or not stderrReader.eof():
   # Process all available lines from the stdout Queue.
   while not stdoutQueue.empty():
       line = stdoutQueue.get()
       print 'Received stdout: ' + repr(line)

       # Do stuff with stdout line.

   # Process all available lines from the stderr Queue.
   while not stderrQueue.empty():
       line = stderrQueue.get()
       print 'Received stderr: ' + repr(line)

       # Do stuff with stderr line.

   # Sleep for a short time to avoid excessive CPU use while waiting for data.
   sleep(0.05)

print "Waiting for async readers to finish..."
stdoutReader.join()
stderrReader.join()

# Close subprocess' file descriptors.
process.stdout.close()
process.stderr.close()

print "Waiting for process to exit..."
returnCode = process.wait()

if returnCode != 0:
   raise subprocess.CalledProcessError(returnCode, command)

Je voulais juste partager ceci, car je me suis retrouvé sur cette question en essayant de faire quelque chose de similaire, mais aucune des réponses n'a résolu mon problème. J'espère que ça aide quelqu'un!

Notez que dans mon cas d'utilisation, un processus externe tue le processus que nous Popen().

1
Will

Ce PoC lit en permanence la sortie d'un processus et peut être consulté en cas de besoin. Seul le dernier résultat est conservé, toutes les autres sorties sont ignorées, ce qui empêche le PIPE de se développer en mémoire:

import subprocess
import time
import threading
import Queue


class FlushPipe(object):
    def __init__(self):
        self.command = ['python', './print_date.py']
        self.process = None
        self.process_output = Queue.LifoQueue(0)
        self.capture_output = threading.Thread(target=self.output_reader)

    def output_reader(self):
        for line in iter(self.process.stdout.readline, b''):
            self.process_output.put_nowait(line)

    def start_process(self):
        self.process = subprocess.Popen(self.command,
                                        stdout=subprocess.PIPE)
        self.capture_output.start()

    def get_output_for_processing(self):
        line = self.process_output.get()
        print ">>>" + line


if __== "__main__":
    flush_pipe = FlushPipe()
    flush_pipe.start_process()

    now = time.time()
    while time.time() - now < 10:
        flush_pipe.get_output_for_processing()
        time.sleep(2.5)

    flush_pipe.capture_output.join(timeout=0.001)
    flush_pipe.process.kill()

print_date.py

#!/usr/bin/env python
import time

if __== "__main__":
    while True:
        print str(time.time())
        time.sleep(0.01)

sortie: vous pouvez voir clairement qu'il n'y a qu'une sortie à partir d'intervalle ~ 2.5s, rien entre les deux.

>>>1520535158.51
>>>1520535161.01
>>>1520535163.51
>>>1520535166.01
1
Robert Nagtegaal

Pour répondre à la question initiale, la meilleure façon pour IMO consiste simplement à rediriger le sous-processus stdout directement vers la variable stdout de votre programme (la même chose peut être effectuée pour stderr, comme dans l'exemple ci-dessous).

p = Popen(cmd, stdout=sys.stdout, stderr=sys.stderr)
p.communicate()
0
Alleo

Cela fonctionne au moins en Python3.4

import subprocess

process = subprocess.Popen(cmd_list, stdout=subprocess.PIPE)
for line in process.stdout:
    print(line.decode().strip())
0
arod

Aucune des réponses ici ne répond à tous mes besoins.

  1. Pas de threads pour stdout (pas de files d'attente, etc.)
  2. Non bloquant car j'ai besoin de vérifier d'autres choses
  3. Utilisez PIPE comme je devais faire plusieurs choses, par exemple flux de sortie, écrivez dans un fichier journal et renvoyez une chaîne de caractères de la sortie.

Un peu d’arrière-plan: j’utilise un ThreadPoolExecutor pour gérer un pool de threads, chacun lançant un sous-processus et l’exécutant en simultané. (En Python2.7, mais cela devrait également fonctionner dans les versions 3.x les plus récentes). Je ne veux pas utiliser les threads uniquement pour la collecte de sortie, car je veux que le plus grand nombre soit disponible pour d'autres tâches (un pool de 20 processus utiliserait 40 threads pour l'exécution; 1 pour le thread de processus et 1 pour la sortie standard ... et plus si vous voulez stderr je suppose)

Je supprime beaucoup d'exceptions et c'est le cas ici, donc c'est basé sur un code qui fonctionne en production. Espérons que je ne l'ai pas gâché dans le copier-coller. En outre, les commentaires sont les bienvenus!

import time
import fcntl
import subprocess
import time

proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

# Make stdout non-blocking when using read/readline
proc_stdout = proc.stdout
fl = fcntl.fcntl(proc_stdout, fcntl.F_GETFL)
fcntl.fcntl(proc_stdout, fcntl.F_SETFL, fl | os.O_NONBLOCK)

def handle_stdout(proc_stream, my_buffer, echo_streams=True, log_file=None):
    """A little inline function to handle the stdout business. """
    # fcntl makes readline non-blocking so it raises an IOError when empty
    try:
        for s in iter(proc_stream.readline, ''):   # replace '' with b'' for Python 3
            my_buffer.append(s)

            if echo_streams:
                sys.stdout.write(s)

            if log_file:
                log_file.write(s)
    except IOError:
        pass

# The main loop while subprocess is running
stdout_parts = []
while proc.poll() is None:
    handle_stdout(proc_stdout, stdout_parts)

    # ...Check for other things here...
    # For example, check a multiprocessor.Value('b') to proc.kill()

    time.sleep(0.01)

# Not sure if this is needed, but run it again just to be sure we got it all?
handle_stdout(proc_stdout, stdout_parts)

stdout_str = "".join(stdout_parts)  # Just to demo

Je suis sûr que des frais généraux sont ajoutés ici, mais ce n'est pas un problème dans mon cas. Fonctionnellement, il fait ce dont j'ai besoin. La seule chose que je n’ai pas résolue, c’est la raison pour laquelle cela fonctionne parfaitement pour les messages de journal, mais certains messages print apparaissent plus tard et tous en même temps.

0
Rafe