¿Cómo puedo ejecutar un comando externo de forma asíncrona desde Python?


Necesito ejecutar un comando de shell de forma asíncrona desde un script Python. Con esto quiero decir que quiero que mi script Python continúe ejecutándose mientras el comando externo se apaga y hace lo que sea necesario.

He leído este post:

Llamar a un comando externo en Python

Luego me fui e hice algunas pruebas, y parece que os.system() hará el trabajo siempre que use & al final del comando para que no tenga que esperar a que devolver. Lo que me pregunto es si esta es la manera correcta de lograr tal cosa? He intentado commands.call() pero no va a funcionar para mí porque se bloquea en el comando externo.

Por favor, avísame si usar os.system() para esto es aconsejable o si debo probar alguna otra ruta.

Author: Community, 2009-03-12

7 answers

Subproceso.Popen hace exactamente lo que quieres.

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

(Editar para completar la respuesta de los comentarios)

La instancia de Popen puede hacer varias otras cosas como usted poll() para ver si todavía está funcionando, y usted puede communicate() con él para enviar los datos en stdin, y esperar a que termine.

 98
Author: Ali Afshar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-11 22:16:03

Si desea ejecutar muchos procesos en paralelo y luego manejarlos cuando produzcan resultados, puede usar polling como en lo siguiente:

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)

El flujo de control allí es un poco complicado porque estoy tratando de hacerlo pequeño you puede refactorizar a su gusto. :-)

Esto tiene la ventaja de atender primero las solicitudes de finalización temprana. Si llama a communicate en el primer proceso en ejecución y resulta que se ejecuta más tiempo, los otros procesos en ejecución han estado sentados ahí ociosos cuando podrías haber estado manejando sus resultados.

 38
Author: cdleary,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-02 00:05:53

Lo que me pregunto es si este [os.system ()] ¿es la manera correcta de lograr tal cosa?

No. os.system() no es el camino correcto. Es por eso que todos dicen usar subprocess.

Para más información, léase http://docs.python.org/library/os.html#os.system

El módulo de subproceso proporciona más potentes instalaciones para el desove nuevo processes and retrieving their resultados; el uso de ese módulo es preferible a usar esta función. Utilizar el módulo subprocess. Comprobar especialmente la Sustitución de Más Edad Funciones con el Módulo subprocess apartado.

 9
Author: S.Lott,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-11 22:24:32

Usando pexpect [http://www.noah.org/wiki/Pexpect ] con readlines sin bloqueo es otra forma de hacer esto. Pexpect resuelve los problemas de bloqueo, le permite ejecutar fácilmente los procesos en segundo plano, y ofrece formas fáciles de tener devoluciones de llamada cuando su proceso escupe cadenas predefinidas, y generalmente hace que la interacción con el proceso sea mucho más fácil.

 6
Author: Gabe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-06 14:30:28

He tenido mucho éxito con el módulo asyncproc, que trata muy bien con la salida de los procesos. Por ejemplo:

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
 6
Author: Noah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-12 18:30:30

Tengo el mismo problema al intentar conectarme a un terminal 3270 usando el software de scripting s3270 en Python. Ahora estoy resolviendo el problema con una subclase de Proceso que encontré aquí:

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

Y aquí está la muestra tomada del archivo:

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()
 3
Author: Patrizio Rullo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-06-06 10:07:22

Considerando que "no tengo que esperar a que regrese", una de las soluciones más fáciles será esta:

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

Pero... Por lo que he leído esto no es "la manera adecuada de lograr tal cosa" debido a los riesgos de seguridad creados por subprocess.CREATE_NEW_CONSOLE bandera.

Las cosas clave que suceden aquí es el uso de subprocess.CREATE_NEW_CONSOLE para crear una nueva consola y .pid (devuelve el ID del proceso para que pueda verificar el programa más adelante si lo desea) para no esperar a que el programa termine su trabajo.

 2
Author: 8day,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-06 11:53:19