2012-12-31 8 views
14

ho capito come utilizzare call() per ottenere il mio script Python per eseguire un comando:Python, sottoprocesso, call(), check_call e returncode da trovare se esiste un comando

import subprocess 

mycommandline = ['lumberjack', '-sleep all night', '-work all day'] 
subprocess.call(mycommandline) 

questo funziona ma c'è un problema, cosa succede se gli utenti non hanno il boscaiolo nel loro percorso di comando? Funzionerebbe se il boscaiolo fosse inserito nella stessa directory dello script python, ma come fa lo script a sapere che dovrebbe cercare il boscaiolo? Ho pensato che se ci fosse un errore command-not-found allora il taglialegna non sarebbe nel percorso del comando, lo script potrebbe cercare di capire quale sia la sua directory e cercare il boscaiolo e infine avvertire l'utente di copiare il boscaiolo in uno di quei due posti se non è stato trovato in nessuno dei due. Come posso scoprire qual è il messaggio di errore? Ho letto che check_call() può restituire un messaggio di errore e qualcosa su un attributo returncode. Non sono riuscito a trovare esempi su come usare check_call() e returncode, quale sarebbe il messaggio o come potrei capire se il messaggio è command-not-found.

Sto andando anche su questo nel modo giusto?

+2

'check_call()' dovrebbe generare un errore se il comando non esce in modo pulito (che è previsto per comandi inesistenti) – inspectorG4dget

+3

Inoltre, non consiglierei all'utente di copiare l'eseguibile in giro, tranne che come ultima risorsa . I gestori di pacchetti sono quasi sempre la migliore opzione per installare roba. –

+1

@DaveBrunker: Sembri un po 'confuso riguardo a 'call' vs.' check_call'. 'call' fornisce già il codice di ritorno, non come attributo, proprio come il valore di ritorno della funzione. Il motivo per usare 'check_call' è che si occupa di controllare quel codice di ritorno per te, sollevando un' CalledProcessError'. O si alza un 'OSError' (o simile, a seconda della versione di Python) perché il programma non venga trovato. – abarnert

risposta

2

Wow, che è stato veloce!Ho combinato semplice esempio di Theodros Zelleke e l'uso di steveha di funzioni con abarnert commenti su commenti OSError e Lattyware di circa lo spostamento di file:

import os, sys, subprocess 

def nameandpath(): 
    try: 
     subprocess.call([os.getcwd() + '/lumberjack']) 
     # change the word lumberjack on the line above to get an error 
    except OSError: 
     print('\nCould not find lumberjack, please reinstall.\n') 
     # if you're using python 2.x, change the() to spaces on the line above 

try: 
    subprocess.call(['lumberjack']) 
    # change the word lumberjack on the line above to get an error 
except OSError: 
    nameandpath() 

ho testato su Mac OS-X (6.8/Snow Leopard), Debian (squeeze) e Windows 7). Sembrava funzionare come volevo in tutti e tre i sistemi operativi. Ho provato a usare check_call e CalledProcessError, ma non importa quello che ho fatto, mi è sembrato di ricevere un errore ogni volta e non ho potuto ottenere lo script per gestire gli errori. Per testare la sceneggiatura ho cambiato il nome da 'lumberjack' a 'deadparrot', dato che avevo il taglialegna nella directory con il mio script.

Vedi qualche problema con questo script nel modo in cui è stato scritto?

4

subprocess genera un'eccezione, OSError, quando un comando non viene trovato.

Quando il comando viene trovato e subprocess esegue il comando per te, il codice risultato viene restituito dal comando. Lo standard è che il codice 0 significa successo e qualsiasi errore è un codice di errore diverso da zero (che varia, controlla la documentazione per il comando specifico che stai utilizzando).

Quindi, se si cattura OSError è possibile gestire il comando inesistente e se si controlla il codice del risultato è possibile scoprire se il comando è riuscito o meno.

