Запуск интерактивной команды изнутри 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