2009-03-11 7 views
81

Ho bisogno di eseguire un comando shell in modo asincrono da uno script Python. Con questo voglio dire che voglio che il mio script Python continui a funzionare mentre il comando esterno si spegne e fa tutto ciò che deve fare.Come posso eseguire un comando esterno in modo asincrono da Python?

ho letto questo post:

Calling an external command in Python

Allora sono andato via e ha fatto alcuni test, e sembra che os.system() farà il lavoro a condizione che io uso & alla fine del comando in modo da non dover aspettare che ritorni. Quello che mi chiedo è se questo è il modo giusto per realizzare una cosa del genere? Ho provato commands.call() ma non funzionerà per me perché blocca sul comando esterno.

Per favore fatemi sapere se usare os.system() per questo è consigliabile o se dovrei provare qualche altro percorso.

risposta

90

subprocess.Popen fa esattamente quello che vuoi.

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

(Edit per completare la risposta da commenti)

L'istanza Popen può fare varie altre cose, come si può poll() per vedere se è ancora in esecuzione, e si può communicate() con esso per inviarlo dati su stdin e attendi che termini.

+2

È anche possibile utilizzare poll() per verificare se il processo secondario è stato terminato o utilizzare wait() per attendere che termini. –

+0

Adam, molto vero, anche se potrebbe essere preferibile usare communic() per attendere perché ha una migliore gestione dei buffer in/out e ci sono situazioni in cui le inondazioni potrebbero bloccarle. –

+0

Adam: i documenti dicono "Avviso Questo sarà deadlock se il processo figlio genera un output sufficiente a una pipe stdout o stderr in modo tale da bloccare l'attesa del buffer di pipe del sistema operativo per accettare più dati. –

36

Se si desidera eseguire molti processi in parallelo e poi gestire quando si ottengano risultati, è possibile utilizzare il polling come nel seguente:

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) 

Il flusso di controllo c'è un po 'contorto, perché I' Sto cercando di renderlo piccolo - puoi refactoring per il vostro gusto. :-)

Questo ha il vantaggio di servire prima le richieste di finitura anticipata. Se si chiama communicate sul primo processo in esecuzione e risulta essere il più lungo, gli altri processi in esecuzione saranno rimasti inattivi quando si sarebbe potuto gestire i risultati.

+3

@Tino Dipende da come si definisce busy-wait. Vedi [Qual è la differenza tra busy-wait e polling?] (Http://stackoverflow.com/questions/10594426/) –

+1

C'è un modo per interrogare un insieme di processi non solo uno? –

+1

nota: potrebbe bloccarsi se un processo genera abbastanza output. Dovresti consumare stdout contemporaneamente se usi PIPE (ci sono (troppi ma non abbastanza) avvertimenti nei documenti del sottoprocesso a riguardo). – jfs

8

Quello che mi chiedo è se questo [os.system()] è il modo corretto per realizzare una cosa del genere?

No. os.system() non è il modo corretto. Ecco perché tutti dicono di usare subprocess.

Per ulteriori informazioni, leggere http://docs.python.org/library/os.html#os.system

Il modulo sottoprocesso fornisce ulteriori strutture potenti per la deposizione delle uova nuove processi e il recupero dei loro risultati; l'uso di tale modulo è preferibile all'utilizzo di questa funzione. Utilizzare il sottoprocesso modulo. Controllare in particolare la sostituzione delle precedenti Funzioni con il sottoprocesso Modulo sezione.

6

Ho avuto un buon successo con il modulo asyncproc, che si adatta perfettamente all'output dei processi. Ad esempio:

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

sondaggio! = Nessuno: ??? Non dovrebbe essere 'se il sondaggio non è None:'? –

+0

sì, certo - facendo il giro del codice di quattro anni? :) – Noah

+0

è ovunque su github? – Nick

3

Ho lo stesso problema cercando di connettersi a un terminale 3270 utilizzando il software s3270 script in Python. Ora sto risolvendo il problema con una sottoclasse di processo che ho trovato qui:

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

Ed ecco il campione prelevato dal file:

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

Utilizzando pexpect [http://www.noah.org/wiki/Pexpect] con non- bloccare i readlines è un altro modo per farlo. Pexpect risolve i problemi di deadlock, ti ​​consente di eseguire facilmente i processi in background e offre dei modi semplici per avere dei callback quando il tuo processo sputa stringhe predefinite, e generalmente rende molto più semplice l'interazione con il processo.

+0

pexpect è un modo non ortodosso per eseguire i processi, ma lo trovo piuttosto pratico. – droope

2

Considerando "Io non devo aspettare che si return", una delle soluzioni più semplici sarà questo:

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

Ma ... Da quello che ho letto questo non è "il modo corretto per realizzare una cosa del genere "a causa dei rischi per la sicurezza creati dal flag subprocess.CREATE_NEW_CONSOLE.

Le cose fondamentali che accadere qui è l'uso di subprocess.CREATE_NEW_CONSOLE per creare nuova console e .pid (ID rendimenti di processo in modo che si possa controllare il programma in seguito, se si desidera) in modo che di non aspettare per il programma di finire il suo lavoro.

+0

che indica cosa ciascun parametro era nel sottoprocesso.Il metodo Popo era molto utile –