La tâche que j'essaie d'accomplir est de diffuser un fichier Ruby et d'imprimer la sortie. ( [~ # ~] note [~ # ~] : Je ne veux pas tout imprimer en même temps)
main.py
from subprocess import Popen, PIPE, STDOUT
import pty
import os
file_path = '/Users/luciano/Desktop/Ruby_sleep.rb'
command = ' '.join(["Ruby", file_path])
master, slave = pty.openpty()
proc = Popen(command, bufsize=0, Shell=True, stdout=slave, stderr=slave, close_fds=True)
stdout = os.fdopen(master, 'r', 0)
while proc.poll() is None:
data = stdout.readline()
if data != "":
print(data)
else:
break
print("This is never reached!")
Ruby_sleep.rb
puts "hello"
sleep 2
puts "goodbye!"
Problème
La diffusion du fichier fonctionne correctement. La sortie Bonjour/Au revoir est imprimée avec un délai de 2 secondes. Exactement comme le script devrait fonctionner. Le problème est que readline () se bloque à la fin et ne se ferme jamais. Je n'atteins jamais le dernier tirage.
Je sais qu'il y a beaucoup de questions comme celle-ci ici un stackoverflow mais aucune d'elles ne m'a fait résoudre le problème. Je ne suis pas dans le sous-processus, alors donnez-moi une réponse plus pratique/concrète.
Cordialement
éditer
Correction du code involontaire. (rien à voir avec l'erreur réelle)
Je suppose que vous utilisez pty
pour les raisons décrites dans Q: Pourquoi ne pas simplement utiliser un tuyau (popen ())? (toutes les autres réponses jusqu'à présent ignorent votre "REMARQUE: je ne veux pas tout imprimer en même temps" ).
pty
est Linux uniquement comme dit dans la documentation :
Étant donné que la gestion des pseudo-terminaux dépend fortement de la plate-forme, il existe du code pour le faire uniquement pour Linux. (Le code Linux est censé fonctionner sur d'autres plates-formes, mais n'a pas encore été testé.)
On ne sait pas dans quelle mesure cela fonctionne sur d'autres systèmes d'exploitation.
Vous pouvez essayer pexpect
:
import sys
import pexpect
pexpect.run("Ruby ruby_sleep.rb", logfile=sys.stdout)
Ou stdbuf
pour activer la mise en mémoire tampon des lignes en mode non interactif:
from subprocess import Popen, PIPE, STDOUT
proc = Popen(['stdbuf', '-oL', 'Ruby', 'Ruby_sleep.rb'],
bufsize=1, stdout=PIPE, stderr=STDOUT, close_fds=True)
for line in iter(proc.stdout.readline, b''):
print line,
proc.stdout.close()
proc.wait()
Ou en utilisant pty
de stdlib basé sur @ Antti Haapala's answer :
#!/usr/bin/env python
import errno
import os
import pty
from subprocess import Popen, STDOUT
master_fd, slave_fd = pty.openpty() # provide tty to enable
# line-buffering on Ruby's side
proc = Popen(['Ruby', 'Ruby_sleep.rb'],
stdin=slave_fd, stdout=slave_fd, stderr=STDOUT, close_fds=True)
os.close(slave_fd)
try:
while 1:
try:
data = os.read(master_fd, 512)
except OSError as e:
if e.errno != errno.EIO:
raise
break # EIO means EOF on some systems
else:
if not data: # EOF
break
print('got ' + repr(data))
finally:
os.close(master_fd)
if proc.poll() is None:
proc.kill()
proc.wait()
print("This is reached!")
Les trois exemples de code affichent "bonjour" immédiatement (dès que le premier EOL est vu).
laissez l'ancien exemple de code plus compliqué ici car il peut être référencé et discuté dans d'autres articles sur SO
Ou en utilisant pty
basé sur @ la réponse d'Antti Haapala :
import os
import pty
import select
from subprocess import Popen, STDOUT
master_fd, slave_fd = pty.openpty() # provide tty to enable
# line-buffering on Ruby's side
proc = Popen(['Ruby', 'Ruby_sleep.rb'],
stdout=slave_fd, stderr=STDOUT, close_fds=True)
timeout = .04 # seconds
while 1:
ready, _, _ = select.select([master_fd], [], [], timeout)
if ready:
data = os.read(master_fd, 512)
if not data:
break
print("got " + repr(data))
Elif proc.poll() is not None: # select timeout
assert not select.select([master_fd], [], [], 0)[0] # detect race condition
break # proc exited
os.close(slave_fd) # can't do it sooner: it leads to errno.EIO error
os.close(master_fd)
proc.wait()
print("This is reached!")
Je ne sais pas ce qui ne va pas avec votre code, mais ce qui suit semble fonctionner pour moi:
#!/usr/bin/python
from subprocess import Popen, PIPE
import threading
p = Popen('ls', stdout=PIPE)
class ReaderThread(threading.Thread):
def __init__(self, stream):
threading.Thread.__init__(self)
self.stream = stream
def run(self):
while True:
line = self.stream.readline()
if len(line) == 0:
break
print line,
reader = ReaderThread(p.stdout)
reader.start()
# Wait until subprocess is done
p.wait()
# Wait until we've processed all output
reader.join()
print "Done!"
Notez que je n'ai pas Ruby installé et que je ne peux donc pas vérifier votre problème réel. Fonctionne bien avec ls
, cependant.
Fondamentalement, ce que vous regardez ici est une condition de concurrence entre votre proc.poll()
et votre readline()
. Étant donné que l'entrée sur le descripteur de fichier master
n'est jamais fermée, si le processus tente de faire une readline()
dessus après que le processus Ruby a terminé la sortie, il ne sera jamais rien à lire, mais le canal ne se fermera jamais. Le code ne fonctionnera que si le processus Shell se ferme avant que votre code n'essaye une autre readline ().
Voici la chronologie:
readline()
print-output
poll()
readline()
print-output (last line of real output)
poll() (returns false since process is not done)
readline() (waits for more output)
(process is done, but output pipe still open and no poll ever happens for it).
La solution facile consiste à simplement utiliser le module de sous-processus comme il le suggère dans la documentation, pas en conjonction avec openpty:
http://docs.python.org/library/subprocess.html
Voici un problème très similaire à approfondir:
L'utilisation du sous-processus avec select et pty se bloque lors de la capture de la sortie
Essaye ça:
proc = Popen(command, bufsize=0, Shell=True, stdout=PIPE, close_fds=True)
for line in proc.stdout:
print line
print("This is most certainly reached!")
Comme d'autres l'ont noté, readline()
se bloquera lors de la lecture des données. Il le fera même lorsque votre processus enfant sera mort. Je ne sais pas pourquoi cela ne se produit pas lors de l'exécution de ls
comme dans l'autre réponse, mais peut-être que l'interpréteur Ruby détecte qu'il écrit sur un PIPE et qu'il ne le fera donc pas se ferme automatiquement.