Je veux subprocess.Popen()
rsync.exe sous Windows et imprimer le stdout en Python.
Mon code fonctionne, mais il n'attrape pas la progression tant qu'un transfert de fichier n'est pas effectué! Je souhaite imprimer la progression de chaque fichier en temps réel.
En utilisant Python 3.1 maintenant depuis que j'ai entendu dire qu'il devrait être meilleur pour gérer les E / S.
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()
python
subprocess
stdout
John A
la source
la source
Réponses:
Quelques règles de base pour
subprocess
.shell=True
. Il appelle inutilement un processus shell supplémentaire pour appeler votre programme.sys.argv
en python est une liste, tout commeargv
en C. Vous passez donc une liste àPopen
pour appeler des sous-processus, pas une chaîne.stderr
unPIPE
lorsque vous ne le lisez pas.stdin
lorsque vous n'y écrivez pas.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 tamponne sa sortie lorsqu'il détecte qu'il est connecté à un tube au lieu d'un terminal. C'est le comportement par défaut - lorsqu'ils sont connectés à un tube, les programmes doivent explicitement vider stdout pour les résultats en temps réel, sinon la bibliothèque C standard sera mise en mémoire tampon.
Pour tester cela, essayez d'exécuter ceci à la place:
cmd = [sys.executable, 'test_out.py']
et créez 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 "Hello" et attendre 10 secondes avant de donner "World". Si cela se produit avec le code python ci-dessus et non avec
rsync
, cela signifie qu'ilrsync
met en mémoire tampon la sortie, vous n'avez donc pas de chance.Une solution serait de se connecter directement à un
pty
, en utilisant quelque chose commepexpect
.la source
shell=False
est la bonne chose lorsque vous construisez une ligne de commande, en particulier à partir de données entrées par l'utilisateur. Mais néanmoinsshell=True
est également utile lorsque vous obtenez toute la ligne de commande à partir d'une source fiable (par exemple, codée en dur dans le script).shell=True
. Pensez-y - vous invoquez un autre processus sur votre système d'exploitation, impliquant l'allocation de mémoire, l'utilisation du disque, la planification du processeur, juste pour diviser une chaîne ! Et celui que vous vous êtes joint !! Vous pouvez diviser en python, mais il est de toute façon plus facile d'écrire chaque paramètre séparément. En outre, en utilisant un moyen liste vous ne devez pas échapper à caractères shell spéciaux: espaces,;
,>
,<
,&
.. Vos paramètres peuvent contenir ces caractères et vous n'avez pas à vous inquiéter! Je ne vois passhell=True
vraiment de raison d'utiliser , à moins que vous n'exécutiez une commande shell uniquement.csv
module. Mais à titre d'exemple, votre pipeline en python serait:p = Popen(['cut', '-f1'], stdin=open('longfile.tab'), stdout=PIPE) ; p2 = Popen(['head', '-100'], stdin=p.stdout, stdout=PIPE) ; result, stderr = p2.communicate() ; print result
Notez que vous pouvez travailler avec des noms de fichiers longs et des caractères spéciaux de shell sans avoir à vous échapper, maintenant que le shell n'est pas impliqué. De plus, c'est beaucoup plus rapide car il y a un processus de moins.for line in iter(p.stdout.readline, b'')
place defor line in p.stdout
Python 2 sinon les lignes ne sont pas lues en temps réel même si le processus source ne met pas en mémoire tampon sa sortie.Je sais que c'est un vieux sujet, mais il y a une solution maintenant. Appelez rsync avec l'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())
la source
preexec_fn=os.setpgrp
pour que le programme survienne à son script parent 2. vous sautez la lecture du tube du processus 3. le processus génère beaucoup de données, remplissant le tube 4. vous êtes bloqué pendant des heures , en essayant de comprendre pourquoi le programme que vous exécutez se ferme après un certain laps de temps . La réponse de @nosklo m'a beaucoup aidé.Sous Linux, j'ai eu le même problème de se débarrasser de la mise en mémoire tampon. J'ai finalement utilisé "stdbuf -o0" (ou, unbuffer from expect) pour me débarrasser du tampon PIPE.
proc = Popen(['stdbuf', '-o0'] + cmd, stdout=PIPE, stderr=PIPE) stdout = proc.stdout
Je pourrais alors utiliser select.select sur stdout.
Voir aussi /unix/25372/
la source
stdbuf -o0
s'est avéré vraiment utile avec un tas de tests pytest / pytest-bdd que j'ai écrits qui génèrent une application C ++ et vérifient qu'elle émet certaines instructions de journal. Sansstdbuf -o0
, ces tests ont nécessité 7 secondes pour obtenir la sortie (mise en mémoire tampon) du programme C ++. Maintenant, ils fonctionnent presque instantanément!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 transmettez-le dans l'
env
argument àPopen
.Sinon, si vous êtes sous Linux / Unix, vous pouvez utiliser l'
stdbuf
outil. Par exemple, comme:cmd = ["stdbuf", "-oL"] + cmd
Voir aussi ici à propos
stdbuf
ou d'autres options.la source
for line in p.stdout: ...
bloque toujours jusqu'au prochain saut de ligne.
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 enfant ferme sa sortie standard ou se termine.
read()/read(-1)
bloquerait jusqu'à ce que le processus fils ferme sa sortie stdout ou quitte.la source
inchar
n'est jamaisNone
utilisé à laif not inchar:
place (read()
renvoie une chaîne vide sur EOF). btw, c'est pirefor line in p.stdout
ne pas imprimer même des lignes complètes en temps réel dans Python 2 (for line in
iter (p.stdout.readline, '') `pourrait être utilisé à la place).for line in p.stdout:
fonctionne sur Python 3. Assurez-vous de bien comprendre la différence entre''
(chaîne Unicode) etb''
(octets). Voir Python: lire l'entrée en continu de subprocess.communicate ()Votre problème est:
for line in p.stdout: print(">>> " + str(line.rstrip())) p.stdout.flush()
l'itérateur lui-même a une mémoire tampon supplémentaire.
Essayez de faire comme ceci:
while True: line = p.stdout.readline() if not line: break print line
la source
Vous ne pouvez pas obtenir stdout pour imprimer sans tampon sur un tube (à moins que vous ne puissiez réécrire le programme qui imprime sur stdout), voici donc ma solution:
Redirigez stdout vers sterr, qui n'est pas mis en mémoire tampon.
'<cmd> 1>&2'
devrait le faire. Ouvrez le processus comme suit:myproc = subprocess.Popen('<cmd> 1>&2', stderr=subprocess.PIPE)
Vous ne pouvez pas faire la distinction entre stdout ou stderr, mais vous obtenez immédiatement toutes les sorties.
J'espère que cela aidera quiconque à résoudre ce problème.
la source
1>&2
change simplement les fichiers vers lesquels pointent les descripteurs de fichiers avant de lancer le programme. Le programme lui-même ne peut pas faire la distinction entre la redirection de stdout vers stderr (1>&2
) ou vice-versa (2>&1
) donc cela n'aura aucun effet sur le comportement de mise en mémoire tampon du programme. Et de toute façon, la1>&2
syntaxe est interprétée par le shell.subprocess.Popen('<cmd> 1>&2', stderr=subprocess.PIPE)
échouerait parce que vous ne l'avez pas spécifiéshell=True
.Modifiez la sortie stdout du processus rsync pour qu'elle ne soit pas tamponnée.
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)
la source
Pour éviter la mise en cache de la sortie, vous pouvez 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.
PPS : a obtenu cette réponse d'une autre question
la source
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 préoccupe depuis plusieurs jours jusqu'à ce que je trouve cela dans pyDoc.
Il semble que rsync affichera '\ r' lorsque la traduction est en cours.
la source
J'ai remarqué qu'il n'est pas fait mention de l'utilisation d'un fichier temporaire comme intermédiaire. Ce qui suit permet de contourner les problèmes de mise en mémoire tampon en sortant dans un fichier temporaire et vous permet d'analyser les données provenant de rsync sans vous connecter à un pty. J'ai testé ce qui suit sur une boîte 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...
la source
while not p.poll()
conduit à une boucle infinie si le sous-processus sep.poll() is None
open(file_name)
peut échouercommand_argv = ["stdbuf","-i0","-o0","-e0"] + command_argv
et appelle:popen = subprocess.Popen(cmd, stdout=subprocess.PIPE)
et maintenant je peux lire sans aucune mise en mémoire tamponsi vous exécutez quelque chose comme ça dans un thread et enregistrez la propriété ffmpeg_time dans une propriété d'une méthode afin que vous puissiez y accéder, cela fonctionnerait très bien.J'obtiens des sorties comme celle-ci: la sortie est comme si vous utilisez le threading dans tkinter
input = 'path/input_file.mp4' output = 'path/input_file.mp4' command = "ffmpeg -y -v quiet -stats -i \"" + str(input) + "\" -metadata title=\"@alaa_sanatisharif\" -preset ultrafast -vcodec copy -r 50 -vsync 1 -async 1 \"" + output + "\"" process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, shell=True) for line in self.process.stdout: reg = re.search('\d\d:\d\d:\d\d', line) ffmpeg_time = reg.group(0) if reg else '' print(ffmpeg_time)
la source
Dans Python 3, voici une solution, qui supprime une commande de la ligne de commande et délivre en temps réel des chaînes bien décodées à mesure qu'elles sont reçues.
Receveur (
receiver.py
):import subprocess import sys cmd = sys.argv[1:] p = subprocess.Popen(cmd, stdout=subprocess.PIPE) for line in p.stdout: print("received: {}".format(line.rstrip().decode("utf-8")))
Exemple de programme simple qui pourrait générer une sortie en temps réel (
dummy_out.py
):import time import sys for i in range(5): print("hello {}".format(i)) sys.stdout.flush() time.sleep(1)
Production:
$python receiver.py python dummy_out.py received: hello 0 received: hello 1 received: hello 2 received: hello 3 received: hello 4
la source