capter stdout en temps réel à partir du sous-processus
je veux subprocess.Popen()
rsync.exe dans Windows, et imprime le stdout en Python.
mon code fonctionne, mais il ne capte pas la progression tant qu'un transfert de fichier n'est pas fait! Je veux imprimer le progrès pour chaque fichier en temps réel.
en utilisant Python 3.1 maintenant puisque j'ai entendu qu'il devrait être mieux à la manipulation IO.
import subprocess, time, os, sys
cmd = "rsync.exe -vaz -P source/ dest/"
p, line = True, 'start'
p = subprocess.Popen(cmd,
shell=True,
bufsize=64,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)
for line in p.stdout:
print(">>> " + str(line.rstrip()))
p.stdout.flush()
11 réponses
Quelques règles de base pour subprocess
.
- Jamais utiliser
shell=True
. Il invoque inutilement un processus shell supplémentaire pour appeler votre programme. - lors de l'appel de processus, les arguments sont transmis sous forme de listes.
sys.argv
en python est une liste, et doncargv
en C. Donc vous passez une liste àPopen
pour appeler des sous-processus, pas une chaîne. - Ne pas rediriger
stderr
à unPIPE
lorsque vous n'êtes pas le lire. - Ne pas rediriger
stdin
lorsque vous n'êtes pas à l'écrit.
exemple:
import subprocess, time, os, sys
cmd = ["rsync.exe", "-vaz", "-P", "source/" ,"dest/"]
p = subprocess.Popen(cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
for line in iter(p.stdout.readline, b''):
print(">>> " + line.rstrip())
cela dit, il est probable que rsync tampons de sa sortie lorsqu'il détecte qu'il est connecté à un tuyau au lieu d'un terminal. C'est le comportement par défaut - lorsqu'il est connecté à un tuyau, les programmes doivent explicitement flush stdout pour résultats en temps réel, sinon la bibliothèque standard c servira de tampon.
pour tester cela, essayez d'exécuter ceci à la place:
cmd = [sys.executable, 'test_out.py']
et créer un test_out.py
fichier avec le contenu:
import sys
import time
print ("Hello")
sys.stdout.flush()
time.sleep(10)
print ("World")
L'exécution de ce sous-processus devrait vous donner "bonjour" et attendre 10 secondes avant de donner "monde". Si cela se produit avec le code python ci-dessus et non avec rsync
, cela signifie que rsync
est lui-même un tampon de sortie, Donc vous êtes hors de la chance.
une solution serait de se connecter directement à un pty
, en utilisant quelque chose comme pexpect
.
je sais que c'est un vieux sujet, mais il existe une solution maintenant. Appeler le rsync avec option --outbuf=L. exemple:
cmd=['rsync', '-arzv','--backup','--outbuf=L','source/','dest']
p = subprocess.Popen(cmd,
stdout=subprocess.PIPE)
for line in iter(p.stdout.readline, b''):
print '>>> {}'.format(line.rstrip())
sous Linux, j'ai eu le même problème de me débarrasser du tampon. J'ai finalement utilisé "stdbuf-o0" (ou, unbuffer de expectative) pour se débarrasser du tampon de PIPE.
proc = Popen(['stdbuf', '-o0'] + cmd, stdout=PIPE, stderr=PIPE)
stdout = proc.stdout
je pourrais alors utiliser select.sélectionnez sur stdout.
vous ne pouvez pas obtenir stdout pour imprimer Non tamponné à un tuyau (sauf si vous pouvez réécrire le programme qui imprime à stdout), donc voici ma solution:
rediriger stdout vers sterr, qui n'est pas tamponné. '<cmd> 1>&2'
devrait le faire. Ouvrir le procédé comme suit: myproc = subprocess.Popen('<cmd> 1>&2', stderr=subprocess.PIPE)
Vous ne pouvez pas distinguer de stdout ou de stderr, mais vous obtenez toutes les sorties immédiatement.
Espérons que cela aide quelqu'un s'attaquer à ce problème.
for line in p.stdout:
...
bloque toujours jusqu'à la ligne suivante.
pour un comportement "en temps réel", vous devez faire quelque chose comme ceci:
while True:
inchar = p.stdout.read(1)
if inchar: #neither empty string nor None
print(str(inchar), end='') #or end=None to flush immediately
else:
print('') #flush for implicit line-buffering
break
la boucle "while" est laissée lorsque le processus de l'enfant ferme son ou ses stdout.
read()/read(-1)
bloquerait jusqu'à ce que le processus de l'enfant ferme son casier judiciaire ou sorte.
votre problème est:
for line in p.stdout:
print(">>> " + str(line.rstrip()))
p.stdout.flush()
l'itérateur lui-même a un tampon supplémentaire.
essayez de faire comme ceci:
while True:
line = p.stdout.readline()
if not line:
break
print line
remplacer le procédé stdout par le procédé rsync à débiter.
p = subprocess.Popen(cmd,
shell=True,
bufsize=0, # 0=unbuffered, 1=line-buffered, else buffer-size
stdin=subprocess.PIPE,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)
p = subprocess.Popen(command,
bufsize=0,
universal_newlines=True)
j'écris une interface graphique pour rsync en python, et j'ai les mêmes problèmes. Ce problème me trouble depuis plusieurs jours jusqu'à ce que je le trouve dans pyDoc.
si universal_newlines est True, les objets file stdout et stderr sont ouverts comme des fichiers texte en mode universal newlines. Les lignes peuvent être supprimées par n'importe laquelle des conventions' \n',' \r',' \r\n', '\ r \ n ' Ou '\ R \ n', La Convention Windows. L'ensemble de ces externes les représentations sont vues comme '\n ' par le programme Python.
il semble que rsync va sortir '\r' quand translate est en cours.
pour éviter la mise en cache de la sortie, vous pourriez essayer pexpect,
child = pexpect.spawn(launchcmd,args,timeout=None)
while True:
try:
child.expect('\n')
print(child.before)
except pexpect.EOF:
break
PS : je sais que cette question est assez ancienne, fournissant toujours la solution qui a fonctionné pour moi.
Spa : réponse tirée d'une autre question
j'ai remarqué qu'il n'est pas question d'utiliser un fichier temporaire comme intermédiaire. Ce qui suit permet de contourner les problèmes de buffering en sortant vers un fichier temporaire et vous permet d'analyser les données provenant de rsync sans se connecter à un pty. J'ai testé ce qui suit sur une machine linux, et la sortie de rsync a tendance à différer d'une plate-forme à l'autre, de sorte que les expressions régulières pour analyser la sortie peuvent varier:
import subprocess, time, tempfile, re
pipe_output, file_name = tempfile.TemporaryFile()
cmd = ["rsync", "-vaz", "-P", "/src/" ,"/dest"]
p = subprocess.Popen(cmd, stdout=pipe_output,
stderr=subprocess.STDOUT)
while p.poll() is None:
# p.poll() returns None while the program is still running
# sleep for 1 second
time.sleep(1)
last_line = open(file_name).readlines()
# it's possible that it hasn't output yet, so continue
if len(last_line) == 0: continue
last_line = last_line[-1]
# Matching to "[bytes downloaded] number% [speed] number:number:number"
match_it = re.match(".* ([0-9]*)%.* ([0-9]*:[0-9]*:[0-9]*).*", last_line)
if not match_it: continue
# in this case, the percentage is stored in match_it.group(1),
# time in match_it.group(2). We could do something with it here...
utilisez | tee
pour rediriger le stdout vers un fichier nommé out.txt lors de l'affichage de stdout en temps réel sur le terminal
import subprocess, time, os, sys
cmd = "rsync.exe -vaz -P source/ dest/ | tee out.txt"
p, line = True, 'start'
p = subprocess.Popen(cmd,
shell=True)
p.wait()
vous pouvez sortir le stdout du fichier.txt, après un processus secondaire.
# Get stdout from file out.txt
f = open('out.txt')
out = f.read()
f.close()