6.12. Gestire gli argomenti da riga di comando

Python supporta pienamente la creazione di programmi che possono essere eseguiti da riga di comando, completi di argomenti, sia con flag nel formato breve che nel formato esteso per specificare le varie opzioni. Nessuno di questi è specifico di XML, ma questo script fa un buon uso dell'interpretazione da riga di comando, dunque pare un buon momento per menzionarla.

È difficile parlare dell'elaborazione da riga di comando senza prima capire come gli argomenti da riga di comando vengono presentati al programma Python, dunque scriviamo un semplice programma per vederli.

Esempio 6.43. Introduzione a sys.argv

Se non lo avete ancora fatto, potete scaricare questo ed altri esempi usati in questo libro.

#argecho.py
import sys

for arg in sys.argv: 1
    print arg
1 Ogni argomento da riga di comando passato al programma sarà in sys.argv, che è semplicemente una lista. Qui stiamo stampando ogni argomento su una riga diversa.

Esempio 6.44. Il contenuto di sys.argv

[f8dy@oliver py]$ python argecho.py             1
argecho.py
[f8dy@oliver py]$ python argecho.py abc def     2
argecho.py
abc
def
[f8dy@oliver py]$ python argecho.py --help      3
argecho.py
--help
[f8dy@oliver py]$ python argecho.py -m kant.xml 4
argecho.py
-m
kant.xml
1 La prima cosa da sapere su sys.argv è che contiene il nome dello script che stiamo chiamando. Utilizzeremo questa conoscenza a nostro vantaggio più tardi, nel capitolo Programmazione orientata ai dati. Non preoccupatevi di questo per ora.
2 Gli argomenti da riga di comando sono separati da spazi ed ognuno di essi viene rappresentato da un elemento nella lista sys.argv.
3 Anche i flag da riga di comando, come --help, vengono rappresentati come elementi della lista sys.argv.
4 Per rendere le cose ancor più interessanti, alcuni flag da riga di comando possono a loro volta prendere degli argomenti. Per esempio, qui abbiamo il flag (-m) che prende un argomento (kant.xml). Entrambi, sia il flag che il suo argomento, sono semplicemente degli elementi sequenziali nella lista sys.argv. Non viene fatto alcun tentativo di associare l'uno con l'altro; tutto quello che ottenete è una lista.

Come possiamo vedere, tutte le informazioni vengono passate da riga di comando ma non sembra che sia poi tutto così semplice da usare. Per programmi semplici che prendono un solo argomento e non hanno flag, potete semplicemente usare sys.argv[1] per leggere l'argomento. Non c'è vergogna in questo, io lo faccio sempre. Per programmi più complessi, avete bisogno del modulo getopt.

Esempio 6.45. Introduzione a getopt


def main(argv):                         
    grammar = "kant.xml"                 1
    try:                                
        opts, args = getopt.getopt(argv, "hg:d", ["help", "grammar="]) 2
    except getopt.GetoptError:           3
        usage()                          4
        sys.exit(2)                     

...

if __name__ == "__main__":
    main(sys.argv[1:])
1 Per prima cosa, date un'occhiata alla fine dell'esempio e notate che stiamo chiamando la funzione main con sys.argv[1:]. Ricordate, sys.argv[0] è il nome dello script che stiamo eseguendo; non ne siamo interessati per l'elaborazione da riga di comando, così lo possiamo togliere e passare il resto della lista.
2 È qui che avvengono le elaborazioni interessanti. La funzione getopt del modulo getopt prende tre parametri: la lista degli argomenti (che abbiamo ottenuto da sys.argv[1:]), una stringa contenente tutti i possibili flag composti da un solo carattere che questo programma può accettare, ed una lista di comandi più lunghi che sono equivalenti alla loro versione da un solo carattere. È abbastanza intricato a prima vista e sarà spiegato in maggior dettaglio in seguito.
3 Se qualcosa dovesse andare male provando ad analizzare questi flag da riga di comando, getopt solleverà un'eccezione, che noi cattureremo. Abbiamo detto a getopt tutti i flag che conosciamo, dunque questo significa che probabilmente l'utente ha passato qualche flag che non siamo in grado di interpretare.
4 Come di consueto nel mondo UNIX, quando al nostro script vengono passati dei flag che non può capire, stampiamo un riassunto sull'utilizzo corretto del programma ed usciamo. Notate che non ho mostrato la funzione usage qui. Dovremmo scriverla da qualche parte e farle stampare un riassunto appropriato; non è automatico.

