Подпроцессы python readlines() зависает
Задача, которую я пытаюсь выполнить, - это поток рубинового файла и распечатка вывода. ( ПРИМЕЧАНИЕ: я не хочу распечатывать все сразу)
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!"
Проблема
Поток файлов работает нормально. Выход приветствия/прощания печатается с задержкой в 2 секунды. Точно как script должен работать. Проблема в том, что readline() зависает в конце и никогда не завершает работу. Я никогда не дошел до последней печати.
Я знаю, что здесь много вопросов, таких как stackoverflow, но не из-за них я решил решить эту проблему. Я не так во всей субпроцессе, поэтому, пожалуйста, дайте мне более практичный/конкретный ответ.
Привет
изменить
Исправить непреднамеренный код. (ничего общего с фактической ошибкой)
Ответы
Ответ 1
Я предполагаю, что вы используете pty
по причинам, изложенным в Q: Почему бы просто не использовать pipe (popen())? ( все остальные ответы до сих пор игнорируют ваш "ПРИМЕЧАНИЕ: я не хочу распечатывать все сразу" ).
pty
является только Linux как сказано в документах:
Поскольку псевдотерминальная обработка сильно зависит от платформы, это код для этого только для Linux. (Предполагается, что код Linux будет работать на других платформах, но еще не протестирован.)
Неясно, насколько хорошо он работает на других ОС.
Вы можете попробовать pexpect
:
import sys
import pexpect
pexpect.run("ruby ruby_sleep.rb", logfile=sys.stdout)
Или stdbuf
, чтобы включить буферизацию строк в неинтерактивном режиме:
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()
Или используя pty
из stdlib на основе @Ответ Антти Хаапалы:
#!/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 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!")
Все три примера кода печатают "привет" сразу (как только первый EOL замечен).
оставьте здесь старый более сложный пример кода, потому что он может ссылаться и обсуждаться в других сообщениях на SO
Или используя pty
на основе @Antti Haapala answer:
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 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!")
Ответ 2
В основном вы смотрите здесь состояние гонки между вашим proc.poll()
и вашим readline()
. Поскольку вход в дескриптор файла master
никогда не закрывается, если процесс пытается сделать readline()
на нем после завершения процесса ruby, считывания никогда не будет, но труба никогда не будет закрыта. Код будет работать только в том случае, если процесс оболочки завершится до того, как ваш код попробует другой readline().
Вот график:
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).
Простое исправление заключается в том, чтобы просто использовать модуль подпроцесса, как он предлагает в документах, а не в сочетании с openpty:
http://docs.python.org/library/subprocess.html
Вот очень похожая проблема для дальнейшего изучения:
Использование подпроцесса с select и pty зависает при захвате вывода
Ответ 3
Не уверен, что не так с вашим кодом, но, похоже, для меня работает следующее:
#!/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!"
Обратите внимание: я не установил Ruby и, следовательно, не могу проверить вашу актуальную проблему. Однако работает отлично с ls
.
Ответ 4
Попробуйте следующее:
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!")
Как отмечали другие, readline()
будет блокироваться при чтении данных. Это даже сделает это, когда ваш детский процесс умер. Я не уверен, почему этого не происходит при выполнении ls
, как в другом ответе, но, возможно, интерпретатор ruby обнаруживает, что он записывает в PIPE, и поэтому он не будет закрываться автоматически.