Стандартный вывод подпроцесса Python не захватывает приглашение ввода

Я использую подпроцесс для порождения команды conda create и захвата полученного stdout для последующего использования. Я также немедленно вывожу stdout в консоль, чтобы пользователь все еще мог видеть ход выполнения подпроцесса:

p = subprocess.Popen('conda create -n env1 python', stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
for line in iter(p.stdout.readline, b''):
    sys.stdout.write(line.decode(sys.stdout.encoding))

Это работает нормально до середины выполнения, когда conda create требует ввода пользователя: он запрашивает Proceed (n/y)? и ждет, пока пользователь введет параметр. Однако приведенный выше код не печатает приглашение, а просто ожидает ввода на пустом экране. После получения ввода приглашение печатается после этого, а затем выполнение продолжается, как и ожидалось.

Я предполагаю, что это связано с тем, что вход каким-то образом блокирует запись подсказки в stdout, и поэтому readline не получает новые данные до тех пор, пока блок ввода не будет снят.

Есть ли способ убедиться, что приглашение на ввод будет напечатано до того, как подпроцесс будет ожидать ввода данных пользователем? Обратите внимание, что я работаю в Windows.


person Mrfence    schedule 28.07.2020    source источник
comment
Обратите внимание, что readline на самом деле ищет всю строку до символа конца строки \n. Подсказка Proceed (n/y)? y не выводит всю строку, а ожидает ввода (y) в строке.   -  person MisterMiyagi    schedule 28.07.2020
comment
Вы поднимаете хороший вопрос. Вероятно, это простое исправление с использованием read вместо этого?   -  person Mrfence    schedule 28.07.2020


Ответы (2)


Хотя я уверен, что pexpect сработал бы в этом случае, я решил, что это будет излишеством. Вместо этого я воспользовался проницательностью MisterMiyagi и заменил readline на read.

Окончательный код таков:

p = subprocess.Popen('conda create -n env1 python', stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while p.poll() is None:
    sys.stdout.write(p.stdout.read(1).decode(sys.stdout.encoding))
    sys.stdout.flush()

Обратите внимание на read(1), так как простое использование read() блокирует цикл while до тех пор, пока не будет найден EOF. Если перед приглашением ввода не найден EOF, вообще ничего не будет напечатано! Кроме того, вызывается flush, что гарантирует, что текст, записанный в sys.stdout, действительно виден на экране.

person Mrfence    schedule 28.07.2020

В этом случае я рекомендую использовать pexpect. стандартный ввод != стандартный вывод

Пример использования, когда он условно отправляет на stdin запросы на stdout

def expectgit(alog):
    TMPLOG = "/tmp/pexpect.log"
    cmd = f'''
ssh -T [email protected] ;\
echo "alldone" ;
'''
    with open(TMPLOG, "w") as log:
        ch = pexpect.spawn(f"/bin/bash -c \"{cmd}\"", encoding='utf-8', logfile=log)
        while True:
            i = ch.expect(["successfully authenticated", "Are you sure you want to continue connecting"])
            if i == 0:
                alog.info("Git all good")
                break
            elif i == 1:
                alog.info("Fingerprint verification")
                ch.send("yes\r")
        ch.expect("alldone")
        i = ch.expect([pexpect.EOF], timeout=5)
        ch.close()
        alog.info("expect done - EOF")

    with open(TMPLOG) as log:
        for l in log.readlines():
            alog.debug(l.strip())
person Rob Raymond    schedule 28.07.2020