Подпроцессы 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, и поэтому он не будет закрываться автоматически.