2017-01-29 2 views
2

У меня есть сценарий, который печатает цветной выход, если он находится на tty. Букет из них выполняется параллельно, поэтому я не могу поместить их stdout в tty. Я также не контролирую код сценария (чтобы заставить раскраску), поэтому я хочу подделать его через pty. Мой код:Прочтено от 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 выглядит странно. Я думаю, что выход может быть тихим большим, и любого количества там может быть недостаточно. Я попробовал много решений из переполнения стека, но ни один из них не работает (он ничего не печатает или не висит навсегда).

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

Спасибо!

ответ

3

Это не будет работать для длинных выходов: subprocess.call будет блокироваться после заполнения буфера PTY. Вот почему subprocess.communicate существует, но это не сработает с PTY.

Стандартный/Самым простым решением является использование внешнего модуля pexpect, который использует PTYs внутренне, например,

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) 
+0

хмм, я попробовал, и это не работает. Он просто идет 'while True:' навсегда. –

+0

Я обновил вызов «Popen» таким образом, что порожденный процесс * только * видит PTY.Если это не изменит ситуацию, то: какую ОС вы используете? Вы уверены, что скрипт, который вы пытаетесь запустить, завершает/не блокирует, потому что он ожидает ввода пользователем? Прерывает ли мой пример как-либо, или он работает навсегда? Можете ли вы придумать пример команды, которую вы готовы поделиться, где цикл не заканчивается? С помощью какой версии Python? (Для целей отладки попробуйте добавить 'print repr (output [-1])' внутри цикла while.) – Phillip

+0

Я запускаю OS X 10.12, Python 2.7.10. Даже с вашим примером, '/ bin/ls --color', это stucks. 'print repr (output [-1])' бесконечно пишет '' '' - просто пустую строку. –

Смежные вопросы