Interactive input/output using Python
Two solutions for this issue on Linux:
First one is to use a file to write the output to, and read from it simultaneously:
from subprocess import Popen, PIPEfw = 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()
Second, as J.F. Sebastian offered, is to make p.stdout and p.stderr pipes non-blocking using fnctl module:
import osimport fcntlfrom 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: breakout1 = p.stdout.read()p.stdin.write("5\n")while True: try: out2 = p.stdout.read() except IOError: continue else: break
None of the current answers worked for me. At the end, I've got this working:
import subprocessdef 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)
Tested with this dummy.py
script:
#!/usr/bin/env python3.6import randomimport timewhile True: message = input() time.sleep(random.uniform(0.1, 1.0)) # simulates process time print(message[::-1])
The caveats are (all managed in the functions):
- Input/output always lines with newline.
- Flush child's stdin after every write.
- Use
readline()
from child's stdout.
It's a pretty simple solution in my opinion (not mine, I found it here: https://eli.thegreenplace.net/2017/interacting-with-a-long-running-child-process-in-python/). I was using Python 3.6.
Here is an interactive shell. You have to run read() on a separate thread, otherwise it will block the write()
import sysimport osimport subprocessfrom subprocess import Popen, PIPEimport threadingclass 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()