Interactive input/output using Python Interactive input/output using Python python python

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