Читайте из pty без бесконечных зависаний
У меня есть скрипт, который печатает цветной вывод, если он находится на tty. Некоторые из них выполняются параллельно, поэтому я не могу поместить их стандартный вывод в tty. У меня также нет контроля над кодом сценария (для принудительного окрашивания), поэтому я хочу подделать его через pty. Мой код:
invocation = get_invocation()
master, slave = pty.openpty()
subprocess.call(invocation, stdout=slave)
print string_from_fd(master)
И я не могу понять, что должно быть в string_from_fd
, На данный момент у меня есть что-то вроде
def string_from_fd(fd):
return os.read(fd, 1000)
Это работает, но это число 1000
выглядит странно. Я думаю, что результат может быть довольно большим, и любое число может быть недостаточным. Я перепробовал много решений от переполнения стека, но ни одно из них не работает (ничего не печатает или зависает навсегда).
Я не очень знаком с файловыми дескрипторами и всем этим, поэтому любые разъяснения, если я делаю что-то не так, были бы очень благодарны.
Спасибо!
1 ответ
Это не будет работать для длинных выходов: subprocess.call
заблокируется после заполнения буфера PTY. Вот почему subprocess.communicate
существует, но это не будет работать с PTY.
Стандартным / самым простым решением является использование внешнего модуля pexpect, который использует PTY для внутренних целей: например,
pexpect.spawn("/bin/ls --color=auto").read()
даст вам ls
вывод с цветовыми кодами.
Если вы хотите придерживаться subprocess
, то вы должны использовать subprocess.Popen
по причине, указанной выше. Вы правы в своем предположении, что мимоходом 1000
Вы читаете не более 1000 байтов, поэтому вам придется использовать цикл. os.read
блокирует, если нечего читать, и ожидает появления данных. Уловка заключается в том, как распознать, когда процесс завершился: в этом случае вы знаете, что больше данных не поступит. Следующий звонок os.read
заблокирует навсегда. К счастью, операционная система помогает вам обнаружить эту ситуацию: если все файловые дескрипторы псевдотерминала, которые могут использоваться для записи, закрыты, то os.read
либо вернет пустую строку, либо вернет ошибку, в зависимости от ОС. Вы можете проверить это условие и выйти из цикла, когда это произойдет. Теперь последняя часть понимания следующего кода - понять, как открывать дескрипторы файлов и subprocess
идти вместе: subprocess.Popen
внутренние звонки fork()
, который дублирует текущий процесс, включая все открытые файловые дескрипторы, а затем внутри одного из двух путей выполнения вызовов exec()
, что завершает текущий процесс в пользу нового. В другом пути выполнения управление возвращается к вашему скрипту Python. Так после звонка subprocess.Popen
Есть два допустимых файловых дескриптора для подчиненного конца PTY: один относится к порожденному процессу, другой к вашему скрипту Python. Если вы закроете свой файл, то единственный дескриптор файла, который можно использовать для отправки данных на мастер-конец, принадлежит процессу, который был создан. По окончании он закрывается, и PTY входит в состояние, когда read
на главном конце не получится.
Вот код:
import os
import pty
import subprocess
master, slave = pty.openpty()
process = subprocess.Popen("/bin/ls --color", shell=True, stdout=slave,
stdin=slave, stderr=slave, close_fds=True)
os.close(slave)
output = []
while True:
try:
data = os.read(master, 1024)
except OSError:
break
if not data:
break
output.append(data) # In Python 3, append ".decode()" to os.read()
output = "".join(output)