La cosa grandiosa di subprocess è che si può fare raccogliere tutto il testo da stdout e stderr, e quindi è possibile eliminarlo o restituirlo o accedere o visualizzarlo come ti piace. Uso spesso un wrapper che scarta tutto l'output da un comando, a meno che il comando non abbia esito positivo, nel qual caso viene emesso il testo da stderr.

Sono d'accordo che non si dovrebbe chiedere agli utenti di copiare gli eseguibili in giro. I programmi devono trovarsi in una directory elencata nella variabile PATH; se manca un programma dovrebbe essere installato, o se è installato in una directory non su PATH l'utente dovrebbe aggiornare il PATH per includere quella directory.

Si noti che si ha la possibilità di provare subprocess più volte con vari percorsi hard-coded per eseguibili:

import os 
import subprocess as sp 

def _run_cmd(s_cmd, tup_args): 
    lst_cmd = [s_cmd] 
    lst_cmd.extend(tup_args) 
    result = sp.call(lst_cmd) 
    return result 

def run_lumberjack(*tup_args): 
    try: 
     # try to run from /usr/local/bin 
     return _run_cmd("/usr/local/bin/lumberjack", tup_args) 
    except OSError: 
     pass 

    try: 
     # try to run from /opt/forest/bin 
     return _run_cmd("/opt/forest/bin/lumberjack", tup_args) 
    except OSError: 
     pass 

    try: 
     # try to run from "bin" directory in user's home directory 
     home = os.getenv("HOME", ".") 
     s_cmd = home + "/bin/lumberjack" 
     return _run_cmd(s_cmd, tup_args) 
    except OSError: 
     pass 

    # Python 3.x syntax for raising an exception 
    # for Python 2.x, use: raise OSError, "could not find lumberjack in the standard places" 
    raise OSError("could not find lumberjack in the standard places") 

run_lumberjack("-j") 

EDIT: Dopo averci pensato un po ', ho deciso di riscrivere completamente il sopra. È molto più semplice passare semplicemente un elenco di posizioni e fare un giro per provare le posizioni alternative finché non si lavora. Ma non volevo costruire la stringa per la home directory dell'utente se non era necessaria, quindi ho semplicemente reso legale inserire un callable nella lista delle alternative. Se avete domande su questo, basta chiedere.

import os 
import subprocess as sp 

def try_alternatives(cmd, locations, args): 
    """ 
    Try to run a command that might be in any one of multiple locations. 

    Takes a single string argument for the command to run, a sequence 
    of locations, and a sequence of arguments to the command. Tries 
    to run the command in each location, in order, until the command 
    is found (does not raise OSError on the attempt). 
    """ 
    # build a list to pass to subprocess 
    lst_cmd = [None] # dummy arg to reserve position 0 in the list 
    lst_cmd.extend(args) # arguments come after position 0 

    for path in locations: 
     # It's legal to put a callable in the list of locations. 
     # When this happens, we should call it and use its return 
     # value for the path. It should always return a string. 
     if callable(path): 
      path = path() 

     # put full pathname of cmd into position 0 of list  
     lst_cmd[0] = os.path.join(path, cmd) 
     try: 
      return sp.call(lst_cmd) 
     except OSError: 
      pass 
    raise OSError('command "{}" not found in locations list'.format(cmd)) 

def _home_bin(): 
    home = os.getenv("HOME", ".") 
    return os.path.join(home, "bin") 

def run_lumberjack(*args): 
    locations = [ 
     "/usr/local/bin", 
     "/opt/forest/bin", 
     _home_bin, # specify callable that returns user's home directory 
    ] 
    return try_alternatives("lumberjack", locations, args) 

run_lumberjack("-j") 
19

Un semplice frammento:

try: 
    subprocess.check_call(['executable']) 
except subprocess.CalledProcessError: 
    pass # handle errors in the called executable 
except OSError: 
    pass # executable not found