wake-up-neo.com

Interaktive Eingabe/Ausgabe mit Python

Ich habe ein Programm, das mit dem Benutzer interagiert (verhält sich wie eine Shell), und ich möchte es interaktiv mit dem Python-Subprozess-Modul ausführen. Das heißt, ich möchte die Möglichkeit haben, in stdin zu schreiben und sofort die Ausgabe von stdout zu erhalten. Ich habe viele der hier angebotenen Lösungen ausprobiert, aber keine davon scheint für meine Bedürfnisse zu funktionieren.

Der Code, den ich geschrieben habe, basiert auf Ausführen eines interaktiven Befehls in 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)

Das Problem ist, dass die Warteschlange leer bleibt, als ob es keine Ausgabe gibt. Nur wenn ich stdin alle Eingaben schreibe, die das Programm ausführen und beenden muss, dann bekomme ich die Ausgabe (die ich nicht will). . Zum Beispiel wenn ich so etwas mache:

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

Gibt es eine Möglichkeit zu tun, was ich tun möchte?

EDIT: Das Skript wird auf einem Linux-Computer ausgeführt Ich habe mein Skript geändert und das universal_newlines = True + gelöscht. Trotzdem bekomme ich keine Ausgabe.

Zweiter Versuch: Ich habe diese Lösung ausprobiert und es funktioniert für mich:

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

Zwei Lösungen für dieses Problem unter Linux:

Zuerst müssen Sie eine Datei verwenden, um die Ausgabe zu schreiben und gleichzeitig daraus zu lesen:

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

Zweitens, wie es J.F. Sebastian anbot, ist es, p.stdout- und p.stderr-Pipes mit dem fnctl-Modul zu blockieren:

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
14

Keine der aktuellen Antworten funktionierte für mich. Am Ende habe ich diese Arbeit:

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)

Getestet mit diesem dummy.py-Skript:

#!/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])

Die Vorbehalte sind: Eingabe/Ausgabe immer Zeilen mit Zeilenvorschub, leert child stdin nach jedem Schreibvorgang und verwendet readline() aus child stdout (alle in den Funktionen verwalteten). 

Es ist eine ziemlich einfache Lösung, meiner Meinung nach (nicht meine, ich habe sie hier gefunden: https://eli.thegreenplace.net/2017/interacting-with-a-long-running-child-process-in-python/ ). Ich habe Python 3.6 verwendet.

4
gorcajo

Hier ist eine interaktive Shell. Sie müssen read () in einem separaten Thread ausführen, andernfalls wird das write () blockiert.

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