2011-11-27 5 views
4

J'essaie d'obtenir une sortie d'un sous-processus, puis de donner des commandes à ce processus en fonction de la sortie précédente. Je dois le faire un nombre variable de fois, quand le programme a besoin de plus de contribution. (Je dois également pouvoir cacher l'invite de commande de sous-processus si possible). Je pensais que ce serait une tâche facile étant donné que j'ai vu ce problème discuté dans les messages de 2003 et il est presque 2012 et il semble être un besoin assez commun et semble vraiment qu'il devrait être une partie fondamentale de n'importe quel langage de programmation. Apparemment je me suis trompé et d'une manière ou d'une autre près de 9 ans plus tard, il n'y a toujours pas de manière standard d'accomplir cette tâche de manière stable, non destructive et indépendante de la plateforme!Récupération et exécution de commandes à un sous-processus python

Je ne comprends pas vraiment beaucoup sur les E/S de fichiers et la mise en mémoire tampon ou l'enfilage, donc je préférerais une solution aussi simple que possible. S'il y a un module qui accomplit ceci qui est compatible avec python 3.x, je serais très disposé à le télécharger. Je me rends compte qu'il y a plusieurs questions qui demandent fondamentalement la même chose, mais je n'ai pas encore trouvé de réponse à la tâche simple que j'essaie d'accomplir.

Voici le code que j'ai jusqu'ici basé sur une variété de sources; Cependant, je n'ai absolument aucune idée de ce qu'il faut faire ensuite. Toutes mes tentatives se sont soldées par un échec et certains ont réussi à utiliser 100% de mon CPU (pour ne rien faire) et ne pas quitter.

import subprocess 
from subprocess import Popen, PIPE 
p = Popen(r'C:\postgis_testing\shellcomm.bat',stdin=PIPE,stdout=PIPE,stderr=subprocess.STDOUT shell=True) 
stdout,stdin = p.communicate(b'command string') 

Dans le cas où ma question ne sait pas que je suis annonce le texte du fichier de lot d'échantillons que je Démontre une situation dans laquelle il est nécessaire d'envoyer plusieurs commandes à la sous-processus (si vous tapez une chaîne de commande incorrecte du programme boucles).

@echo off 
:looper 
set INPUT= 
set /P INPUT=Type the correct command string: 
if "%INPUT%" == "command string" (echo you are correct) else (goto looper) 

Si quelqu'un peut m'aider je l'apprécierais beaucoup, et je suis sûr que beaucoup d'autres le feraient aussi bien!

EDIT voici le code fonctionnel utilisant le code eryksun (post suivant):

import subprocess 
import threading 
import time 
import sys 

try: 
    import queue 
except ImportError: 
    import Queue as queue 

def read_stdout(stdout, q, p): 
    it = iter(lambda: stdout.read(1), b'') 
    for c in it: 
     q.put(c) 
     if stdout.closed: 
      break 

_encoding = getattr(sys.stdout, 'encoding', 'latin-1') 
def get_stdout(q, encoding=_encoding): 
    out = [] 
    while 1: 
     try: 
      out.append(q.get(timeout=0.2)) 
     except queue.Empty: 
      break 
    return b''.join(out).rstrip().decode(encoding) 

def printout(q): 
    outdata = get_stdout(q) 
    if outdata: 
     print('Output: %s' % outdata) 

if __name__ == '__main__': 
    #setup 
    p = subprocess.Popen(['shellcomm.bat'], stdin=subprocess.PIPE, 
        stdout=subprocess.PIPE, stderr=subprocess.PIPE, 
        bufsize=0, shell=True) # I put shell=True to hide prompt 
    q = queue.Queue() 
    encoding = getattr(sys.stdin, 'encoding', 'utf-8') 

    #for reading stdout 
    t = threading.Thread(target=read_stdout, args=(p.stdout, q, p)) 
    t.daemon = True 
    t.start() 

    #command loop 
    while p.poll() is None: 
     printout(q) 
     cmd = input('Input: ') 
     cmd = (cmd + '\n').encode(encoding) 
     p.stdin.write(cmd) 
     time.sleep(0.1) # I added this to give some time to check for closure (otherwise it doesn't work) 

    #tear down 
    for n in range(4): 
     rc = p.poll() 
     if rc is not None: 
      break 
     time.sleep(0.25) 
    else: 
     p.terminate() 
     rc = p.poll() 
     if rc is None: 
      rc = 1 

    printout(q) 
    print('Return Code: %d' % rc) 

Toutefois, lorsque le script est exécuté à partir d'une invite de commande suivantes se produit:

C:\Users\username>python C:\postgis_testing\shellcomm7.py 
Input: sth 
Traceback (most recent call last): 
File "C:\postgis_testing\shellcomm7.py", line 51, in <module> 
    p.stdin.write(cmd) 
IOError: [Errno 22] Invalid argument 

Il semble que le Le programme se ferme lorsqu'il est exécuté à partir de l'invite de commande. des idées?

Répondre

3

Cette démo utilise un thread dédié pour lire depuis stdout. Si vous cherchez autour, je suis sûr que vous pouvez trouver une implémentation plus complète écrite dans une interface orientée objet. Au moins, je peux dire que cela fonctionne pour moi avec votre fichier batch fourni à la fois dans Python 2.7.2 et 3.2.2.

shellcomm.bat:

@echo off 
echo Command Loop Test 
echo. 
:looper 
set INPUT= 
set /P INPUT=Type the correct command string: 
if "%INPUT%" == "command string" (echo you are correct) else (goto looper) 

