web-dev-qa-db-fra.com

Comment puis-je exécuter une commande externe de manière asynchrone à partir de 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 de s'exécuter alors que la commande externe est désactivée)]. et fait tout ce qu'il doit faire.

J'ai lu ce post:

appel d'une commande externe en Python

Je suis ensuite parti et ai fait quelques tests. Il semble que os.system() fera le travail à condition que j'utilise & À la fin de la commande pour ne pas avoir à attendre pour il retourne. Ce que je me demande, c'est si c'est la bonne façon d'accomplir une telle chose? J'ai essayé commands.call() mais cela ne fonctionnera pas pour moi car il se bloque sur la commande externe.

S'il vous plaît laissez-moi savoir si en utilisant os.system() pour cela est conseillé ou si je devrais essayer un autre itinéraire.

99
user76923

subprocess.Popen fait exactement ce que vous voulez.

from subprocess import Popen
p = Popen(['watch', 'ls']) # something long running
# ... do other stuff while subprocess is running
p.terminate()

(Modifier pour compléter la réponse à partir des commentaires)

L’instance Popen peut faire d’autres choses comme vous pouvez poll() pour voir si elle est toujours en cours d’exécution, et vous pouvez communicate() = avec lui pour lui envoyer des données sur stdin, et attendre sa fin.

116
Ali Afshar

Si vous souhaitez exécuter plusieurs processus en parallèle puis les gérer lorsqu'ils génèrent des résultats, vous pouvez utiliser l'interrogation comme suit:

from subprocess import Popen, PIPE
import time

running_procs = [
    Popen(['/usr/bin/my_cmd', '-i %s' % path], stdout=PIPE, stderr=PIPE)
    for path in '/tmp/file0 /tmp/file1 /tmp/file2'.split()]

while running_procs:
    for proc in running_procs:
        retcode = proc.poll()
        if retcode is not None: # Process finished.
            running_procs.remove(proc)
            break
        else: # No process is done, wait a bit and check again.
            time.sleep(.1)
            continue

    # Here, `proc` has finished with return code `retcode`
    if retcode != 0:
        """Error handling."""
    handle_results(proc.stdout)

Le flux de contrôle là-bas est un peu compliqué parce que j'essaie de le rendre petit - vous pouvez le refactoriser à votre goût. :-)

Cela présente l'avantage de traiter d'abord les demandes de finition anticipée. Si vous appelez communicate sur le premier processus en cours d'exécution et que l'exécution s'avère la plus longue, les autres processus en cours d'exécution auront été lancés. assis là au ralenti quand vous auriez pu manipuler leurs résultats.

41
cdleary

Ce que je me demande, c'est si ce [os.system ()] est la bonne manière d'accomplir une telle chose?

N ° os.system() n'est pas la bonne façon. C'est pourquoi tout le monde dit d'utiliser subprocess.

Pour plus d'informations, lisez http://docs.python.org/library/os.html#os.system

Le module de sous-processus offre des fonctionnalités plus puissantes pour générer de nouveaux processus et extraire leurs résultats. L'utilisation de ce module est préférable à l'utilisation de cette fonction. Utilisez le module de sous-processus. Vérifiez en particulier la section Remplacement des anciennes fonctions avec la section Module du sous-processus.

10
S.Lott

J'ai eu un bon succès avec le module asyncproc , qui traite bien du résultat 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
7
Noah

Utiliser pexpect [ http://www.noah.org/wiki/Pexpect ] avec des lignes de lecture non bloquantes est un autre moyen de procéder. Pexpect résout les problèmes d'interblocage, vous permet d'exécuter facilement les processus en arrière-plan et offre des moyens simples d'obtenir des rappels lorsque votre processus crache des chaînes prédéfinies et facilite généralement beaucoup plus l'interaction avec le processus.

6
Gabe

Considérant "je n'ai pas à attendre son retour", 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 de réaliser une telle chose" en raison des risques de sécurité créés par subprocess.CREATE_NEW_CONSOLE drapeau.

Les éléments clés qui se produisent ici sont l'utilisation de subprocess.CREATE_NEW_CONSOLE pour créer une nouvelle console et .pid (retourne l’ID de processus afin que vous puissiez vérifier le programme ultérieurement si vous le souhaitez) afin de ne pas attendre que le programme termine son travail.

3
8day

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 Processus que j'ai trouvée ici:

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

Et voici l'exemple extrait du fichier:

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 __== '__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()
3
Patrizio Rullo

La réponse acceptée est très ancienne .

J'ai trouvé une meilleure réponse moderne ici:

https://kevinmccarthy.org/2016/07/25/streaming-subprocess-stdin-and-stdout-with-asyncio-in-python/

et fait quelques changements:

  1. le faire fonctionner sur windows
  2. le faire fonctionner avec plusieurs commandes
import sys
import asyncio

if sys.platform == "win32":
    asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())


async def _read_stream(stream, cb):
    while True:
        line = await stream.readline()
        if line:
            cb(line)
        else:
            break


async def _stream_subprocess(cmd, stdout_cb, stderr_cb):
    try:
        process = await asyncio.create_subprocess_exec(
            *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
        )

        await asyncio.wait(
            [
                _read_stream(process.stdout, stdout_cb),
                _read_stream(process.stderr, stderr_cb),
            ]
        )
        rc = await process.wait()
        return process.pid, rc
    except OSError as e:
        # the program will hang if we let any exception propagate
        return e


def execute(*aws):
    """ run the given coroutines in an asyncio loop
    returns a list containing the values returned from each coroutine.
    """
    loop = asyncio.get_event_loop()
    rc = loop.run_until_complete(asyncio.gather(*aws))
    loop.close()
    return rc


def printer(label):
    def pr(*args, **kw):
        print(label, *args, **kw)

    return pr


def name_it(start=0, template="s{}"):
    """a simple generator for task names
    """
    while True:
        yield template.format(start)
        start += 1


def runners(cmds):
    """
    cmds is a list of commands to excecute as subprocesses
    each item is a list appropriate for use by subprocess.call
    """
    next_name = name_it().__next__
    for cmd in cmds:
        name = next_name()
        out = printer(f"{name}.stdout")
        err = printer(f"{name}.stderr")
        yield _stream_subprocess(cmd, out, err)


if __== "__main__":
    cmds = (
        [
            "sh",
            "-c",
            """echo "$Shell"-stdout && sleep 1 && echo stderr 1>&2 && sleep 1 && echo done""",
        ],
        [
            "bash",
            "-c",
            "echo 'hello, Dave.' && sleep 1 && echo dave_err 1>&2 && sleep 1 && echo done",
        ],
        [sys.executable, "-c", 'print("hello from python");import sys;sys.exit(2)'],
    )

    print(execute(*runners(cmds)))

Il est peu probable que les exemples de commandes fonctionnent parfaitement sur votre système et ne gèrent pas les erreurs étranges, mais ce code montre une manière d'exécuter plusieurs sous-processus à l'aide d'asyncio et de diffuser le résultat.

0
Terrel Shumway