Entrada / salida interactiva utilizando python

Tengo un progtwig que interactúa con el usuario (actúa como un shell) y quiero ejecutarlo de forma interactiva utilizando el módulo de subproceso de Python. Eso significa que quiero la posibilidad de escribir en stdin e inmediatamente obtener la salida de stdout. Intenté muchas soluciones ofrecidas aquí, pero ninguna de ellas parece funcionar para mis necesidades.

El código que escribí basado en Ejecutar un comando interactivo desde 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) 

El problema es que la cola permanece vacía, como si no hubiera salida. Solo si escribo en stdin todas las entradas que el progtwig necesita para ejecutar y terminar, obtengo la salida (que no es lo que quiero). Por ejemplo si hago algo como:

 p.stdin.write("1\n5\n") errors = getOutput(errQueue) output = getOutput(outQueue) 

¿Hay alguna manera de hacer lo que quiero hacer?

EDITAR: El script se ejecutará en una máquina Linux. Cambié mi script y eliminé universal_newlines = True + configuré bufsize a 1 y borré la entrada estándar inmediatamente después de wrtie. Todavía no tengo salida.

Segundo bash: probé esta solución y me funciona:

 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() 

Dos soluciones para este problema en Linux:

El primero es usar un archivo para escribir el resultado y leerlo simultáneamente:

 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() 

En segundo lugar, como ofreció JF Sebastian, es hacer que las tuberías p.stdout y p.stderr no se bloqueen utilizando el módulo 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 

Ninguna de las respuestas actuales funcionó para mí. Al final, tengo este trabajo:

 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) 

Probado con este script 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]) 

Las advertencias son: entrada / salida siempre líneas con nueva línea, vaciar la entrada estándar del niño después de cada escritura, y usar readline() desde la salida estándar del niño (todo eso administrado en las funciones).

Es una solución bastante simple, en mi opinión (no la mía, la encontré aquí: https://eli.thegreenplace.net/2017/interacting-with-a-long-running-child-process-in-python/ ). Yo estaba usando Python 3.6.

Aquí hay una shell interactiva. Tienes que ejecutar read () en un subproceso separado, de lo contrario bloqueará la escritura ()

 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()