Зависает подпроцесс Python readlines()
Задача, которую я пытаюсь выполнить, - передать файл ruby в потоковом режиме и распечатать выходные данные. (ПРИМЕЧАНИЕ: я не хочу распечатывать все сразу)
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!"
Проблема
Потоковая передача файла работает нормально. Выходные данные hello / goodbye выводятся с задержкой в 2 секунды. Именно так и должен работать скрипт. Проблема в том, что readline() зависает в конце и никогда не завершается. Я никогда не добираюсь до последней печати.
Я знаю, что здесь много подобных вопросов, связанных с stackoverflow, но ни один из них не заставил меня решить проблему. Я не разбираюсь во всем этом подпроцессе, поэтому, пожалуйста, дайте мне более практический / конкретный ответ.
С уважением
Редактировать
Исправлен непреднамеренный код. (ничего общего с фактической ошибкой)
Переведено автоматически
Ответ 1
Я предполагаю, что вы используете pty
по причинам, изложенным в Вопрос: Почему бы просто не использовать канал (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 на основе ответа @Antti Haapala:
#!/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!")
Все три примера кода выводят 'hello' немедленно (как только появляется первый EOL).
оставим здесь старый, более сложный пример кода, потому что на него можно ссылаться и обсуждать в других сообщениях на SO
Или с использованием pty
на основе ответа @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!")
Ответ 2
Не уверен, что не так с вашим кодом, но, похоже, у меня работает следующее:
#!/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
работает нормально.
Ответ 3
По сути, то, на что вы смотрите здесь, - это состояние гонки между вашим 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 зависает при захвате выходных данных
Ответ 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!")
As others have noted, readline()
will block when reading data. It will even do so when your child process has died. I am not sure why this does not happen when executing ls
as in the other answer, but maybe the ruby interpreter detects that it is writing to a PIPE and therefore it will not close automatically.