web-dev-qa-db-fra.com

Entrée/sortie interactive à l'aide de python

J'ai un programme qui interagit avec l'utilisateur (agit comme un shell) et je veux l'exécuter en utilisant le module de sous-processus python de manière interactive. Cela signifie que je veux avoir la possibilité d’écrire sur stdin et d’obtenir immédiatement le résultat de stdout. J'ai essayé beaucoup de solutions proposées ici, mais aucune ne semble répondre à mes besoins.

Le code que j'ai écrit basé sur Exécution d'une commande interactive à partir de 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)

Le problème est que la file d'attente reste vide, comme s'il n'y avait pas de sortie . Seulement si j'écris dans stdin toutes les entrées que le programme doit exécuter et terminer, je récupère la sortie (ce qui n'est pas ce que je veux) . Par exemple, si je fais quelque chose comme:

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

Y a-t-il un moyen de faire ce que je veux faire?

EDIT: Le script s’exécutera sur une machine Linux . J’ai modifié mon script et supprimé le universal_newlines = True. Je n'ai toujours pas de sortie.

Deuxième essai: J'ai essayé cette solution et cela fonctionne pour moi:

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
Talor Abramovich

Deux solutions à ce problème sous Linux:

La première consiste à utiliser un fichier pour écrire la sortie et à la lire simultanément:

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

Deuxièmement, comme l'a proposé J.F. Sebastian, consiste à rendre les tuyaux p.stdout et p.stderr non bloquants à l'aide du module fnctl:

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
Talor Abramovich

Aucune des réponses actuelles n'a fonctionné pour moi. À la fin, j'ai ce travail:

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)

Testé avec ce script dummy.py:

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

Les mises en garde sont les suivantes: entrée/sortie toujours les lignes avec une nouvelle ligne, vidage du stdin de l'enfant après chaque écriture, et utilisation de readline() depuis la sortie standard de l'enfant (tout ce qui est géré dans les fonctions). 

C'est une solution assez simple, à mon avis (pas la mienne, je l'ai trouvée ici: https://eli.thegreenplace.net/2017/interacting-with-a-long-running-child-process-in-python/ ). J'utilisais Python 3.6.

4
gorcajo

Voici un shell interactif. Vous devez exécuter read () sur un thread séparé, sinon cela bloquera write ()

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