J'essaie d'écrire un script wrapper pour un programme en ligne de commande (svnadmin verify) qui affichera un indicateur de progression de Nice pour l'opération. Cela nécessite que je sois capable 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 en utilisant 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 être tamponnée quelque part, ce qui l'a fait apparaître dans deux morceaux, les lignes 1 à 332, puis 333 à 439 (la 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
à 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 modifier le mode de livraison des lignes. .
À 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 de programme 'en temps réel' d'un programme exécuté à l'aide d'un sous-processus? Existe-t-il une autre option dans Python compatible avec les versions ultérieures (pas exec*
)?
J'ai essayé cela, et pour une raison quelconque, alors que le code
for line in p.stdout:
...
tampons agressivement, la variante
while True:
line = p.stdout.readline()
if not line: break
...
ne fait pas. Apparemment, il s'agit d'un bug connu: http://bugs.python.org/issue3907 (Le problème est maintenant "Fermé" à partir du 29 août 2018)
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 directement diriger la sortie du sous-processus 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 pour 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.
Vous pouvez utiliser un itérateur sur chaque octet dans 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.")
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
<< Programme C >>
#include <stdio.h>
void main()
{
int count = 1;
while (1)
{
printf(" Count %d\n", count++);
fflush(stdout);
sleep(1);
}
}
<< Programme Python >>
#!/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 >> Impression: compte 1 Impression: compte 2 Impression: compte 3.
J'espère que ça aide.
~ sairam
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 bien passez ceci 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 à propos de stdbuf
ou d’autres options.
(Voir aussi ici pour la même réponse.)
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é une instruction break ou 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()
L'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. Ainsi, la sortie de votre application est mise en tampon 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 pas mort.
C’est le squelette de base que j’utilise toujours pour cela. Il facilite la mise en œuvre des délais d'attente 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()
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()
Trouvé cette fonction "plug-and-play" 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)
Le sous-processus Streaming stdin et stdout avec asyncio en Python post 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 __== "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())