Вопрос-Ответ

Python subprocess readlines() hangs

Зависает подпроцесс 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.

2023-10-13 18:45 python subprocess