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