Successivo: Funzioni Passwd, Precedente: Gestione File Dati, Su: Funzioni di libreria [Contenuti][Indice]
La maggior parte dei programmi di utilità su sistemi compatibili con POSIX
prevedono opzioni presenti sulla riga di comando che possono essere usate per
cambiare il modo in cui un programma si comporta. awk
è un esempio di
tali programmi (vedi la sezione Opzioni sulla riga di comando).
Spesso le opzioni hanno degli argomenti (cioè, dati che servono al
programma per eseguire correttamente le opzioni specificate
sulla riga di comando).
Per esempio, l’opzione -F di awk
richiede di usare la stringa
specificata
come separatore di campo. La prima occorrenza, sulla riga di comando, di
-- o di una stringa che non inizia con ‘-’ segnala la fine
delle opzioni.
I moderni sistemi Unix hanno una funzione C chiamata getopt()
per
elaborare gli argomenti presenti
sulla riga di comando. Il programmatore fornisce una
stringa che descrive le opzioni, ognuna delle quali consiste di
una sola lettera. Se un’opzione richiede un
argomento, nella stringa l’opzione è seguita da due punti (:
).
A getopt()
vengono anche
passati il numero e i valori degli argomenti presenti sulla riga di comando
e viene chiamata in un ciclo.
getopt()
scandisce gli argomenti della riga di comando cercando
le lettere delle opzioni.
A ogni passaggio del ciclo restituisce un carattere
singolo che rappresenta la successiva lettera di opzione trovata, o ‘?’
se viene trovata un’opzione non prevista.
Quando restituisce -1, non ci sono ulteriori
opzioni da trattare sulla riga di comando.
Quando si usa getopt()
, le opzioni che non prevedono argomenti
possono essere raggruppate.
Inoltre, le opzioni che hanno argomenti richiedono obbligatoriamente che
l’argomento sia specificato.
L’argomento può seguire immediatamente la lettera
dell’opzione, o può costituire un argomento separato sulla riga di comando.
Dato un ipotetico programma che ha tre opzioni sulla riga di comando, -a, -b e -c, dove -b richiede un argomento, tutti i seguenti sono modi validi per invocare il programma:
programma -a -b pippo -c dati1 dati2 dati3 programma -ac -bpippo -- dati1 dati2 dati3 programma -acbpippo dati1 dati2 dati3
Si noti che quando l’argomento è raggruppato con la sua opzione, la parte rimanente dell’argomento è considerato come argomento dell’opzione. In quest’esempio, -acbpippo indica che tutte le opzioni -a, -b e -c sono presenti, e che ‘pippo’ è l’argomento dell’opzione -b.
getopt()
fornisce quattro variabili esterne a disposizione del
programmatore:
optind
L’indice nel vettore dei valori degli argomenti (argv
) dove si può
trovare il primo argomento sulla riga di comando che non sia un’opzione.
optarg
Il valore (di tipo stringa) dell’argomento di un’opzione.
opterr
Solitamente getopt()
stampa un messaggio di errore quando trova un’opzione
non valida. Impostando opterr
a zero si disabilita questa funzionalità.
(un’applicazione potrebbe voler stampare un proprio messaggio di errore.)
optopt
La lettera che rappresenta l’opzione sulla riga di comando.
Il seguente frammento di codice C mostra come getopt()
potrebbe
elaborare gli argomenti della riga di comando per awk
:
int main(int argc, char *argv[]) { … /* stampa un appropriato messaggio */ opterr = 0; while ((c = getopt(argc, argv, "v:f:F:W:")) != -1) { switch (c) { case 'f': /* file */ … break; case 'F': /* separatore di campo */ … break; case 'v': /* assegnamento di variabile */ … break; case 'W': /* estensione */ … break; case '?': default: messaggio_di_aiuto(); break; } } … }
La versione fornita dal progetto GNU dei programmi di utilità originali Unix hanno popolarizzato l’uso della versione lunga delle opzioni immesse nella riga di comando. Per esempio, --help in aggiunta a -h. Gli argomenti per queste opzioni lunghe sono forniti come argomenti separati [da uno o più spazi] sulla riga di comando (‘--source 'program-text'’) oppure sono separati dalla relativa opzione da un segno ‘=’ (‘--source='program-text'’).
Incidentalmente, gawk
al suo interno usa la funzione GNU
getopt_long()
per elaborare sia le normali opzioni che quelle lunghe
in stile GNU
(vedi la sezione Opzioni sulla riga di comando).
L’astrazione fornita da getopt()
è molto utile ed è piuttosto
comoda anche nei programmi awk
. Di seguito si riporta una
versione awk
di getopt()
che accetta sia le opzioni+
lunghe che quelle corte.
Questa funzione mette in evidenza uno dei
maggiori punti deboli di awk
, che è quello di essere molto carente
nella manipolazione di caratteri singoli. La funzione deve usare ripetute
chiamate a substr()
per accedere a caratteri singoli
(vedi la sezione Funzioni di manipolazione di stringhe).75
La spiegazione della funzione viene data man mano che si elencano i pezzi di codice che la compongono:
# getopt.awk --- imita in awk la funzione di libreria C getopt(3) # Supporta anche le opzioni lunghe. # Variabili esterne: # Optind -- indice in ARGV del primo argomento che non è un'opzione # Optarg -- valore di tipo stringa dell'argomento dell'opzione corrente # Opterr -- se diverso da zero, viene stampato un messaggio diagnostico # Optopt -- lettera dell'opzione corrente # Restituisce: # -1 alla fine delle opzioni # "?" per un'opzione non riconosciuta # <s> una stringa che rappresenta l'opzione corrente # Dati privati: # _opti -- indice in un'opzione multipla, p.es., -abc
La funzione inizia con commenti che elencano e descrivono le variabili globali utilizzate, spiegano quali sono i codici di ritorno, il loro significato, e ogni altra variabile che è “esclusiva” a questa funzione di libreria. Tale documentazione è essenziale per qualsiasi programma, e in modo particolare per le funzioni di libreria.
La funzione getopt()
dapprima controlla di essere stata effettivamente
chiamata con una stringa di opzioni (il parametro opzioni
). Se
sia opzioni
che opzioni_lunghe
hanno lunghezza zero,
getopt()
restituisce immediatamente -1:
function getopt(argc, argv, opzioni, opzioni_lunghe, unaopz, i) { if (length(opzioni) == 0 && length(opzioni_lunghe) == 0) return -1 # nessuna opzione specificata
if (argv[Optind] == "--") { # fatto tutto Optind++ _opti = 0 return -1
} else if (argv[Optind] !~ /^-[^:[:space:]]/) { _opti = 0 return -1 }
Il successivo controllo cerca la fine delle opzioni. Due trattini
(--) marcano la fine delle opzioni da riga di comando, e lo stesso
fa qualsiasi argomento sulla riga di comando che non inizi con ‘-’
(a meno che non sia un argomento all’opzione immediatamente precedente).
Optind
è usato per scorrere il vettore degli argomenti presenti
sulla riga di comando; mantiene il suo valore attraverso chiamate
successive a getopt()
, perché è una variabile globale.
L’espressione regolare /^-[^:[:space:]/
, chiede di cercare un
‘-’ seguito da qualsiasi cosa che non sia uno spazio vuoto o un carattere
di due punti (:
).
Se l’argomento corrente sulla riga di comando non corrisponde a
quest’espressione regolare, vuol dire che non si tratta di un’opzione, e
quindi viene terminata l’elaborazione delle opzioni.
A questo punto si controlla se stiamo elaborando un’opzione corta (che
consiste di una sola lettera), oppure un’opzione lunga (indicata da due
trattini, p.es. ‘--filename’). Se si tratta di un’opzione corta,
il programma prosegue così:
if (argv[Optind] !~ /^--/) { # se opzione corta if (_opti == 0) _opti = 2 unaopz = substr(argv[Optind], _opti, 1) Optopt = unaopz i = index(opzioni, unaopz) if (i == 0) { if (Opterr) printf("%c -- opzione non ammessa\n", unaopz) > "/dev/stderr" if (_opti >= length(argv[Optind])) { Optind++ _opti = 0 } else _opti++ return "?" }
La variabile _opti
tiene traccia della posizione nell’argomento
della riga di comando correntemente in esame
(argv[Optind]
). Se opzioni multiple sono
raggruppate con un ‘-’ (p.es., -abx), è necessario
restituirle all’utente una per volta.
Se _opti
è uguale a zero, viene impostato a due, ossia all’indice
nella
stringa del successivo carattere da esaminare (‘-’, che è alla
posizione uno viene ignorato).
La variabile unaopz
contiene il carattere,
ottenuto con substr()
. Questo è salvato in Optopt
per essere
usato dal programma principale.
Se unaopz
non è nella stringa delle opzioni opzioni
,
si tratta di un’opzione
non valida. Se Opterr
è diverso da zero, getopt()
stampa un
messaggio di errore sullo standard error che è simile al messaggio
emesso dalla versione C di getopt()
.
Poiché l’opzione non è valida, è necessario tralasciarla e passare al successivo
carattere di opzione. Se _opti
è maggiore o uguale alla lunghezza
dell’argomento corrente della riga di comando, è necessario passare al
successivo argomento, in modo che Optind
venga incrementato e
_opti
sia reimpostato a zero. In caso contrario, Optind
viene
lasciato com’è e _opti
viene soltanto incrementato.
In ogni caso, poiché l’opzione non è valida, getopt()
restituisce
"?"
. Il programma principale può esaminare Optopt
se serve
conoscere quale lettera di opzione è quella non valida. Proseguendo:
if (substr(opzioni, i + 1, 1) == ":") { # ottiene un argomento di opzione if (length(substr(argv[Optind], _opti + 1)) > 0) Optarg = substr(argv[Optind], _opti + 1) else Optarg = argv[++Optind] _opti = 0 } else Optarg = ""
Se l’opzione richiede un argomento, la lettera di opzione è seguita da
due punti (:
)
nella stringa opzioni
. Se rimangono altri caratteri nell’argomento
corrente sulla riga di comando (argv[Optind]
), il resto di quella stringa
viene assegnato a Optarg
. Altrimenti, viene usato il successivo
argomento sulla riga di comando (‘-xFOO’ piuttosto che
‘-x FOO’). In
entrambi i casi, _opti
viene reimpostato a zero, perché non ci sono altri
caratteri da esaminare nell’argomento corrente sulla riga di comando.
Continuando:
if (_opti == 0 || _opti >= length(argv[Optind])) { Optind++ _opti = 0 } else _opti++ return unaopz
Infine, per un’opzione corta, se _opti
è zero o maggiore della
lunghezza dell’argomento corrente sulla riga di comando, significa che
l’elaborazione di quest’elemento in argv
è terminata, quindi
Optind
è incrementato per puntare al successivo elemento
in argv
. Se nessuna delle condizioni è vera, viene incrementato
solo _opti
, cosicché la successiva lettera di opzione può
essere elaborata con la successiva chiamata a getopt()
.
D’altra parte, se il test precedente determina che si ha a che fare con un’opzione lunga, il programma prosegue in maniera differente:
} else { j = index(argv[Optind], "=") if (j > 0) unaopz = substr(argv[Optind], 3, j - 3) else unaopz = substr(argv[Optind], 3) Optopt = unaopz
Per prima cosa cerchiamo di vedere se quest’opzione contiene al suo interno un segno "=", in quanto per le opzioni lunghe (p.es. ‘--unaopzione’) è possibile che il relativo argomento sia specificato sia come ‘--unaopzione=valore’ che come ‘--unaopzione valore’.
i = match(opzioni_lunghe, "(^|,)" unaopz "($|[,:])") if (i == 0) { if (Opterr) printf("%s -- opzione non ammessa\n", unaopz) > "/dev/stderr" Optind++ return "?" }
Poi, si tenta di trovare l’opzione corrente in opzioni_lunghe
.
L’espressione regolare fornita a match()
,
"(^|,)" unaopz "($|[,:])"
,
trova una corrispondenza per quest’opzione all’inizio di
opzioni_lunghe
, o all’inizio di una successiva opzione lunga
(l’opzione lunga precedente dovrebbe essere delimitata da una
virgola) e, comunque, o alla fine della stringa opzioni_lunghe
(‘$’), oppure seguita da una virgola
(che separa quest’opzione da un’opzione successiva) da un
":" (che indica che quest’opzione lunga accetta un argomento
(‘[,:]’).
Utilizzando quest’espressione regolare, si controlla che l’opzione
corrente è presente oppure no in opzioni_lunghe
(anche nel caso
in cui la stringa opzioni_lunghe
non sia stata specificata, il test
darà un risultato negativo). In caso di errore, si può stampare un
messaggio di errore e poi restituire "?"
.
Si continua poi:
if (substr(opzioni_lunghe, i+1+length(unaopz), 1) == ":") { if (j > 0) Optarg = substr(argv[Optind], j + 1) else Optarg = argv[++Optind] } else Optarg = ""
A questo punto si vede se quest’opzione accetta un argomento e, se
così è, si imposta Optarg
al valore di tale argomento
(sia che si tratti del valore che segue il segno "=" sulla riga
di comando, sia che si tratti del successivo argomento sulla riga
si comando stessa).
Optind++ return unaopz } }
Incrementiamo Optind
(che era già stato incrementato una volta
se un argomento richiesto era separato dalla relativa opzione con un
segno di "="), e restituiamo l’opzione lunga (senza i trattini iniziali).
La regola BEGIN
inizializza sia Opterr
che Optind
a uno.
Opterr
viene impostato a uno, perché il comportamento di default per
getopt()
è quello di stampare un messaggio diagnostico dopo aver visto
un’opzione non valida. Optind
è impostato a uno, perché non
c’è alcun motivo
per considerare il nome del programma, che è in ARGV[0]
:
BEGIN { Opterr = 1 # il default è eseguire una diagnosi Optind = 1 # salta ARGV[0] # programma di controllo if (_getopt_test) { _myshortopts = "ab:cd" _mylongopts = "longa,longb:,otherc,otherd" while ((_go_c = getopt(ARGC, ARGV, _myshortopts, _mylongopts)) != -1) printf("c = <%s>, Optarg = <%s>\n", _go_c, Optarg) printf("argomenti che non sono opzioni:\n") for (; Optind < ARGC; Optind++) printf("\tARGV[%d] = <%s>\n", Optind, ARGV[Optind]) } }
Il resto della regola BEGIN
è un semplice programma di controllo. Qui
sotto si riportano i risultati di
alcune esecuzioni di prova
del programma di controllo:
$ awk -f getopt.awk -v _getopt_test=1 -- -a -cbARG bax -x -| c = <a>, Optarg = <> -| c = <c>, Optarg = <> -| c = <b>, Optarg = <ARG> -| argomenti che non sono opzioni: -| ARGV[3] = <bax> -| ARGV[4] = <-x> $ awk -f getopt.awk -v _getopt_test=1 -- -a -x -- xyz abc -| c = <a>, Optarg = <> error→ x -- opzione non ammessa -| c = <?>, Optarg = <> -| argomenti che non sono opzioni: -| ARGV[4] = <xyz> -| ARGV[5] = <abc> $ awk -f getopt.awk -v _getopt_test=1 -- -a \ > --longa -b xx --longb=foo=bar --otherd --otherc arg1 arg2 -| c = <a>, Optarg = <> -| c = <longa>, Optarg = <> -| c = <b>, Optarg = <xx> -| c = <longb>, Optarg = <foo=bar> -| c = <otherd>, Optarg = <> -| c = <otherc>, Optarg = <> -| argomenti che non sono opzioni: -| ARGV[8] = <arg1> -| ARGV[9] = <arg2>
In tutte le esecuzioni, il primo -- fa terminare gli argomenti dati
ad awk
, in modo che awk
non tenti di interpretare le opzioni
-a, etc. come sue opzioni.
NOTA: Dopo che
getopt()
è terminato, il codice a livello utente deve eliminare tutti gli elementi diARGV
da 1 aOptind
, in modo cheawk
non tenti di elaborare le opzioni sulla riga di comando come nomi-file.
Usare ‘#!’ con l’opzione -E può essere d’aiuto per evitare
conflitti tra le opzioni del proprio programma e quelle di gawk
,
poiché l’opzione -E fa sì che gawk
abbandoni
l’elaborazione di ulteriori opzioni.
(vedi la sezione Programmi awk
da eseguire come script e
vedi la sezione Opzioni sulla riga di comando).
Molti degli esempi presentati in
Programmi utili scritti in awk
,
usano getopt()
per elaborare i propri argomenti.
Questa funzione
è stata scritta prima che gawk
acquisisse la capacità di
dividere le stringhe in caratteri singoli usando ""
come separatore.
È stata lasciata così, poiché l’uso di substr()
è più portabile.
Successivo: Funzioni Passwd, Precedente: Gestione File Dati, Su: Funzioni di libreria [Contenuti][Indice]