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()
61
demandé sur Mad Physicist 2009-10-22 16:14:43

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 donc argv en C. Donc vous passez une liste à Popen pour appeler des sous-processus, pas une chaîne.
  • Ne pas rediriger stderr à un PIPE 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 .

77
répondu nosklo 2017-03-25 15:29:06

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())
25
répondu Elvin 2016-03-03 14:51:56

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.

Voir aussi https://unix.stackexchange.com/questions/25372 /

10
répondu Ling 2017-04-13 12:36:30

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.

7
répondu Erik 2012-10-01 14:34:02
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.

7
répondu IBue 2013-06-21 21:33:23

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
6
répondu zviadm 2013-05-02 20:29:35

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)
2
répondu Will 2009-10-22 12:26:39
    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.

2
répondu YIFCC 2017-09-11 13:16:56

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

1
répondu nithin 2016-08-14 14:08:46

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...
0
répondu MikeGM 2013-03-07 23:05:13

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()
0
répondu banghuazhao 2017-09-12 03:06:36