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() зависает в конце и никогда не завершается. Я никогда не доходил до последнего отпечатка.

Я знаю, что здесь много таких вопросов, как переполнение стека, но ни один из них не помог мне решить проблему. Я не очень разбираюсь во всем этом подпроцессе, поэтому, пожалуйста, дайте мне более практический / конкретный ответ.

С уважением

редактировать

Исправьте непреднамеренный код. (ничего общего с фактической ошибкой)

4 ответа

Решение

Я полагаю, вы используете pty по причинам, изложенным в Q: Почему бы просто не использовать канал (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!")

Не уверен, что не так с вашим кодом, но мне кажется, что работает следующее:

#!/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, хоть.

В основном то, на что вы смотрите, это состояние гонки между вашими 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 зависает при захвате вывода

Попробуй это:

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 ​​обнаружит, что пишет в ТРУБКУ, и поэтому он не закроется автоматически.

Другие вопросы по тегам