La command line 

Si intende, per command line, la riga di testo digitata al prompt di sistema: essa contiene un comando DOS (che può essere il nome di un programma), completo degli eventuali parametri da avviamento. Ad esempio, la command line 
pippo /a /b file0 *.dat
lancia il programma PIPPO passandogli quattro parametri, separati tra loro da spazi. 

E' noto che il linguaggio C mette a disposizione un metodo semplice e standardizzato per accedere ai parametri della command line: allo scopo è sufficiente dichiarare la funzione main() con due parametri, nell'ordine un intero e un array di stringhe (un array di puntatori a carattere), convenzionalmente chiamati argc e argv (arguments counter e arguments vector): 
void main(int argc, char **argv)

    ....
La variabile argc contiene il numero di parole, separate da spazi, presenti sulla command line (incluso, dunque, il nome del programma); gli elementi di argv referenziano le parole componenti la command line (argv[0] punta al nome del programma, completo di pathname[1]). Ogni sequenza di caratteri compresa tra due o più spazi (o tabulazioni) è considerata un parametro (se si intende passare al programma una stringa contenente spazi come un unico parametro è sufficiente racchiuderla tra doppi apici, "come questa") e le wildcard non vengono espanse (con riferimento all'esempio sopra riportato, argv[4] in PIPPO contiene "*.dat"). 

Aggiungendo un terzo parametro a main() è possibile avere a disposizione anche le stringhe che costituiscono l'environment del programma[2]
void main(int argc, char **argv, char **envp)

    ....
Come argv, anche envp (environment pointer) è un array di puntatori a stringa, o meglio un array di puntatori a carattere. 

Il lavoro di parsing (cioè di scansione) della command line e di valorizzazione di inzializzazione delle stringhe e dei rispettivi puntatori è svolto da due funzioni di libreria, non documentate in quanto implementate per uso interno: nelle librerie del C Borland i loro nomi sono _setargv__() e _setenvp__() [3]

_setargv__() e _setenvp__() 

Appare evidente che _setargv__() e _setenvp__() svolgono un compito utile solo se effettivamente il programma ha necessità di conoscere le stringhe che compongono l'environment e, rispettivamente, la command line. Esse vengono tuttavia invocate dal modulo di startup in ogni caso: è possibile accrescere l'efficienza dei programma che non necessitano di tali informazioni con un semplice stratagemma. 
#pragma option -k-      /* evita stack frame nelle funzioni senza parametri */

void _setargv__(void)   /* sostituisce la _setargv__() di libreria */
{
}

void _setenvp__(void)   /* sostituisce la _setenvp__() di libreria */
{
}

void main(void) /* non usa command line ed environment */
{
    ....
}
Naturalmente si può dichiarare una sola funzione fittizia, qualora l'altra sia necessaria: 
#pragma option -k-      /* evita stack frame nelle funzioni senza parametri */

void _setenvp__(void)   /* sostituisce la _setenvp__() di libreria */
{
}

void main(int argc, char **argv)        /* non usa l'environment */
{
    ....
}
La dichiarazione di entrambe le funzioni _set...() riduce di alcune centinaia di byte la dimensione dell'eseguibile[4] (.COM o .EXE) e ne rende leggermente più rapido il caricamento. 

Entrambe _setargv__() e _setenvp__() sono eseguite prima di main(), siano esse le funzioni di libreria o quelle dichiarate nel sorgente: in questo caso nulla vieta al programmatore di crearne versioni personalizzate, non necessariamente "nullafacenti". 

WILDARGS.OBJ 

Una versione più sofisticata di _setargv__() è quella implementata nel modulo WILDARGS.OBJ (fornito con il compilatore): essa è in grado di espandere le wildcards e restituire in argv l'elenco completo dei file corrispondenti ai nomi "collettivi" specificati sulla command line. Se il file PIPPO.C è compilato con: 
bcc pippo.c wildargs.obj
il vettore argv ricavato dalla command line dell'esempio presentato poco sopra può avere più di quattro elementi, in quanto *.dat è espanso in tante stringhe quanti sono i file .dat effettivamente presenti nella directory. Solo se non vi è alcun file .dat argv ha cinque elementi ed argv[4] è ancora "*.dat". 

Esiste un solo object file WILDARGS.OBJ per tutti i modelli di memoria

PSP e command line 

Le variabili argc e argv e il file WILDARGS.OBJ costituiscono gli strumenti standard per la gestione della command line: essi rendono disponibili gli elemeti (stringhe) che la compongono. Chi desideri andare "al di là" del C, ed accedere direttamente alla riga di comando digitata al prompt[5], deve vedersela con il Program Segment Prefix

Infatti, nel PSP, il byte ad offset 80h memorizza la lunghezza della command line (escluso il nome del programma ed incluso il CR terminale); ad offset 81h si trova la riga di comando (a partire dal primo spazio o tabulazione seguente il nome del programma), che include il carattere di CR

Il mini­programma dell'esempio seguente, compilato sotto Borland C++ 2.0, stampa la command line, o meglio la sequenza di argomenti passati al programma tramite questa. 
#include <stdio.h>
#include <dos.h>        // per MK_FP()
#include <string.h>     // per _fstrncpy()

void main(void)
{
    int cmdlen;
    char cmdline[127];
    extern unsigned _psp;  // indirizzo di segmento del PSP

    cmdlen = (int)(*(char far *)MK_FP(_psp,0x80));
    _fstrncpy((char far *)cmdline,(char far *)MK_FP(_psp,0x81),cmdlen);
    cmdline[cmdlen] = NULL;
    puts(cmdline);
}
Si noti che main() non ha parametri: la command line viene infatti letta direttamente nel PSP. La variabile cmdlen viene valorizzata con la lunghezza della command line: il doppio cast è necessario in quanto detto dato è costituito, nel PSP, da un solo byte; il cast di MK_FP() a (int far *) avrebbe l'effetto di restituire una word in cui il byte più significativo è, in realtà, il primo carattere della riga di comando (di solito un blank). Il buffer cmdline è dimensionato a 127 byte: infatti tale è la massima lunghezza della command line (incluso il CR terminale, che deve essere sostituito con un NULL per ottenere una stringa stampabile dalle funzioni C). Per copiare in cmdline la porzione di di nostro interesse è utilizzata _fnstrcpy(), che è equivalente a strncpy() ma accetta puntatori far [6]. Se le librerie del compilatore utilizzate non includono una versione far della strncpy() è necessario copiare la stringa byte per byte, ad esempio con un ciclo while
    ....
    register i = 0;
    ....
    while((cmdline[i] = *(char far *)MK_FP(_psp,i+0x81)) != (char)0x0D)
        i++;
    cmdline[i] = NULL;
    ....
Dal momento che la seconda metà (gli ultimi 128 byte) del è utilizzata dal DOS come DTA (Disk Transfer Address) di default per il programma, è indispensabile che le operazioni descritte siano le prime eseguite dal programma stesso, onde evitare che la command line sia sovrascritta. 

Una gestione Unix-like 

Nei sistemi Unix la command line è spesso utilizzata per fornire al programma invocato direttive, dette opzioni, di modifica degli eventuali default di funzionamento. Nel tempo, la modalità di elencazione delle opzioni ha raggiunto, sia pure in modo non esplicitamente formale, un livello di standardizzazione tale da poter parlare di una vera e propria sintassi delle opzioni, in parte ripresa e spesso seguita anche in ambiente DOS. 

I parametri di main() argc e argv costituiscono solo una base di partenza per implementare una gestione Unix­like delle opzioni di command line: il resto è lasciato alla buona volontà del programmatore. Proviamo a scendere nel dettaglio: lo scopo è definire una sintassi per le command line option analoga a quella comunemente seguita nei sistemi Unix, e realizzare un insieme di funzioni libreria in grado di implementare in qualunque programma la gestione delle opzioni secondo quella sintassi. 

La sintassi 

Va innazitutto precisato che per opzione si intende un carattere alfanumerico, detto optLetter, preceduto sulla command line da un particolare carattere, detto switch character, che ha proprio lo scopo di identificare la optLetter quale opzione. In ambiente DOS lo switch character è, solitamente, la barra '/'; nei sistemi Unix è usato il trattino '­'. Il programma può prevedere che l'opzione supporti un parametro, detto argomento, rappresentato da una sequenza di caratteri qualsiasi, nel qual caso il carattere alfanumerico che rappresenta l'opzione è detto argLetter (e non optLetter). 

Il programma riconosce le optLetter e le argLetter in quanto esse sono elencate in una stringa, convenzionalmente denominata optionS, in base alle regole elencate di seguito: 
1)
La stringa optionS non deve contenere spazi[7]
2)
Le optLetter e le argLetter non possono essere segni di interpunzione. 
3)
Se il programma deve considerare equivalenti maiuscole e minuscole è necessario inserire in optionS sia la maiuscola che la minuscola per ogni optLetter o argLetter. 
4)
Ogni argLetter è seguita dal carattere ':'. 
5)
Per le argLetter non è data alcuna indicazione sugli argomenti validi: spetta al programma valutarli ed accettarli o segnalare eventuali errori. 
Lo switch character e la optLetter (o argLetter seguita dal proprio argomento) costituiscono un option cluster (gruppo di opzioni), nell'ambito del quale valgono le regole seguenti: 
1)
Lo switch deve essere preceduto da uno spazio e seguito immediatamente da optLetter o argLetter. 
2)
Uno switch isolato tra due spazi è un errore di sintassi. 
3)
Uno switch seguito da un carattere non compreso tra le optLetter e le argLetter riconosciute dal programma è un errore di sintassi. 
4)
Una optLetter può essere seguita da uno spazio o dalla successiva optLetter o argLetter. 
5)
Una argLetter deve essere seguita dal proprio argomento, che non può essere uno spazio. 
6)
Tra una argLetter e l'argomento può esservi uno spazio oppure un carattere ':'. 
7)
Se un argomento inizia con il carattere ':', questo deve essere ripetuto. 
8)
Un argomento può contenere (anche quale carattere iniziale) lo switch character; se contiene spazi deve essere compreso tra virgolette. 
9)
Un argomento deve essere seguito da uno spazio. 
10)
Maiuscole e minuscole non sono equivalenti, tanto in optLetter e argLetter quanto negli argomenti. 
11)
Gli option cluster devono essere elencati tutti all'inizio della command line; la prima stringa presente in essa non introdotta dallo switch character è considerata il primo dei parametri non­option. 
12)
Se il primo parametro non­option inizia con lo switch character, questo deve essere ripetuto due volte. 
13)
La ripetizione, nella command line, della medesima opzione è sintatticamente lecita: spetta al programma valutare se ciò costituisca un errore. 
Spaventati? Non è il caso: tale insieme di regole è complesso solo apparentemente. In effetti esso formalizza una realtà probabilmente già nota. Vediamo un paio di esempi. 

