как отправить клавишу табуляции в стандартный ввод подпроцесса python

Предыстория: у меня есть подпроцесс Python, который подключается к приложению, похожему на оболочку, которое использует библиотеку readline для обработки ввода, и это приложение имеет подпрограмму TAB-complete для ввода команд, как bash. Дочерний процесс порождается, например:

def get_cli_subprocess_handle():
    return subprocess.Popen(
                            '/bin/myshell',
                            shell=False,
                            stdin=subprocess.PIPE,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT,
                            )

Все отлично работает, кроме табуляции. Всякий раз, когда моя программа на Python передает символ табуляции '\t' в подпроцесс, я получаю 5 пробелов в стандартном вводе STDIN вместо запуска процедуры завершения табуляции библиотеки readline. :(

Вопрос. Что я могу отправить в STDIN подпроцесса, чтобы вызвать дочернюю функцию завершения табуляции? Возможно, спросили по-другому: как отправить ключ TAB, а не символ TAB, если это даже возможно?

Похоже, но без ответа и отклонено:

инициировать завершение табуляции для пакетного процесса python, построенного вокруг строки чтения


person Trevor    schedule 28.01.2013    source источник
comment
Вероятно, это не ответ (конечно, это неадекватное решение), но почему бы вам не проверить, являются ли входные данные 5 последовательными пробелами? Вероятно, последовательность escape \t захвачена вашей оболочкой и переведена в 5 пробелов.   -  person Manuel    schedule 28.01.2013
comment
@Manuel - На самом деле, я сам ввожу '\t' через re.sub(), поэтому я знаю, что это не 5 пробелов. Спасибо за двойную проверку!   -  person Trevor    schedule 29.01.2013


Ответы (3)


Приложение, похожее на оболочку, вероятно, различает терминал, подключенный к стандартному вводу, и канал, подключенный к нему. Многие утилиты Unix делают именно это, чтобы оптимизировать свою буферизацию (строка или блок), а утилиты, подобные оболочке, скорее всего, отключат средства завершения команд при пакетном вводе (например, PIPE), чтобы избежать непредвиденных результатов. Завершение команды на самом деле является интерактивной функцией, которая требует ввода с терминала.

Ознакомьтесь с модулем pty и попробуйте использовать пару ведущий/подчиненный в качестве канала для вашего подпроцесса.

person isedev    schedule 28.01.2013
comment
Спасибо, Иседев! Я подключился через masterPTY, slaveTTY с помощью метода pty.openpty(), а затем смог отправить ключ табуляции в соответствии с моими требованиями. ... Я добавил ответ ниже, подробно описав применение вашего предложения к моему делу. - person Trevor; 29.01.2013

На самом деле нет такой вещи, как отправка табуляции key в канал. Канал может принимать только строки битов, и если символ табуляции этого не делает, решения может и не быть.

Существует проект, который делает нечто подобное, под названием pexpect. Просто глядя на его код interact(), я не вижу ничего очевидного, что заставляет его работать, а ваш нет. Учитывая это, наиболее вероятным объяснением является то, что pexpect действительно выполняет некоторую работу, чтобы выглядеть как псевдотерминал. Возможно, вы могли бы включить его код для этого?

person Ken Kinder    schedule 28.01.2013
comment
Спасибо, Кен! Проект pexpect был действительно полезен, и ваше объяснение было правильным. - person Trevor; 29.01.2013

Основываясь на исевском ответе, я изменил свой код следующим образом:

import os, pty

def get_cli_subprocess_handle():
    masterPTY, slaveTTY = pty.openpty()
    return masterPTY, slaveTTY, subprocess.Popen(
                                                 '/bin/myshell',
                                                 shell=False,
                                                 stdin=slaveTTY,
                                                 stdout=slaveTTY,
                                                 stderr=slaveTTY,
                                                 )

Используя этот возвращенный кортеж, я смог выполнить select.select([masterPTY],[],[]) и os.read(masterPTY, 1024) по мере необходимости и написал мастеру-pty функцию, очень похожую на приватный метод в исходниках модуля pty:

def write_all(masterPTY, data):
    """Successively write all of data into a file-descriptor."""
    while data:
        chars_written = os.write(masterPTY, data)
        data = data[chars_written:]
    return data

Спасибо всем за хорошие решения. Надеюсь, этот пример поможет кому-то еще. :)

person Trevor    schedule 29.01.2013