153 votes

Comment puis-je exécuter une commande externe de manière asynchrone depuis Python ?

Je dois exécuter une commande shell de manière asynchrone à partir d'un script Python. Je veux dire par là que je veux que mon script Python continue à s'exécuter pendant que la commande externe part faire ce qu'elle doit faire.

J'ai lu ce post :

Appel d'une commande externe en Python

J'ai ensuite fait quelques tests, et on dirait que os.system() fera l'affaire à condition que j'utilise & à la fin de la commande pour ne pas avoir à attendre qu'elle revienne. Je me demande si c'est la bonne façon d'accomplir une telle chose ? J'ai essayé commands.call() mais cela ne fonctionne pas pour moi car cela bloque sur la commande externe.

Faites-moi savoir si vous utilisez os.system() si cela est conseillé ou si je dois essayer une autre voie.

8voto

Noah Points 3398

J'ai eu de bons résultats avec le asyncproc qui gère bien la sortie des processus. Par exemple :

import os
from asynproc import Process
myProc = Process("myprogram.app")

while True:
    # check to see if process has ended
    poll = myProc.wait(os.WNOHANG)
    if poll is not None:
        break
    # print any new output
    out = myProc.read()
    if out != "":
        print out

0 votes

Est-ce que c'est quelque part sur Github ?

0 votes

C'est une licence GPL, donc je suis sûr qu'il y est souvent. En voici une : github.com/albertz/helpers/blob/master/asyncproc.py

0 votes

J'ai ajouté un gist avec quelques modifications pour le faire fonctionner avec python3 (remplace principalement les str par des bytes). Voir gist.github.com/grandemk/cbc528719e46b5a0ffbd07e3054aab83

7voto

Gabe Points 304

Utilisation de pexpecter avec des lignes de lecture non bloquantes est une autre façon de procéder. Pexpect résout les problèmes de blocage, vous permet d'exécuter facilement les processus en arrière-plan, offre des moyens faciles d'avoir des callbacks lorsque votre processus crache des chaînes prédéfinies, et rend généralement l'interaction avec le processus beaucoup plus facile.

6voto

8day Points 49

Si l'on considère que "je n'ai pas besoin d'attendre qu'il revienne", l'une des solutions les plus simples sera la suivante :

subprocess.Popen( \
    [path_to_executable, arg1, arg2, ... argN],
    creationflags = subprocess.CREATE_NEW_CONSOLE,
).pid

Mais... D'après ce que j'ai lu, ce n'est pas "la bonne façon d'accomplir une telle chose" en raison des risques de sécurité créés par subprocess.CREATE_NEW_CONSOLE drapeau.

Les choses clés qui se passent ici sont l'utilisation de subprocess.CREATE_NEW_CONSOLE pour créer une nouvelle console et .pid (renvoie l'ID du processus afin que vous puissiez vérifier le programme plus tard si vous le souhaitez) afin de ne pas attendre que le programme termine son travail.

3voto

Patrizio Rullo Points 177

J'ai le même problème en essayant de me connecter à un terminal 3270 en utilisant le logiciel de script s3270 en Python. Maintenant je résous le problème avec une sous-classe de Process que j'ai trouvé ici :

http://code.activestate.com/recipes/440554/

Et voici l'échantillon tiré du dossier :

def recv_some(p, t=.1, e=1, tr=5, stderr=0):
    if tr < 1:
        tr = 1
    x = time.time()+t
    y = []
    r = ''
    pr = p.recv
    if stderr:
        pr = p.recv_err
    while time.time() < x or r:
        r = pr()
        if r is None:
            if e:
                raise Exception(message)
            else:
                break
        elif r:
            y.append(r)
        else:
            time.sleep(max((x-time.time())/tr, 0))
    return ''.join(y)

def send_all(p, data):
    while len(data):
        sent = p.send(data)
        if sent is None:
            raise Exception(message)
        data = buffer(data, sent)

if __name__ == '__main__':
    if sys.platform == 'win32':
        shell, commands, tail = ('cmd', ('dir /w', 'echo HELLO WORLD'), '\r\n')
    else:
        shell, commands, tail = ('sh', ('ls', 'echo HELLO WORLD'), '\n')

    a = Popen(shell, stdin=PIPE, stdout=PIPE)
    print recv_some(a),
    for cmd in commands:
        send_all(a, cmd + tail)
        print recv_some(a),
    send_all(a, 'exit' + tail)
    print recv_some(a, e=0)
    a.wait()

1voto

ShitalShah Points 2213

Il y a plusieurs réponses ici, mais aucune d'entre elles ne répond à mes exigences ci-dessous :

  1. Je ne veux pas attendre que la commande se termine ou polluer mon terminal avec les sorties des sous-processus.

  2. Je veux exécuter bash script avec des redirections.

  3. Je veux supporter le piping dans mon script bash (par exemple find ... | tar ... ).

La seule combinaison qui satisfait aux exigences ci-dessus est la suivante :

subprocess.Popen(['./my_script.sh "arg1" > "redirect/path/to"'],
                 stdout=subprocess.PIPE, 
                 stderr=subprocess.PIPE,
                 shell=True)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X