Se optionS è "A:F:PuU:wXZ:", nella command line: 
PROG /uPFPi /X /AL /f UnFile AltraStringa
PROG è il nome del programma e sono individuate le optLetter 'u', 'P' e 'X', nonché le argLetter 'F' (il cui argomento è "Pi") e 'A' (con l'argomento "L"), mentre la lettera 'f' non è un'opzione valida; le stringhe "UnFile" e "AltraStringa" costituiscono validi parametri non­option. 

Se optionS è "AT:J:", nella command line 
PROG -A -T:4 -AJ::k -T2 --123- -w- -  Parola "Due parole"
PROG è il nome del programma ed è riconosciuta la optLetter 'A', presente due volte; sono inoltre individuate le argLetter 'J' (con l'argomento ":k") e 'T', anch'essa presente due volte (con gli argomenti, rispettivamente, "4" e "2"). Il primo parametro non­option è la stringa "­123­"; i successivi sono le stringhe "­w­", "-", "Parola" e "Due parole". Il parametro "­" è valido, in quanto non è il primo non­option parameter (nel qual caso dovrebbe essere digitato come due trattini). 

Se optionS è, ancora, "AT:J:", nella command line 
PROG  -J -J "UnaSolaParola" -A
PROG è il nome del programma ed è riconosciuta la argLetter 'J', il cui argomento è "­J". Il primo non­option argument è la stringa "UnaSolaParola". La stringa "­A" rappresenta il secondo parametro non­option e non viene riconosciuta quale optLetter in quanto tutto ciò che segue il primo non­option argument è comunque considerato tale a sua volta. 

Le funzioni per la libreria 

Sulla scorta delle regole descritte è possibile realizzare le funzioni necessarie per una gestione Unix-like[8] della command line. Di seguito è presentato il listato di PARSEOPT.H, header file incluso nel sorgente delle funzioni e necessario anche per la realizzazione di programmi che le richiamano. Esso contiene i prototipi delle funzioni, i templates delle strutture appositamente definiti ed alcune costanti manifeste.

Sì! Sì! Voglio studiarmi il listato! 

L'interfaccia utente di alto livello è costituito dalla parseoptions(), che gestisce al proprio interno, secondo la sintassi descritta poco sopra, tutte le operazioni necessarie per processare le opzioni specificate sulla riga di comando del programma. 

I suoi primi due parametri sono gli ormai noti argc e argv, che devono quindi essere dichiarati parametri di main(); il terzo parametro è la stringa optionS, che, come descritto, contiene l'elenco delle optLetter e delle argLetter. Il quarto parametro è l'indirizzo di un array di strutture di tipo VOPT, il cui template è definito, come si vede, in PARSEOPT.H. Ogni struttura è formata da due campi: 
struct VOPT {
    char opt;
    int (*fun)(struct OPT *tmp,int no);
};
Il campo opt è un carattere e rappresenta una optLetter o una argLetter, mentre il campo fun, puntatore a funzione, contiene l'indirizzo della funzione di validazione dell'optLetter o argLetter: parseoptions(), quando viene individuata sulla command line la optLetter o argLetter del campo opt, lancia la funzione il cui indirizzo è contenuto in fun, passandole un puntatore a struttura OPT (di cui diremo tra breve) e un intero esprimente la posizione dell'opzione sulla command line. La funzione a cui punta fun è definita dall'utente. Vediamo un esempio: se la optionS contiene la optLetter 'a' e abbiamo definito la funzione valid_a(), che deve essere invocata quando ­a è specificata sulla riga di comando, si ha: 
#include <STOREOPT.H>
....

char *optionS = "abc:";
....

int valid_a(struct OPT *tmp,int no)
{
    ....
}

....

int valid_err(struct OPT *tmp,int no)
{
    ....
}

int valid_glob(struct OPT *tmp,int no)
{
    ....
}

struct VOPT valfuncs[] = {
    ....
    {'a',valid_a},
    ....
    {ERRCHAR,valid_err},
    ....
    {NULL,valid_glob}
};

....

void main(int argc,char **argv)
{
    struct OPT *optArray;

    if(!(optArray = parseoptions(argc,argv,optionS,valfuncs)))
        perror("Problem");
    ....
Tralasciamo, per il momento, il contenuto di valid_a() e delle altre funzioni di validazione opzioni per analizzare alcune altre importanti caratteristiche dell'array valfuncs: il campo opt dell'ultimo elemento dell'array deve essere il carattere ASCII 0 (NULL), in quanto è proprio questo il "segnale" che consente a parseoptions() di capire che nell'array non vi sono altri elementi. Il campo fun corrispondente può, a sua volta, contenere NULL; se invece è inizializzato con un puntatore a funzione, parseoptions() lo utilizza per invocare quella funzione per ogni oggetto non­option incontrato sulla command line dopo l'ultima opzione. E' proprio questo il caso dell'esempio: se ipotizziamo che sulla riga di comando l'ultima opzione sia seguita da tre non­option items, valid_glob() è chiamata tre volte. 

Inoltre, se il campo opt di uno degli elementi di valfuncs contiene il valore definito dalla costante manifesta ERRCHAR, la funzione indirizzata dal campo fun corrispondente, nell'esempio valid_err(), è chiamata ogni volta che sulla command line è incontrata una optLetter o argLetter non valida (non inclusa nella optionS). 

L'array valfuncs ha pertanto numero di elementi pari alle optLetter e argLetter in optionS, più, opzionalmente, un elemento per la gestione delle opzioni errate, più un elemento finale con campo opt inizializzato a NULL (e campo fun nullo o puntatore alla funzione di validazione dei non­options items). 

La restituisce NULL in caso di errore di sistema[9], nel qual caso può essere utilizzata perror() per visualizzare la descrizione dell'errore. Se, al contrario, l'elaborazione termina con successo viene restituito un puntatore ad array di strutture OPT, il cui template è definito in PARSEOPT.H
struct OPT {
    char opt;
    char *arg;
    int val;
};
Vediamo il significato di ogni elemento dell'array (nell'esempio optArray) e, per ogni elemento, il significato dei singoli campi. La prima struttura OPT presente in optArray (optArray[0]) contiene informazioni sugli elementi successivi. In particolare, il campo opt contiene l'indice[10], nell'array stesso, dell'elemento relativo al primo non­option item presente sulla command line; in altre parole optArray[optArray[0].opt] è la struttura OPT che, nell'array, si riferisce al primo non­option item. Il campo arg contiene sempre argv[0], cioè il puntatore alla stringa che rappresenta il path completo del programma. Il campo val contiene il numero dei non­option items presenti sulla riga di comando; se non ve ne sono, entrambi i campi opt e val sono inizializzati a 0

Nell'array optArray troviamo poi un elemento per ogni optLetter e argLetter incontrata sulla command line. Nel caso di una optLetter, il campo opt contiene l'optLetter stessa, il campo arg è NULL e il campo val contiene l'intero restituito dalla funzione di validazione (campo fun della struct VOPT). Nel caso di una argLetter, invece, il campo arg punta ad una stringa contenente l'argomento fornito alla argLetter stessa sulla command line. Si noti che arg è sempre puntatore a stringa, anche nel caso in cui l'argomento della optLetter sia un numero: in tal caso occorre utilizzare l'appropriata funzione di conversione (ad esempio atoi()) per ottenere il valore numerico. 

Se la optLetter o argLetter incontrata non si trova in errorS, il campo arg contiene il puntatore alla stringa ERROR_S definita in PARSEOPT.H. Nella riga di comando, i due punti (":") isolati sono interpretati come opzione illecita ed il campo arg contiene la stringa ILLEGAL_S (anch'essa definita in PARSEOPT.H). In entrambi i casi appena descritti, il campo val assume valore ­1

Si noti ancora che assume che lo switch character utilizzato sia quello di default per il sistema operativo: in DOS esso è la barra ("/"); se si preferisce utilizzare un altro carattere per introdurre le opzioni, ad esempio il trattino ("­") secondo la convenzione Unix, occorre modificare il default mediante una chiamata a setswitch()

Ipotizziamo ora che il listato d'esempio faccia parte di un programma chiamato PROG ed invocato con la seguente riga di comando
PROG /a /c14 /b pippo "Qui, Quo, Qua" pluto
Al ritorno da parseoptions() optArray contiene 6 elementi, valorizzati come segue: 
optArray[0].opt 4  4 è l'indice dell'elemento di optArray relativo al primo non­option item, se ve ne sono; altrimenti esso esprime il numero di elementi di cui si compone optArray
optArray[0].arg pathname di PROG  E' argv[0].
optArray[0].val 3  Indica che vi sono 3 non­option item. Essi sono referenziati da optArray[4], optArray[5] e optArray[6]; si osservi inoltre che 6, ricavabile con (optArray[0].opt + optArray[0].val ­ 1), è l'indice dell'ultimo elemento dell'array. Infine, argv[argc-optArray[0].val] è il primo non­option item in argv
optArray[1].opt 'a'  Prima opzione sulla command line.
optArray[1].arg NULL  'a' è una optLetter: non ha argomento. 
optArray[1].val valore restituito da valid_a()  Se 'a' non fosse un'opzione valida, o fosse specificata in modo errato, il campo conterrebbe il valore della costante manifesta ERRCHAR (­1). 
optArray[2].opt 'c'  Seconda opzione sulla command line.
optArray[2].arg "14"  'c' è una argLetter: "14" è il suo argomento, sempre sotto forma di stringa. 
optArray[2].val valore restituito da valid_c()  Se 'c' non fosse un'opzione valida, o fosse specificata in modo errato, il campo conterrebbe il valore della costante manifesta ERRCHAR (­1). 
optArray[3].opt 'b'  Terza opzione sulla command line.
optArray[3].arg NULL  'b' è una optLetter: non ha argomento. 
optArray[3].val valore restituito da valid_b()  Se 'b' non fosse un'opzione valida, o fosse specificata in modo errato, il campo conterrebbe il valore della costante manifesta ERRCHAR (­1). 
optArray[4].opt 0  Primo non­option item sulla command line. 
optArray[4].arg "pippo"  Il non­option item stesso, come stringa. 
optArray[4].val valore restituito da valid_glob()  Sempre 0.
optArray[5].opt 1  Secondo non­option item sulla command line. 
optArray[5].arg "Qui, Quo, Qua"  Il non­option item stesso, come stringa. 
optArray[5].val valore restituito da valid_glob() 
optArray[4].opt 2  Primo non­option item sulla command line. 
optArray[4].arg "pluto"  Il non­option item stesso, come stringa. 
optArray[4].val valore restituito da valid_glob() 
E' il momento di descrivere le funzioni di validazione, quelle, cioè, i cui puntatori sono contenuti nei campi fun degli elementi dell'array valfuncs. Va precisato subito che il codice di dette funzioni dipende dalle esigenze del programma. Generalmente, ad ogni optLetter e argLetter corrisponde una appropriata funzione di validazione, ma nulla vieta di utilizzare una medesima funzione per controllare più opzioni: è sufficiente che i campi fun a queste corrispondenti siano tutti inizializzati con lo stesso puntatore. Analoghe considerazioni valgono a proposito della funzione richiamata in caso di opzione errata, e di quella richiamata una volta per ogni non­option item. 

Ad esempio, la funzione valid_err() potrebbe visualizzare un messaggio di errore e interrompere il programma, tramite la funzione di libreria exit()

Tutte queste funzioni, comunque, devono restituire un intero al fine di valorizzare secondo le esigenze del programmatore i campi val degli elementi dell'array di strutture OPT; inoltre tutte ricevono in ingresso, come parametri, il puntatore ad una struct OPT (la quale altro non è che l'elemento dell'array optArray corrispondente a quell'opzione) in cui i campi opt e arg sono già valorizzati come mostrato nella tabella sopra esposta, mentre il campo val contiene 0. Può risultare utile, nelle funzioni di validazione delle argLetter, utilizzare il campo arg per effettuare i necessari controlli di validità dell'argomento associato all'argLetter stessa. L'intero che le funzioni di validazione ricevono come secondo parametro esprime la posizione dell'argLetter (o optLetter) sulla command line, e può essere utilizzato per le opportune verifiche qualora la posizione dell'opzione sia importante. 

Nei programmi si ha spesso la necessità di modificare il comportamento dell'algoritmo a seconda che un'opzione sia stata o meno specificata: in questi casi può essere utile un flag, inizializzato dalla funzione di validazione e controllato laddove occorra nel corso dell'elaborazione. Un metodo efficiente di implementare tale tecnica di gestione delle opzioni è rappresentato da una variabile globale in cui ogni bit è associato ad una opzione. La funzione di validazione pone a 1 il bit; questo è poi verificato da altre funzioni del programma, che possono accedere a quella variabile, proprio in quanto globale. 

Tornando al nostro esempio, le funzioni di validazione potrebbero agire su un intero senza segno: 
unsigned int optBits;

int valid_a(struct OPT *tmp,int no)
{
    ....
    return(optBits |= 1);
}

int valid_b(struct OPT *tmp,int no)
{
    ....
    return(optBits |= 2);
}

int valid_c(struct OPT *tmp,int no)
{
    register int test;

    test = atoi(tmp->arg);
    if(test < 0 || test >= 10)
        valid_err(tmp,no);
    return(optBits |= 4);
}

int valid_glob(struct OPT *tmp,int no)
{
    static int progressiveLen;

    return(progressiveLen += strlen(tmp->arg);
}

int valid_err(struct OPT *tmp,int no)
{
    fprintf(stderr,"Errore: opzione '%c' non valida.\n",tmp->opt);
    exit(-1);
}
I bit della variabile globale optBits sono associati alle singole opzioni: in particolare, il bit 0 corrisponde all'optLetter 'a', il bit 1 all'optLetter 'b' e il bit 2 all'argLetter 'c'; si noti che le costanti utilizzate per valorizzarli sono potenze di 2 (in particolare, 2 elevato ad esponente pari al numero del bit). In tal modo è possibile con un'operazione di OR su bit (composta con l'assegnamento) modificare il singolo bit desiderato. In qualunque altro punto del programma può utilizzare un test analogo al seguente: 
if(optBits & 2)....
per verificare se l'opzione 'b' è stata specificata. L'uso di costanti manifeste come 
#define  OPTION_A     1
#define  OPTION_B     2
#define  OPTION_C     4
facilita notevolmente la vita. 

Si noti che in luogo di una variabile integral si può utilizzare un campo di bit, con il vantaggio, tra l'altro, di non essere costretti ad utilizzare le operazioni di AND e OR su bit, in quanto ogni campo rappresenta di per sé un'opzione 
struct OPTBITS optBits {
    unsigned optionA:1;
    unsigned optionB:1;
    unsigned optionC:1;
}

    ....
    optBits.optionA = 1;
    ....
    if(optBits.optionA)
    ....
Si noti che valid_c(), in caso di errore, chiama valid_err() per interrompere il programma. Inoltre, la scelta dei nomi delle funzioni di validazione è, ovviamente, libera: quelli utilizzati nell'esempio non sono vincolanti, né rappresentano un default. 

Va ancora osservato che parseoptions() è perfettamente compatibile con WILDARGS.OBJ: se come non­option item sono specificati uno o più nomi di file, essi verranno trattati nel modo consueto: l'argv che parseoptions() riceve come secondo parametro contiene già i puntatori ai nomi generati dall'espansione delle wildcard "*" e "?". 

Ed ecco, finalmente (?), il sorgente completo delle funzioni, listate in ordine alfabetico, che realizzano il meccanismo sin qui descritto. Per un esempio pratico di utilizzo vedere la utility DRVSET

Sì! Sì! Voglio studiarmi il listato! 

La funzione storeopt() è il cuore del meccanismo. Essa è progettata[11] per scandire una stringa alla ricerca di optLetter e argLetter e, per queste ultime, isolare l'argomento fornito. La storeopt() analizza una sola stringa ad ogni chiamata, perciò deve essere utilizzata all'interno di un loop che provveda a gestire opportunamente i puntatori contenuti in argv. A ciò provvede parseopt(), che, al ritorno da storeopt() si occupa di lanciare la funzione di validazione dell'opzione mediante l'indirezione del puntatore contenuto nel campo fun della struttura di template VOPT
(*(valfuncs+i)->fun)(tmp,carg)
I parametri tmp e carg, coerentemente con il prototipo delle funzioni di validazione, sono il puntatore alla struct OPT e l'intero rappresentante la posizione dell'opzione sulla command line. 

Se restituisce una condizione di errore (tramite gopError()), parseopt() ricerca nell'array valfuncs un elemento il cui campo opt sia inizializzato con il valore della costante manifesta ERRCHAR e, se questo esiste, lancia la funzione di validazione corrispondente (valid_err() nell'esempio di poco fa). 

Quando segnala, tramite la restituzione di un valore negativo, che non vi sono più optLetter e argLetter, parseopt() considera i restanti elementi di argv come non­option items: se il campo fun dell'ultimo elemento dell'array valfuncs non è NULL viene lanciata la funzione da esso indirizzata una volta per ogni non­option item (la funzione è sempre la stessa, ma cambiano i valori dei campi della struct OPT di cui essa riceve il puntatore. 

Ad ogni iterazione parseopt() alloca la memoria necessaria per aggiungere all'array di strutture OPT quella corrispondente all'item della command line attualmente processato; al termine dell'elaborazione essa restituisce l'indirizzo dell'array oppure NULL in caso di errore (fallita allocazione della memoria). 

Oltre ai parametri richiesti dalla parseoptions(), la parseopt() necessita dello switch character (il carattere che introduce le optLetter e argLetter) e delle due stringhe da utilizzare come campi arg per le opzioni errate e illecite. Risulta evidente, a questo punto, che parseoptions() è semplicemente un "guscio" di alto livello per parseopt(), alla quale passa, oltre ai parametri ricevuti, anche quelli appena elencati, fornendone valori di default. In particolare, per le due stringhe sono utilizzate le costanti manifeste ERROR_S e ILLEGAL_S definite in PARSEOPT.H, mentre per lo switch character è utilizzato il valore restituito dalla getswitch(), che richiede al DOS il carattere di default. 

Al riguardo, sono necessarie alcune precisazioni. Come si è detto, lo switch character di default è la barra in DOS e il trattino in Unix: ciò implica che se si desidera realizzare in ambiente DOS un'interfaccia il più possibile Unix­like occorre dimenticarsi di parseoptions() e chiamare direttamente parseopt(), avendo cura di fornirle come parametro lo switch character desiderato. In alternativa è possibile modificare l'impostazione di default del DOS mediante la setswitch(), prima di chiamare parseoptions()

La setswitch() richiede come parametro il carattere che si desidera impostare come nuovo default e restuisce il precedente default (­1 in caso di errore). La getswitch() non richiede parametri e restituisce il default attuale. Le due funzioni si basano sull'int 21h, servizio 37h, subfunzioni 00h (GetSwitchChar) e 01h (SetSwitchChar); va sottolineato che detto servizio non è ufficialmente documentato e, pertanto, potrebbe non essere disponibile in tutte le versioni di DOS: in particolare, a partire dal DOS 5.0, la subfunzione 01h è ignorata (non determina la restituzione di un errore, ma non ha comunque alcun effetto) ed è perciò necessario utilizzare direttamente parseopt() se si desidera utilizzare il trattino come switch character

INT 21H, SERV. 37H, SUBF. 00H: GET SWITCH CHARACTER 
Input AH 37h 
AL 00h 
Output AL FFh in caso di errore (funzione non supportata). 
DL Attuale switch character, se AL non è FFh
Note Se non vi è errore, le versioni di DOS fino alla 4.x restituiscono 00h in AL; dalla 5.0 in poi AL = 2Fh
INT 21H, SERV. 37H, SUBF. 01H: SET SWITCH CHARACTER 
Input AH 37h 
AL 01h 
DL Nuovo switch character 
Output AL 00h se OK, FFh in caso di errore (funzione non supportata). 
Note Questa chiamata è ignorata dal DOS a partire dalla versione 5.0. 
Va infine osservato che il listato potrebbe essere suddiviso in più sorgenti, uno per ogni funzione (con la sola eccezione di gopError() e storeopt(), da riunire in un unico file[12]); dalla compilazione si otterrebbero così più file .OBJ, da inserire in una libreria. Se ne avvantaggerebbero gli eseguibili incorporanti le funzionalità descritte, dal momento che in essi bverrebbero inclusi dal linker solo i moduli necessari.

OK, andiamo avanti a leggere il libro... 

Non ci ho capito niente! Ricominciamo...