Successivo: , Precedente: , Su: Funzioni di libreria   [Contenuti][Indice]


10.4 Elaborare opzioni specificate sulla riga di comando

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 di ARGV da 1 a Optind, in modo che awk 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.


Note a piè di pagina

(75)

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: , Precedente: , Su: Funzioni di libreria   [Contenuti][Indice]