Traduzione di “Introduction to argtable-2.x”

Una libreria ANSI C per il parsing in stile GNU degli argomenti della linea di comando.

Stewart Heitmann

Fare il parsing degli argomenti di un programma a linea di comando è sempre stato una distrazione dall’obiettivo principale. La libreria argtable semplifica il lavoro permettendo al programmatore di definire le opzioni della linea di comando direttamente nel codice sorgente come un array statico di strutture che viene passato alle funzioni della libreria di argtable la quale provvederà al parsing. I valori estrapolati dalla linea di comando sono depositati in variabili definite dal programmatore dove sono accessibili dal programma principale. Argtable può anche generare descrizioni della sintassi della linea di comando dallo stesso array per una visualizzazione tipo help on-line. Questo software è liberamente disponibile sotto i termini della GNU Library General Public License (LGPL).

myprog -abc –scalar=7 –verbose -o myfile []

Argtable usa GNU getopt per realizzare l’attuale parsing matenendo così il 100% di compatibilità GNU. Supporta sia le opzioni corte GNU(come -abc, -o myfile) sia le opzioni lunghe GNU(come –scalar=7, –verbose), e gli argomenti senza tag (come []). Non sono supportati comandi di linea in formati non-GNU, tipo il DOS con le opzioni /X /Y /Z.

Come lavora

Argtable sviluppa un set di stutture arg_xxx, una per ogni tipo di argomento (letterale, intero, decimale, stringa, nome di file, ecc…) ed ogni struttura può lavorare con occorrenze multiple di argomenti nella linea di comando. Inoltre, ogni opzione può essere digitata in forma breve (es.: -c) oppure in formato lungo (es.: –scalr) in modo completamente intercambiabile. Infatti, ogni opzione prevede alternative multiple, brevi o lunghe opzioni, o entrambe. Le opzioni possono essere definite per non avere tag (es.: ) in questo caso sono identificabili secondo la loro posizione nella riga di comando(le opzioni con tag invece possono comparire ovunque).
Per definire le opzioni della linea di comando di un programma l’utente crea una struttura arg_xxx per ogni tipo di argomento richiesto e li colleziona in un array che chiameremo tabella degli argomenti. L’ordine di queste strutture all’interno della tabella degli argomenti definisce l’ordine in cui vengono attese le opzioni dalla linea di comando, sebbene il parsing ordinato avviene solo per le opzioni senza tag. La stessa tabella degli argomenti è solamente un array di puntatori void, e convenzionalmente ogni struttura arg_xxx contiene una struttura conosciuta arg_hdr che essendo il primo valore viene usato dalle funzioni di argtable per identificare la struttura.
A proposito di esempi, prendiamo in considerazione la struttura arg_int che è usata per le opzioni integer della linea di comando come in –scalar=7.

struct arg_int
{
struct arg_hdr hdr;
int count;
int *ival;
};

Il primo elemento della struttura, hrd, contiene dati “privati” usati dalle funzioni della libreria argtable. Esso contiene cose come i tag degli argomenti stringa e così via. L’accesso a questo dato è apertamente permesso, non è strettamente riservato, ma in generale il programmatore non ha necessità di accedervi e quindi può ignorarlo. Gli elementi variabili count ed ival sono più interessanti, ival punta ad un array di interi che mantiene i valori dalla riga di comando e count restituisce il numero dei valori contenuti nell’array. La memorizzazione dell’array ival avviene quando arg_int è costruito. Questo deve essere fatto con con una delle funzioni costruttrici arg_int dedicate:

struct arg_int* arg_int0(const char* shortopts,
const char* longopts,
const char* datatype,
const char* glossary);

struct arg_int* arg_int1(const char* shortopts,
const char* longopts,
const char* datatype,
const char *glossary);

struct arg_int* arg_intn(const char* shortopts,
const char* longopts,
const char *datatype,
int mincount,
int maxcount,
const char *glossary);

Tutte le funzioni costruttrici lavorano nello stesso modo; esse allocano un blocco di memoria che contiene una struttura arg_xxx in testa seguita dai dati locali della struttura, in questo caso i contenuti dell’array ival. Per questa ragione, non si dovrebbe mai instanziare una struttura arg_xxx manualmente. Usare sempre le funzioni costruttrici che provvedono ad allocare e liberare la struttura usando free quando si finisce.