Cosa sono dunque tutti questi parametri che passiamo alla funzione getopt? Beh, il primo è semplicemente una lista grezza dei flag e degli argomenti da riga di comando (escluso il primo elemento, il nome dello script, che abbiamo rimosso prima di chiamare la funzione main). Il secondo è la lista dei flag brevi che il nostro script accetta.

"hg:d"

-h
print usage summary
-g ...
use specified grammar file or URL
-d
show debugging information while parsing

Il primo ed il terzo flag sono semplicemente dei flag autonomi; li potete specificare oppure no, fanno comunque qualcosa (stampa un aiuto) o cambiano uno stato (abilita il debugging). Comunque, il secondo flag (-g) deve essere seguito da un argomento, che è il nome del file di grammatica da cui leggere. Infatti può essere il nome di un file o un indirizzo web, e non sappiamo ancora quale (lo vedremo dopo), ma sappiamo che deve essere qualcosa. Dunque lo diciamo a getopt inserendo i due punti dopo g nel secondo parametro della funzione getopt.

Per complicare ulteriormente le cose, il nostro script accetta sia flag brevi (come -h) o lunghi (come --help), e vogliamo che facciano la stessa cosa. Questo è lo scopo del terzo parametro di getopt, specificare una lista di flag lunghi che corrispondono a quelli brevi specificati nel secondo parametro.

["help", "grammar="]

--help
print usage summary
--grammar ...
use specified grammar file or URL

Ci sono tre cose da notare qui:

  1. Tutti i flag lunghi sono preceduti da due trattini sulla riga di comando, ma noi non li includiamo nella chiamata a getopt. Sono compresi automaticamente.
  2. Il flag --grammar deve sempre essere seguito da un argomento addizionale, proprio come il flag -g. È evidenziato dal segno uguale, "grammar=".
  3. La lista di flag lunghi è più corta della lista di flag corti, perché il flag -d non ha una corrispondente versione lunga. Questo va bene, solamente -d imposterà il debugging. Ma l'ordine di flag lunghi e corti deve essere il medesimo, dunque dovete specificare per primi i flag brevi che hanno un corrispondente flag lungo, seguiti dal resto dei flag brevi.

Ancora confusi? Diamo un'occhiata al codice e vediamo se ora ha più senso.

Esempio 6.46. Gestire gli argomenti da riga di comando in kgp.py


def main(argv):                          1
    grammar = "kant.xml"                
    try:                                
    except getopt.GetoptError:          
        usage()                         
        sys.exit(2)                     
    for opt, arg in opts:                2
        if opt in ("-h", "--help"):      3
            usage()                     
            sys.exit()                  
        elif opt == '-d':                4
            global _debug               
            _debug = 1                  
        elif opt in ("-g", "--grammar"): 5
            grammar = arg               

    source = "".join(args)               6

    k = KantGenerator(grammar, source)
    print k.output()
1 La variabile grammar terrà traccia del file di grammatica che stiamo usando. La inizializziamo qui nel caso non sia specificata da riga di comando (utilizzando il flag -g o il flag --grammar).
2 La variabile opts che otteniamo da getopt contiene una lista di tuple: flag ed argomento. Se il flag non prende un argomento, allora arg sarà semplicemente None. Questo semplifica la creazione di un ciclo che percorre i flag.
3 getopt valida che i flag da riga di comando siano accettabili, ma non fa alcun genere di conversione tra flag lunghi e brevi. Se specificate il flag -h, opt conterrà "-h"; se specificate il flag --help, opt conterrà "--help". Dunque dobbiamo controllarli entrambi.
4 Ricordate, il flag -d non ha un corrispondente flag lungo, quindi dobbiamo solamente controllarne la forma breve. Se lo troviamo, impostiamo una variabile globale a cui ci riferiremo più avanti per stampare delle informazioni di debug. (L'ho usato durante lo sviluppo dello script. Pensavate che tutti questi esempi fossero funzionati al primo colpo?)
5 Se troviamo un file di grammatica, sia con il flag -g che con il flag --grammar, salviamo l'argomento che lo segue (memorizzato in arg) nella nostra variabile grammar, sovrascrivendo il valore predefinito che abbiamo inizializzato all'inizio della nostra funzione main.
6 Ecco fatto. Abbiamo scorso tutti i flag da riga di comando e ci siamo occupati di ognuno di essi. Significa che tutto ciò che è rimasto devono essere argomenti da riga di comando. Questi tornano indietro dalla funzione getopt nella variabile args. In questo caso, li stiamo trattando come materiale da passare al nostro parser. Se non vi sono argomenti specificati nella riga di comando, args sarà una lista vuota, e source sarà trattato come una stringa vuota.