Search code examples
pythonpython-3.xsubprocess

Stdin control over subprocess


I need a program for controlling server software on a raspberry pi, but I'm stuck on how to provide the stdin for the "controlled" process, such as a Minecraft server, which has a console. In this case, I would need a way to pass "just returned" values into stdin. I've tried PIPEs, tempfiles, doesn't work. With a simple python script (eg. print(input()) ) it told me EOFError: EOF while reading I have no idea why, the tempfiles worked fine for stdout and stderr. Here's my code:

import shlex
import subprocess
import tempfile
import time

class Process():
    def __init__(self, run_command) -> None:
        self.command = run_command
        self._cmd_list = shlex.split(self.command)

    def start(self):
        self._stdout = tempfile.NamedTemporaryFile("w+", encoding="utf+8")
        self._stdin = tempfile.NamedTemporaryFile("a+", encoding="utf+8")
        self._p = subprocess.Popen(self._cmd_list, stdout=self._stdout, stderr=self._stdout, stdin=self._stdin, text=True)

    def get_output(self):
        return self._stdout.read()
    
    def send_input(self, char, newline=True):
        self._stdin.write(char)
        if newline:
            self._stdin.write("\n")
        self._stdin.flush()
    
    def end(self):
        self._p.kill()
        self._stdout.seek(0)
        self.out = self._stdout.read()
        self._stdout.close()
        return self.out
    

if __name__ == "__main__":
    # run tests
    p = Process("python3 test.py")
    p.start()
    time.sleep(0.5)
    p.send_input(input())
    time.sleep(0.5)
    print(p.end())


#where test.py is the simple script above.

Solution

  • tempfiles, doesn't work. With a simple python script (eg. print(input()) ) it told me EOFError: EOF while reading I have no idea why

    That's pretty straightforward: you run a program, that program expects data on stdin, it reads the entire file, which is empty, and is done.

    A file is data at rest, it does not "wait" on the data producer, and can't tell the consumer to do so either (save if both consumer and producer use advisory locks maybe).

    the tempfiles worked fine for stdout and stderr.

    Well of course, the program writes to those as it needs to, as long as they remain available for writing it's happy.

    I've tried PIPEs

    You've probably made a mistake then. Assuming the subcommand does take input over stdin (which isn't always the case mind) this is the normal way to interact.

    Note though that lots of programs have timing or handling issues when you send stuff to stdin in bulk if they're designed to be interactive, they might assume they're receiving one command at a time and not try to process subsets of the buffer. That is why tools like expect and the pexpect Python library exist, to replicate the back and forth of an interactive session where you send a single command, flush, then wait for the prompt or result to come back before resuming.