Le tre forme costruttici di arg_int rappresentano i modi più comuni di utilizzo degli argomenti di linea di comando: arg_int0 è per le opzioni che appaiono zero oppure una volta, arg_int1 è per le opzioni che appaiono esattamente una volta e arg_intn è per le opzioni che appaiono un certo numero di volte (definito da un range). Sia arg_int0 che arg_int1 allocheranno un elemento nell’array ival della risultante struttura, allo stesso modo arg_intn allocherà uno spazio sufficiente per memorizzare fino a maxcount elementi. L’ultima forma è solo più specializzata delle precedenti ed è sviluppata solo per convenienza. Notare che arg_xxx0, arg_xxx1 e arg_xxxn sono nomi convenzionali applicati nello stesso modo a tutte le funzioni costruttrici di argtable.

Continuando con il nostro esempio di arg_int, il codice seguente costruirà una opzione di tipo integer dalla forma –scalar= che deve comparire nella linea di comando dalle tre alle cinque volte inclusa.
struct arg_int *s;
s = arg_intn(NULL,”scalar”,””,3,5,“foo value”); Una volta elaborata s punterà ad un blocco di memoria contenente la struttura arg_int seguita dall’array ival di 5 elementi, oppure il valore NULL se l’allocazione di memoria fallisce. Presumendo che l’allocazione avviene con successo, la sua mappa di memoria potrebbe essere come:

struct arg_int *s = 0xbffff600;
0xbffff600: struct arg_int
0xbffff600: {
0xbffff600: struct arg_hdr hdr;
0xbffff87c: int count = 0;
0xbffff880: int *ival = 0xbffff888;
0xbffff888: };
0xbffff888: ival[0]
0xbffff88c: ival[1]
0xbffff890: ival[2]
0xbffff894: ival[3]
0xbffff898: ival[4]

Si nota che la variabile s->count è inizializzata a zero e rappresenta il numero di valori validi memorizzati nell’array s->ival dopo l’analisi della linea di comando. La grandezza dell’array s->ival è restituito da s->hdr.maxcount.

In questo esempio abbiamo omesso la forma di opzione corta passando un parametro NULL per la costruzione della struttura. Se invece passiamo una opzione corta come “k”
s = arg_intn(“k”,”scalar”,””, 3,5,“foo value”); la struttura risultante potrebbe essere la stessa ma l’opzione può essere accettata sia come -k che in forma lunga –scalar=. Effettivamente, possiamo andare oltre e definire alternative multiple per entrambe le forme corta e lunga. L’alternative per l’opzione corta sono rappresentate da una stringa di singoli caratteri, altrettanto l’opzione lunga viene rappresentata da stringhe separate da virgola. Per esempio,
s = arg_intn(“kKx”,”scalar,foo”, ””,3,5,“foo value”); accetterà le seguenti forme alternative nella linea di comando:
-k -K -x –scalar= –foo=
A parte arg_int, un’altra struttura arg_xxx di interesse sono:

struct arg_lit
{
struct arg_hdr hdr;
int count;
};
Per le opzioni letterali (senza argomenti). Esempio -v, –verbose
struct arg_dbl
{
struct arg_hdr hdr;
int count;
double *dval;
}; Per le opzioni che ricevono numeri reali. Esempio -x1.0e-6, –pi=3.1415
struct arg_str
{
struct arg_hdr hdr;
int count;
const char **sval;
};

Per le opzioni che ricevono stringhe. Esempio -Dmacro, –title=”hello world”

struct arg_file
{
struct arg_hdr hdr;
int count;
const char **filename;
const char **basename;
const char **extension;
};

Per le opzioni che ricevono nomi di file. Questa opzione non solo ritorna il nome del file stesso, ma distingue il nome base e l’estensione. Esempio -o myfile, –infile=memo.txt

La tabella degli argomenti

Costruite la nostre strutture arg_xxx le collazioneremo in una tabella degli argomenti, come nel seguente esempio che definisce gli argomenti della riga di comando :
[-a] [-b] [-c] [--scalar=] [-v|--verbose] [-o myfile] []

struct arg_lit *a = arg_lit0(“a”, NULL, ”the -a option”);
struct arg_lit *b = arg_lit0(“b”, NULL, ”the -b option”);
struct arg_lit *c = arg_lit0(“c”, NULL, ”the -c option”);
struct arg_int *scal = arg_int0(NULL, ”scalar”,””, ”foo value”);
struct arg_lit *verb = arg_lit0(“v”, ”verbose, ”verbose output”);
struct arg_file *o = arg_file0(“o”, NULL,”myfile”, ”output file”);
struct arg_file *file = arg_filen(NULL,NULL,””,1,2, ”input files”);
struct arg_end *end = arg_end(20);
void *argtable[] = {a,b,c,scal,verb,o,file,end};

L’opzioni “-a”, “-b”, “-c”, e “-v|–verbose” non ricevono valori in argomento e così per loro possiamo usare la struttura arg_lit. Usiamo la forma arg_lit0 della funzione costruttrice perchè queste particolari opzioni appaiono nella riga di comando solamente una volta oppure nessuna.
L’opzione “–scalar=” riceve un intero per argomento e quindi usa una struttura arg_int. Apparendo una volta o nessuna usiamo la funzione costruttrice arg_int0.
Le opzioni “-o myfile” e “” si riferiscono entrambe a nomi di file e quindi useremo una struttura arg_file. Usiamo la funzione costruttrice arg_file0 per la prima opzione perchè appare una volta o nessuna, invece la seconda opzione deve apparire una o due volte così usiamo la più generale funzione costruttrice arg_filen. Notare che quest’ultima è una opzione senza tag e quindi non prevede le opzioni corta o lunga.

La struttura arg_end è speciale e non rappresenta delle opznioni della linea di comando. Principalmente esso marca la fine dell’array argtable e memorizza anche gli errori di analisi incontrati durante l’elabolrazione della riga di comando. Il parametro integer passato al costruttore di arg_end corrisponde al massimo numero di errori che memorizzerà, in questo caso 20, gli ulteriori errori sono scaricati e sostituiti con un singolo messaggio di errore “too many errors”.
Vedremo presto come usare arg_end nel riporto degli errori ma prima dobbiamo assicurarci che le componenti della tabella argtable siano allocate correttamente dalle loro funzioni costruttrici. Se questo non avviene ci sarà un valore NULL nell’array di argtable che potrebbe causare degli errori. Possiamo utilizzare la funzione arg_nullcheck per verificare la presenza del valore NULL in argtable. La funzione ritorna un valore non-zero se dei valori NULL sono stati incontrati dall’inizio alla fine della tabella come era stata marcata dalla struttura arg_end.

if (arg_nullcheck(argtable) != 0)
printf(“error: insufficientmemoryn”);

Presumendo che tutto è andato bene, possiamo procedere all’analisi degli argomenti della linea di comando con la nostra tabella degli argomenti. Noi possiamo scrivere direttamente i nostri valori desiderati nella struttura di arg_xxx sapendo che argtable li sovrascriverà solo se i valori nella riga di comando sono ricevuti al posto giusto. Settiamo ora il valore di default con 3 e “-” per la ripetizione e rispettivamente per gli argomenti di outfile.

repeat->ival[0]=3;
outfile->filename[0]=”-“;””

Siamo consapevoli che argtable non richiede l’inizializzazione di ogni valore di default, è semplicemente più conveniente per i nostri programmi se noi pre-carichiamo i valori di default piuttosto che ricontrollare successivamente i valori mancanti. Comunque, tu puoi preferire anche la seconda soluzione.

Processare gli errori

Se la funzione arg_parse riscontra degli errori abbiamo la necessità di visualizzarli e sappiamo che arg_parse non lo fa. Come abbiamo detto precedentemente, la funzione arg_parse memorizza gli errori incontrati nella struttura arg_end della tabella degli argomenti. Per stampare questi errori nell’ordine in cui sono stati incontrati non abbiamo bisogno di conoscere i dettagli della struttura arg_end ma chiameremo semplicemente la funzione arg_print_errors.
void arg_print_errors(FILE* fp, struct arg_end* end, const char* progname); Passiamo alla funzione un puntatore alla struttura arg_end della tabella degli argomenti come pure il nome del programma che è preposto ad ogni messaggio di errore. Il nome del programma può essere NULL se non richiesto.
If (nerrors > 0)
arg_print_errors(stdout,end,”myprog”); Questo esempio illustra il risultato dell’invocazione del nostro programma con opzioni errate nella linea di comando:
$./myprog -x -y -z –scalar=hello –verby
myprog: invalidoption “-x”
myprog: invalid option “-y”
myprog:invalid option “-z”
myprog: invalid argument “hello”to option –scalar=
myprog: invalid option”–verby”
myprog: missing option La ragione per cui la funzione arg_parse non stampa messaggi di errore è per il fatto di poter essere chiamata molte volte per l’analisi della linea di comando con argomenti diversi senza visualizzare messaggi di errori prematuri. Così noi possiamo definire tabelle di argomenti separate per quei programmi che hanno opzioni incompatibili, cercando in ogni tabella di argomenti fino a trovare un candidato idoneo. Se la ricerca fallisce in tutte le tabelle degli argomenti possiamo scegliere di stampare tutti i messaggi di errore, oppure solo quelli dalla tabella più vicina. In questo caso, noi controlleremo quali messaggi visualizzare.

Visualizzare la sintassi dell’opzione

Se vuoi far visualizzare un help on-line dal tuo programma puoi usare la funzione arg_print_syntax che attingendo da una tabella degli argomenti permette di vedere la corretta sintassi della linea di comando. Attualmente ci sono due forme della funzione:
void arg_print_syntax(FILE *fp, void **argtable, const char *suffix);
void arg_print_syntaxv(FILE *fp, void **argtable, const char *suffix); L’ultima forma visualizza un output di maggior dettaglio, ed è distinta dalla “v” alla fine del nome della funzione. Entrambe le funzioni visualizzano la sintassi dell’intera tabella degli argomenti, con il parametro suffix si ha un controllo per inserire caratteri di newline in stringhe che raggiungono la fine dell’output. Nella forma prolissa, ogni elemento della tabella degli argomenti visualizza le sue opzioni corta e lunga separate dal carattere “|” seguito dal tipo di dato stringa. Per esempio,
arg_int0(“kKx”,”scalar,foo”,””, “foo value”); mostrerà la forma verbosa come “[-k|-K|-x|--scalar|--foo=]”. Laddove la forma standard abbrevia l’output visualizzando solamente la prima opzione per ogni elemento della tabella degli argomenti, come in “[-k]”. La forma standard concatena tuute le opzioni corte della tabella degli argomenti in una singola stringa opzione all’inizio della visualizzazione in stile GNU standard (es: -a -b -c sono visualizzate come -abc). La tabella degli argomenti del nostro precedente esempio potrebbe essere mostrata nella forma standard in questo modo:
[-abcv] [--scalar=] [-o myfile] [] e in forma prolissa :
[-a] [-b] [-c] [--scalar=] [-o myfile] [-v|--verbose] [] Notare che le opzioni facoltative sono racchiuse automaticamente in parentesi quadre mentre quelle obbligatorie no. Inoltre gli argomenti che accettano istanze multiple sono visualizzati unoper ogni caso, come in “ []”. Questo avviene per un numero massimo di 3 istanze dopo le quali la ripetizione è sostituita da puntini di sospensione come in “[]…”.

Le funzioni arg_print_syntax per sicurezza ignorano il NULL dell’opzioni stringa corta e lunga, laddove un tipo di dati stringa NULL è automaticamente sostituito dal tipo di dati default per la struttura arg_xxx. Il tipo di dati default può essere evitato usando un tipo di dato stringa vuota invece di un NULL.

Visualizzare il glossario dell’opzione

Gli elementi della tabella degli argomenti individualmente possono essere visualizzati in un layout di glossario dalla funzione arg_print_glossary. Questa funzione visualizza la sintassi completa per ogni elemento seguita dalla stringa di glossario – la stringa di glossario è l’ultimo parametro passato alla funzione costruttrice di arg_xxx. I valori nella tabella con la stringa di glossario NULL non sono visualizzate.
void arg_print_glossary(FILE *fp, void **argtable, const char *format); Il formato stringa passato alla funzione arg_print_glossary è attualmente un formato stringa stile printf. Esso dovrebbe contenere esattamente due parametri formato “%s”, il primo è usato per controllare il formato printf della stringa sintassi dell’opzione e il secondo è per la stringa di glossario. Un tipico formato della stringa potrebbe essere “%-25s %sn”. Il formato stringa permette un controllo fine sulla formattazione visualizzata e quindi richiede diligenza perchè un parametro inaspettato provocherà risultati imprevedibili. Ecco i risultati della chiamata arg_print_glossary riferiti al precedente esempio di tabella degli argomenti:

-a
-b
-c
–scalar=
-v,–verbose
-o myfile
the -a option
the -b option
the -c option
foo value
verbose option
outputfile
input files

Qualche volta potresti desiderare di aggiungere linee supplementari di testo per il glossario, oppure inseire del proprio testo nella stringa di sintassi generata da arg_print_syntax. Si possono aggiungere anche dei caratteri di nuova linea alle stringhe nella tabella degli argomenti ma non è una buona idea. Un modo migliore è aggiungere delle strutture arg_rem alla tua tabella degli argomenti. Sono degli elementi silenziosi nel senso che non alterano l’analisi deli argomenti ma il loro tipo di dato e le stringhe di glossario appaiono nell’output generato dalle funzioni arg_print_syntax e arg_print_glossary. Il nome arg_rem sta per “remark” ed è ispirato alla parola REM utilizzata nel linguaggio BASIC.

Pulizia

