简体   繁体   中英

how can I write to a processes stdin and read from its stdout while it is still running?

Edit: I am running this on windows as the servers are administered on my main gaming rig

I am trying to create a minecraft server controller so that I can streamline the process of administering servers. I have been able to use subprocess to start and stop the server with no issues however when I try to get it to issue commands It will not. Unless I use subprocess.communicate() the problem with this solution however is that it causes the script to wait forever and eventually crash, leaving the server running. Here is the code for what I have.

import subprocess


class Server:
    def __init__(self, server_start_bat, dir):
        self.server_start_bat = server_start_bat
        self.dir = dir

    def start_server(self):
        self.server = subprocess.Popen(self.server_start_bat, cwd=self.dir, shell=True, stdin=subprocess.PIPE,
                                       universal_newlines=True, text=True)

    def stop_server(self):
        self.run_command('stop')
        self.server.communicate()

    def op_me(self):
        self.run_command(f'op Username')

    def clear_weather(self):
        self.run_command(f'weather clear 1000000')

    def mob_griefing_on(self):
        self.run_command(f'gamerule mobGriefing True')

    def mob_griefing_off(self):
        self.run_command(f'gamerule mobGriefing True')

    def set_time_day(self):
        self.run_command(f'time set day')

    def set_time_night(self):
        self.run_command(f'time set night')

    def run_command(self, command_text):
        self.server.stdin.write(f'{command_text}\n')

Edit: Check out Non-blocking read on a subprocess.PIPE in python

I was battling a very similar issue and it took me 3 days to get it all running. The problem with subprocess.communicate is that you can only call it once, and the output seems to be given only once the subprocess terminates (in the experience i had, could very well be wrong).

The question i had, which your question imho boils down to was: how can I write to a processes stdin and read from its stdout while it is still running?

I ended up using Pipes. Note that they have to me flushed if you write something that is smaller that BUFSIZE, which is 0x1000 in my case. See man pipe for more info.

Anyway, below is my code.

import time
import pty
import os
import subprocess
PIPE_BUFSIZE = 4096
_control = False


class Pipe:

    def __init__(self, flags=None, terminal=True):
        """Creates a Pipe you can easily write to and read from. Default is to open up a pseudo-terminal.
            If you supply flags, pipe2 is used."""

        if flags or not terminal:
            self._readfd, self._writefd = os.pipe2(flags)
        else:   # default
            self._readfd, self._writefd = pty.openpty()

        self.readobj = open(self._readfd, "rb", 0)  
        self.writeobj = open(self._writefd, "wb", 0)

    def write(self, text):
        if isinstance(text, str):
            text = text.encode()

        result = self.writeobj.write(text)
        self.writeobj.flush()
        return result

    def read(self, n):
        if _control:    # im not using this anymore since the flush seems to be reliable
            controlstr = b"this_was_flushed"
            controllen = len(controlstr)

            self.writeobj.write(controlstr)
            time.sleep(.001)
            self.writeobj.flush()

            result = self.readobj.read(n+controllen)
            assert result[-controllen:] == controlstr
            return result[:-controllen]
        else:
            self.writeobj.flush()
            return self.readobj.read(n)


class ProcessIOWrapper:
    """Provides an easy way to redirect stdout and stderr using pipes. Write to the processes STDIN and read from STDOUT at any time! """

    def __init__(self, args, inittext=None, redirect=True):

        #create three pseudo terminals
        self.in_pipe = Pipe()
        self.out_pipe = Pipe()
        self.err_pipe = Pipe()

        # if we want to redirect, tell the subprocess to write to our pipe, else it will print to normal stdout
        if redirect:
            stdout_arg= self.out_pipe.writeobj
            stderr_arg= self.err_pipe.writeobj
        else:
            stdout_arg=None
            stderr_arg= None

        self.process = subprocess.Popen(args, stdin=self.in_pipe.readobj, stdout=stdout_arg, stderr=stderr_arg)


    def write(self, text):
        return self.in_pipe.write(text)

    def read(self, n, start=None):
        return self.out_pipe.read(n)




Small C Program i used for testing (used others, too)

#include <stdio.h>


int main(){
    puts("start\n");
    char buf[100]="bufbufbuf";
    while(1){

        gets(buf);
        for (int i=0; i<strlen(buf); i++)
            if (i%2==0) buf[i]+=1;

        puts(buf);
    }
}


The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM