2009-03-11 15 views
81

Necesito ejecutar un comando de shell de forma asincrónica desde un script de Python. Con esto quiero decir que quiero que mi script de Python continúe ejecutándose mientras el comando externo se apaga y hace lo que tiene que hacer.¿Cómo puedo ejecutar un comando externo de forma asincrónica desde Python?

leí este post:

Calling an external command in Python

luego me fui y hice algunas pruebas, y parece que os.system() hará el trabajo siempre que utilizo & al final de la comando para que no tenga que esperar a que vuelva. Lo que me pregunto es si esta es la forma correcta de lograr tal cosa. Intenté commands.call() pero no funcionará para mí porque bloquea el comando externo.

Por favor, hágamelo saber si el uso de os.system() para esto es aconsejable o si debo intentar alguna otra ruta.

Respuesta

90

subprocess.Popen hace exactamente lo que quiere.

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 comentarios)

La instancia Popen pueden hacer varias otras cosas como puede poll() para ver si aún se está ejecutando, y se puede communicate() con él para enviarlo datos en stdin y espere a que finalice.

+2

También puede usar poll() para verificar si el proceso hijo ha finalizado, o use wait() para esperar a que finalice. –

+0

Adam, muy cierto, aunque podría ser mejor usar la función de comunicación() para esperar porque tiene un mejor manejo de los búferes de entrada/salida y hay situaciones en las que las inundaciones pueden bloquear. –

+0

Adam: docs dice "Advertencia Esto bloqueará si el proceso secundario genera suficiente salida para una tubería stdout o stderr, de modo que bloquea la espera de que el búfer de la tubería del sistema operativo acepte más datos. Use la función de comunicación() para evitar eso " –

36

Si desea ejecutar varios procesos en paralelo y luego manejarlos cuando ellos se obtienen resultados, se puede utilizar de votación como en el 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 hay un poco complicado porque yo Estoy tratando de hacerlo pequeño, puedes refactorizar a tu gusto. :-)

Esto tiene la ventaja de atender primero las solicitudes de acabado temprano. Si llama al communicate en el primer proceso que se ejecuta y que se ejecuta durante más tiempo, los otros procesos en ejecución estarán inactivos cuando podría haber estado manejando los resultados.

+3

@Tino Depende de cómo defina ocupado-espera. Consulte [¿Cuál es la diferencia entre busy-wait y polling?] (Http://stackoverflow.com/questions/10594426/) –

+1

¿Hay alguna forma de sondear un conjunto de procesos no solo uno? –

+1

nota: puede bloquearse si un proceso genera suficiente salida. Debería consumir stdout al mismo tiempo si usa PIPE (hay demasiadas advertencias en el subproceso 'documentos al respecto). – jfs

8

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

No. os.system() no es la forma correcta. Es por eso que todos dicen usar subprocess.

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

El módulo subproceso proporciona más instalaciones de gran alcance para el desove nuevos procesos y recuperar sus resultados; usar ese módulo es preferible al uso de esta función. Use el módulo de subproceso. Compruebe especialmente el reemplazo de las funciones anteriores con el subproceso Módulo sección.

6

He tenido un buen éxito con el módulo asyncproc, que se ocupa muy bien de 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 
+0

encuesta! = Ninguno: ??? ¿No debería ser 'si la encuesta no es None'? –

+0

sí, seguro - haciendo las rondas del código de cuatro años? :) – Noah

+0

¿es esto en cualquier parte de github? – Nick

3

Tengo el mismo problema al intentar conectarse a un terminal 3270 usando el software s3270 scripts en Python. Ahora estoy resolver el problema con una subclase de proceso que he encontrado 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() 
6

Usando pexpect [http://www.noah.org/wiki/Pexpect] con la no bloquear readlines es otra forma de hacer esto. Pexpect soluciona los problemas de interbloqueo, le permite ejecutar fácilmente los procesos en segundo plano, y le brinda formas sencillas de realizar 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.

+0

pexpect es una forma poco ortodoxa de ejecutar procesos, pero me parece bastante práctico. – droope

2

Teniendo en cuenta "no tengo que esperar a que vuelva", una de las soluciones más fáciles será la siguiente:

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

Pero ... Por lo que leo esto no es "la forma correcta para lograr tal cosa "debido a los riesgos de seguridad creados por subprocess.CREATE_NEW_CONSOLE bandera.

Las cosas que suceden clave aquí es el uso de subprocess.CREATE_NEW_CONSOLE para crear nueva consola y .pid (ID proceso de retorno de manera que se puede consultar el programa más adelante si lo desea) de manera que no esperar a que el programa para terminar su trabajo.

+0

que indica lo que cada param estaba en el subproceso. El método de Popen fue muy útil –

Cuestiones relacionadas