Alla fine del nostro programma abbiamo la necessità di liberare la memoria allocata da ogni nostra struttura arg_xxx. Possiamo farlo chiamando free per ognuna di loro, ma la funzione arg_freetable può farlo per noi in modo più conveniente.
arg_freetable(argtable,sizeof(argtable)/sizeof(argtable[0])); Questa funzione passerà attraverso la tabella degli argomenti e chiamerà free per ognuno dei suoi elementi per nostra comodità. Notare che il secondo parametro, sizeof(argtable)/sizeof(argtable[0]), rappresenta meramente il numero degli elementi nel nostro array argtable. Una volta completata questa funzione, tutti i componenti dell’array saranno settati NULL.

Suggerimento: dichiarare arg_xxx come variabile globale

Il C ANSI di solito permette alle funzioni costruttrici arg_xxx di posizionarsi nello spazio di dichiarazione globale, così se desideri rendere le tue strutture arg_xxx globali puoi inizializzarle ovunque. Ecco un’espediente di programmazione per usare le strutture arg_xxx globali mentre fissi la tua dichiarazione di argtable staticamente.
#include

/* dichiarazione globale di strutture arg_xxx */
struct arg_lit *a, *b, *c,*verb;
struct arg_int *scal;
struct arg_file *o, *file;
structarg_end *end;

int main(int argc, char **argv)
{
/* inizializzazione delle strutture arg_xxx all’interno di argtable */
void *argtable[] =
{
a= arg_lit0(“a”, NULL, ”the -a option”),
b= arg_lit0(“b”, NULL, ”the -b option”),
c= arg_lit0(“c”, NULL, ”the -c option”),
scal = arg_int0(NULL, ”scalar”,””, ”foovalue”),
verb = arg_lit0(“v”, ”verbose,”verbose output”),
o = arg_file0(“o”,NULL,”myfile”, ”output file”),
file = arg_filen(NULL,NULL,””,1,2, ”inputfiles”),
end = arg_end(20),
};

return0;
};
Vedere il programma ls.c incluso nella distribuzione di argtable (scaricabile dal menù di questo articolo) per un esempio di questo stile di dichiarazione.

Compilare programmi con la libreria argtable

Tutto il codice sorgente che usa la libreria argtable deve includere il file header “argtable2.h” e deve linkare la libreria argtable2. Un tipico makefile unix potrebbe essere:
CC = gcc
CFLAGS = -I/usr/local/include -Wall -ansi
LDFLAGS =-L/usr/local/lib
LDLIBS = -largtable2
myprog.o: myprog.c
$(CC) -c $(CFLAGS) -o myprog.o myprog.c
myprog.o: myprog.c
$(CC) $(LDFLAGS) myprog.o -o myprog $(LDLIBS)
Gli ultimi due make generalmente sono ridondanti come il default make è generalmente sufficiente, così possiamo semplificare l’intero Makefile:
CC = gcc
CFLAGS = -I/usr/local/include -Wall -ansi
LDFLAGS =-L/usr/local/lib
LDLIBS = -largtable2
myprog.o: myprog.c
Infine, puoi addizionare l’opzione “-static” nel CFLAGS se preferisci linkare la tua applicazione staticamente piuttosto che usare librerie caricate dinamicamente.
CFLAGS = -I/usr/local/include -Wall -ansi -static

Codice esempio

La distribuzione argtable è corredata da programmi esempio che implementano una completa e compatibile linea di comando GNU con opzioni per molti dei più comuni comandi unix. Vedere nella cartella argtable-2.X/example/ per il codice sorgente del seguente programma:
echo [-neE] [--help] [--version] [STRING]…

ls [-aAbBcCdDfFgGhHiklLmnNopqQrRsStuUvxX1] [--author] [--block-size=SIZE] [–color=[WHEN]] [--format=WORD] [--full-time] [--si] [--dereference-command-line-symlink-to-dir] [--indicator-style=WORD] [-I PATTERN] [--show-control-chars] [--quoting-style=WORD] [--sort=WORD] [--time=WORD] [--time-style=STYLE] [-T COLS] [-w COLS] [--help] [--version] [FILE]…

mv [-bfiuv] [–backup=[CONTROL]] [--reply={yes,no,query}] [--strip-trailing-slashes] [-S SUFFIX] [--target-directory=DIRECTORY] [--help] [--version] SOURCE [SOURCE]… DEST|DIRECTORY

rm [-dfirv] [--help] [--version] []…

uname [-asnrvmpio] [--help] [--version]

Traduzione a cura di Danilo CICERONE.
Revisione 0.1

Lascia un Commento

L'indirizzo email non verrà pubblicato. I campi obbligatori sono contrassegnati *