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
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="")
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)
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 () .
@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)
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()
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
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()
.
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
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()
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())
Aucune des réponses ici ne répond à tous mes besoins.
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.