Читайте из 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)
Другие вопросы по тегам