Voici ce que je reçois pour la sortie en fonction de la séquence de commandes "mauvais", "encore mal" et "chaîne de commande":

Output: 
Command Loop Test 

Type the correct command string: 
Input: wrong 
Output: 
Type the correct command string: 
Input: still wrong 
Output: 
Type the correct command string: 
Input: command string 
Output: 
you are correct 

Return Code: 0 

Pour en lisant la sortie canalisée, readline peut parfois fonctionner, mais set /P INPUT dans le fichier batch n'écrit naturellement pas de fin de ligne. Donc à la place, j'ai utilisé lambda: stdout.read(1) pour lire un octet à la fois (pas si efficace, mais ça marche). La fonction de lecture place les données dans une file d'attente. Le thread principal obtient la sortie de la file d'attente après avoir écrit une commande.L'utilisation d'un délai d'attente sur l'appel get fait qu'il attend un peu de temps pour s'assurer que le programme attend une entrée. Au lieu de cela, vous pouvez vérifier la sortie des invites pour savoir quand le programme attend une entrée. Cela dit, vous ne pouvez pas vous attendre à ce qu'une telle configuration fonctionne universellement car le programme de la console avec lequel vous essayez d'interagir peut mettre en tampon sa sortie lorsqu'elle est connectée. Dans les systèmes Unix, certaines commandes d'utilitaires sont disponibles que vous pouvez insérer dans un canal pour modifier la mise en mémoire tampon afin qu'elle ne soit pas mise en mémoire tampon, mise en mémoire tampon ou taille donnée, par exemple stdbuf. Il existe également des moyens de tromper le programme en lui faisant croire qu'il est connecté à un pty (voir pexpect). Cependant, je ne connais pas un moyen de contourner ce problème sur Windows si vous n'avez pas accès au code source du programme pour définir explicitement la mise en mémoire tampon en utilisant setvbuf.

import subprocess 
import threading 
import time 
import sys 

if sys.version_info.major >= 3: 
    import queue 
else: 
    import Queue as queue 
    input = raw_input 

def read_stdout(stdout, q): 
    it = iter(lambda: stdout.read(1), b'') 
    for c in it: 
     q.put(c) 
     if stdout.closed: 
      break 

_encoding = getattr(sys.stdout, 'encoding', 'latin-1') 
def get_stdout(q, encoding=_encoding): 
    out = [] 
    while 1: 
     try: 
      out.append(q.get(timeout=0.2)) 
     except queue.Empty: 
      break 
    return b''.join(out).rstrip().decode(encoding) 

def printout(q): 
    outdata = get_stdout(q) 
    if outdata: 
     print('Output:\n%s' % outdata) 

if __name__ == '__main__': 

    ARGS = ["shellcomm.bat"] ### Modify this 

    #setup 
    p = subprocess.Popen(ARGS, bufsize=0, stdin=subprocess.PIPE, 
         stdout=subprocess.PIPE, stderr=subprocess.PIPE) 
    q = queue.Queue() 
    encoding = getattr(sys.stdin, 'encoding', 'utf-8') 

    #for reading stdout 
    t = threading.Thread(target=read_stdout, args=(p.stdout, q)) 
    t.daemon = True 
    t.start() 

    #command loop 
    while 1: 
     printout(q) 
     if p.poll() is not None or p.stdin.closed: 
      break 
     cmd = input('Input: ') 
     cmd = (cmd + '\n').encode(encoding) 
     p.stdin.write(cmd) 

    #tear down 
    for n in range(4): 
     rc = p.poll() 
     if rc is not None: 
      break 
     time.sleep(0.25) 
    else: 
     p.terminate() 
     rc = p.poll() 
     if rc is None: 
      rc = 1 

    printout(q) 
    print('\nReturn Code: %d' % rc) 
+0

Merci beaucoup pour cela, il fonctionne parfaitement dans oisives, mais quand je lance le code modifié (voir mon modifier dans mon message original) il me donne l'erreur suivante C: \ Users \ nom d'utilisateur> python C : \ postgis_testing \ shellcomm7.py entrée: STH retraçage (appel le plus récent en dernier): fichier "C: \ postgis_testing \ shellcomm7.py", ligne 51, dans p.stdin.write (cmd) IOError: [Errno 22] Argument invalide Il semble que le programme se ferme lorsqu'il est exécuté à partir de l'invite de commande. des idées? – THX1138

+0

@ THX1138: J'ai modifié la boucle pour interroger le processus et vérifier stdin avant de demander une entrée. Il casse si le processus s'est terminé. J'ai également supprimé l'objet de processus des arguments 'read_stdout'; il a été laissé par erreur de ma première ébauche. – eryksun

+0

Lorsque j'exécute le nouveau code à partir d'une invite de commande, j'obtiens l'erreur: C: \ Utilisateurs \ nom d'utilisateur> python C: \ postgis_testing \ shellcomm8.py Traceback (appel le plus récent en dernier): Fichier "C: \ postgis_testing \ shellcomm8.py "à la ligne 38, dans bufsize = 0) fichier "C: \ Python32 \ lib \ subprocess.py", à la ligne 741, en __init__ restore_signals, start_new_session) fichier" C: \ Python32 \ lib \ subprocess.py ", ligne 960, dans _execute_child startupinfo) WindowsError: [Erreur 2] Le système ne trouve pas le fichier spécifié – THX1138

Questions connexes