Запуск интерактивной команды изнутри python

У меня есть script, который я хочу запустить из python (2.6.5), который следует за логикой ниже:

  • Запрос пароля. Похож ( "Введите пароль:" ) (* Примечание: вход не отображается на экране)
  • Вывести нерелевантную информацию
  • Запросить пользователя для ответа ( "Blah Blah filename.txt blah blah (Y/N)?:" )

В последней строке приглашения содержится текст, который мне нужно проанализировать (filename.txt). Предоставленный ответ не имеет значения (программа действительно могла выйти из нее без предоставления одного, если я могу разобрать строку)

Мои требования несколько схожи с Обтекание приложения интерактивной командной строки в python script. Может быть, им немного толще, но ответы там кажутся немного запутанными, а мой все еще висит, даже когда OP упоминает, что это не для него.

Посмотрев вокруг, я пришел к выводу, что подпроцесс - лучший способ сделать это, но у меня есть несколько проблем. Строка POpen ниже

p = subprocess.Popen("cmd", shell=True, stdout=subprocess.PIPE, 
stderr=subprocess.STDOUT, stdin=subprocess.PIPE)
  • Когда я вызываю read() или readline() на stdout, приглашение - это принтер на экране, и он зависает.

  • Если я вызываю запись ( "password\n" ) для stdin, приглашение записывается на экран, и оно зависает. Текст в write() не написан (я не курсор перемещает новую строку).

  • Если я вызываю p.communicate( "password\n" ), то такое же поведение, как write()

Я искал несколько идей здесь, чтобы наилучшим образом ввести stdin и, возможно, проанализировать последнюю строку на выходе, если вы чувствуете себя щедрым, хотя я, вероятно, мог бы понять это в конечном итоге. Спасибо!

Ответ 1

Если вы общаетесь с программой, которую создает подпроцесс, вы должны проверить Неблокирование чтения на подпроцессе .PIPE в python. У меня была аналогичная проблема с моим приложением и я обнаружил, что использование очередей является лучшим способом для постоянного общения с подпроцессом.

Что касается получения значений от пользователя, вы всегда можете использовать встроенную функцию raw_input() для получения ответов и для паролей, попробуйте использовать getpass, чтобы получить неэховые пароли от вашего пользователя. Затем вы можете проанализировать эти ответы и записать их в свой подпроцесс 'stdin.

В итоге я сделал что-то похожее на следующее:

import sys
import subprocess
from threading  import Thread

try:
    from Queue import Queue, Empty
except ImportError:
    from queue import Queue, Empty  # python 3.x


def enqueue_output(out, queue):
    for line in iter(out.readline, b''):
        queue.put(line)
    out.close()


def getOutput(outQueue):
    outStr = ''
    try:
        while True: #Adds output from the Queue until it is empty
            outStr+=outQueue.get_nowait()

    except Empty:
        return outStr

p = subprocess.Popen("cmd", stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False, universal_newlines=True)

outQueue = Queue()
errQueue = Queue()

outThread = Thread(target=enqueue_output, args=(p.stdout, outQueue))
errThread = Thread(target=enqueue_output, args=(p.stderr, errQueue))

outThread.daemon = True
errThread.daemon = True

outThread.start()
errThread.start()

try:
    someInput = raw_input("Input: ")
except NameError:
    someInput = input("Input: ")

p.stdin.write(someInput)
errors = getOutput(errQueue)
output = getOutput(outQueue)

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

Ответ 2

Использование потоковой передачи может быть немного излишним для простых задач. Вместо этого можно использовать os.spawnvpe. Он будет порождать оболочку script как процесс. Вы сможете интерактивно взаимодействовать с script. В этом примере я передал пароль как аргумент, очевидно, это не очень хорошая идея.

import os
import sys
from getpass import unix_getpass

def cmd(cmd):
    cmd = cmd.split()
    code = os.spawnvpe(os.P_WAIT, cmd[0], cmd, os.environ)
    if code == 127:
        sys.stderr.write('{0}: command not found\n'.format(cmd[0]))
    return code

password = unix_getpass('Password: ')
cmd_run = './run.sh --password {0}'.format(password)
cmd(cmd_run)

pattern = raw_input('Pattern: ')
lines = []
with open('filename.txt', 'r') as fd:
    for line in fd:
        if pattern in line:
            lines.append(line)

# manipulate lines