Интерактивный ввод/вывод с использованием python
У меня есть программа, которая взаимодействует с пользователем (действует как оболочка), и я хочу запустить ее с помощью модуля подпроцесса python в интерактивном режиме.
Это означает, что я хочу возможность написать stdin и сразу получить вывод из stdout. Я пробовал много решений, предлагаемых здесь, но никто из них, похоже, не работает для моих нужд.
Код, который я написал на основе Запуск интерактивной команды изнутри python
import Queue
import threading
import subprocess
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 Queue.Empty:
return outStr
p = subprocess.Popen("./a.out", stdin=subprocess.PIPE, stout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize = 1)
#p = subprocess.Popen("./a.out", stdin=subprocess.PIPE, stout=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()
p.stdin.write("1\n")
p.stdin.flush()
errors = getOutput(errQueue)
output = getOutput(outQueue)
p.stdin.write("5\n")
p.stdin.flush()
erros = getOutput(errQueue)
output = getOutput(outQueue)
Проблема в том, что очередь остается пустой, как будто нет выхода.
Только если я напишу stdin все входные данные, которые программа должна выполнить и завершить, тогда я получаю вывод (что не то, что я хочу). Например, если я делаю что-то вроде:
p.stdin.write("1\n5\n")
errors = getOutput(errQueue)
output = getOutput(outQueue)
Есть ли способ сделать то, что я хочу сделать?
EDIT:
script будет запущен на машине Linux.
Я изменил свой script и удалил universal_newlines = True + установил bufsize в 1 и сбросил stdin сразу после wrtie. Тем не менее я не получаю выход.
Вторая попытка:
Я пробовал это решение, и оно работает для меня:
from subprocess import Popen, PIPE
fw = open("tmpout", "wb")
fr = open("tmpout", "r")
p = Popen("./a.out", stdin = PIPE, stdout = fw, stderr = fw, bufsize = 1)
p.stdin.write("1\n")
out = fr.read()
p.stdin.write("5\n")
out = fr.read()
fw.close()
fr.close()
Ответы
Ответ 1
Два решения для этой проблемы в Linux:
Сначала нужно использовать файл для записи вывода и чтения из него одновременно:
from subprocess import Popen, PIPE
fw = open("tmpout", "wb")
fr = open("tmpout", "r")
p = Popen("./a.out", stdin = PIPE, stdout = fw, stderr = fw, bufsize = 1)
p.stdin.write("1\n")
out = fr.read()
p.stdin.write("5\n")
out = fr.read()
fw.close()
fr.close()
Во-вторых, как предложил Я. Ф. Себастьян, сделать блокировку p.stdout и p.stderr неблокирующими с помощью модуля fnctl:
import os
import fcntl
from subprocess import Popen, PIPE
def setNonBlocking(fd):
"""
Set the file description of the given file descriptor to non-blocking.
"""
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
flags = flags | os.O_NONBLOCK
fcntl.fcntl(fd, fcntl.F_SETFL, flags)
p = Popen("./a.out", stdin = PIPE, stdout = PIPE, stderr = PIPE, bufsize = 1)
setNonBlocking(p.stdout)
setNonBlocking(p.stderr)
p.stdin.write("1\n")
while True:
try:
out1 = p.stdout.read()
except IOError:
continue
else:
break
out1 = p.stdout.read()
p.stdin.write("5\n")
while True:
try:
out2 = p.stdout.read()
except IOError:
continue
else:
break
Ответ 2
Ни один из текущих ответов не сработал для меня. В конце концов, у меня есть это работает:
import subprocess
def start(executable_file):
return subprocess.Popen(
executable_file,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
def read(process):
return process.stdout.readline().decode("utf-8").strip()
def write(process, message):
process.stdin.write(f"{message.strip()}\n".encode("utf-8"))
process.stdin.flush()
def terminate(process):
process.stdin.close()
process.terminate()
process.wait(timeout=0.2)
process = start("./dummy.py")
write(process, "hello dummy")
print(read(process))
terminate(process)
Протестировано с помощью этого скрипта dummy.py
:
#!/usr/bin/env python3.6
import random
import time
while True:
message = input()
time.sleep(random.uniform(0.1, 1.0)) # simulates process time
print(message[::-1])
Предостережения: ввод/вывод всегда строки с новой строкой, очистка дочернего stdin после каждой записи и использование readline()
из дочернего stdout (все это управляется в функциях).
На мой взгляд, это довольно простое решение (не мое, я нашел его здесь: https://eli.thegreenplace.net/2017/interacting-with-a-long-running-child-process-in-python/). Я использовал Python 3.6.
Ответ 3
Вот интерактивная оболочка. Вы должны запустить read() в отдельном потоке, иначе он заблокирует write()
import sys
import os
import subprocess
from subprocess import Popen, PIPE
import threading
class LocalShell(object):
def __init__(self):
pass
def run(self):
env = os.environ.copy()
p = Popen('/bin/bash', stdin=PIPE, stdout=PIPE, stderr=subprocess.STDOUT, shell=True, env=env)
sys.stdout.write("Started Local Terminal...\r\n\r\n")
def writeall(p):
while True:
# print("read data: ")
data = p.stdout.read(1).decode("utf-8")
if not data:
break
sys.stdout.write(data)
sys.stdout.flush()
writer = threading.Thread(target=writeall, args=(p,))
writer.start()
try:
while True:
d = sys.stdin.read(1)
if not d:
break
self._write(p, d.encode())
except EOFError:
pass
def _write(self, process, message):
process.stdin.write(message)
process.stdin.flush()
shell = LocalShell()
shell.run()