Successivo: Programma sed semplice, Precedente: Programma riordino diario, Su: Programmi vari [Contenuti][Indice]
Sia questo capitolo che il precedente
(Una libreria di funzioni awk
)
presentano un numero elevato di programmi awk
.
Se si vuole fare pratica con questi programmi, è fastidioso doverli
digitare di nuovo manualmente. È per questo che abbiamo pensato a un programma
in grado di estrarre parti di un file in input Texinfo e metterli in file
separati.
Questo Documento è scritto in Texinfo, il programma di formattazione di documenti del progetto GNU. Un solo file sorgente Texinfo può essere usato per produrre sia la documentazione stampata, usando TeX, sia quella online. (Texinfo è esaurientemente documentato nel libro Texinfo—The GNU Documentation Format, disponibile alla Free Software Foundation, e anche online.)
Per quel che ci riguarda, è sufficiente sapere tre cose riguardo ai file di input Texinfo:
awk
. I simboli ‘@’ sono rappresentati nel sorgente
Texinfo come ‘@@’.
Il programma seguente, extract.awk, legge un file sorgente Texinfo
e fa due cose, basandosi sui commenti speciali.
Dopo aver visto il commento ‘@c system …’,
esegue un comando, usando il testo del comando contenuto nella
riga di controllo e passandolo alla funzione system()
(vedi la sezione Funzioni di Input/Output).
Dopo aver trovato il commento ‘@c file nome_file’, ogni riga
successiva è spedita al file nome_file, fino a che si trova un
commento ‘@c endfile’.
Le regole in extract.awk sono soddisfatte sia quando incontrano
‘@c’ che quando incontrano ‘@comment’ e quindi la parte
‘omment’ è opzionale.
Le righe che contengono ‘@group’ e ‘@end group’ sono semplicemente
ignorate.
extract.awk usa la funzione di libreria join()
(vedi la sezione Trasformare un vettore in una sola stringa).
I programmi di esempio nel sorgente Texinfo online di GAWK: Programmare efficacemente in AWK
(gawktexi.in) sono stati tutti inseriti tra righe ‘file’ e righe
‘endfile’. La distribuzione di gawk
usa una copia di
extract.awk per estrarre i programmi di esempio e per installarne
molti in una particolare directory dove gawk
li può trovare.
Il file Texinfo ha un aspetto simile a questo:
… Questo programma ha una regola @code{BEGIN} che stampa un messaggio scherzoso: @example @c file esempi/messages.awk BEGIN @{ print "Non v'allarmate!" @} @c endfile @end example Stampa anche qualche avviso conclusivo: @example @c file esempi/messages.awk END @{ print "Evitate sempre gli archeologi annoiati!" @} @c endfile @end example …
Il programma extract.awk inizia con l’impostare IGNORECASE
a
uno, in modo che un miscuglio di lettere maiuscole e minuscole nelle direttive
non faccia differenza.
La prima regola gestisce le chiamate a system()
, controllando che sia
stato fornito un comando (NF
dev’essere almeno tre) e controllando
anche che il comando termini con un codice di ritorno uguale a zero, che sta
a significare che tutto è andato bene:
# extract.awk --- estrae file ed esegue programmi dal file Texinfo BEGIN { IGNORECASE = 1 } /^@c(omment)?[ \t]+system/ { if (NF < 3) { e = ("extract: " FILENAME ":" FNR) e = (e ": riga `system' con formato errato") print e > "/dev/stderr" next } $1 = "" $2 = "" stat = system($0) if (stat != 0) { e = ("extract: " FILENAME ":" FNR) e = (e ": attenzione: system ha restituito " stat) print e > "/dev/stderr" } }
La variabile e
è stata usata per far sì che la regola
sia agevolemente contenuta nella videata.
La seconda regola gestisce il trasferimento di dati in un file. Verifica che nella direttiva sia stato fornito un nome-file. Se il nome del file non è quello del file corrente, il file corrente viene chiuso. Mantenere aperto il file corrente finché non si trova un nuovo nome file permette di usare la ridirezione ‘>’ per stampare i contenuti nel file, semplificando la gestione dei file aperti.
Il ciclo for
esegue il lavoro. Legge le righe usando getline
(vedi la sezione Richiedere input usando getline
).
Se si raggiunge una fine-file inattesa, viene chiamata la funzione
fine_file_inattesa()
. Se la riga è una riga “endfile”,
il ciclo viene abbandonato.
Se la riga inizia con ‘@group’ o ‘@end group’, la riga viene
ignorata, e si passa a quella seguente. Allo stesso modo, eventuali commenti
all’interno degli esempi vengono ignorati.
Il grosso del lavoro è nelle poche righe che seguono. Se la riga non ha
simboli ‘@’, il programma la può
stampare così com’è. Altrimenti, ogni ‘@’ a inizio parola dev’essere
eliminato.
Per rimuovere i simboli ‘@’, la riga viene divisa nei singoli elementi
del vettore a
, usando la funzione split()
(vedi la sezione Funzioni di manipolazione di stringhe).
Il simbolo ‘@’ è usato come carattere di separazione.
Ogni elemento del vettore a
che risulti vuoto indica due caratteri
‘@’ contigui nella riga originale. Per ogni due elementi vuoti
(‘@@’ nel file originale), va inserito un solo simbolo ‘@’ nel
file in output.
Una volta terminato di esaminare il vettore, viene chiamata la funzione join()
specificando nella chiamata il valore di SUBSEP
(vedi la sezione Vettori multidimensionali),
per riunire nuovamente i pezzi in una riga sola.
La riga è poi stampata nel file di output:
/^@c(omment)?[ \t]+file/ { if (NF != 3) { e = ("extract: " FILENAME ":" FNR ": riga `file' con formato errato") print e > "/dev/stderr" next } if ($3 != file_corrente) { if (file_corrente != "") lista_file[file_corrente] = 1 # memorizza per chiudere dopo file_corrente = $3 } for (;;) { if ((getline riga) <= 0) fine_file_inattesa() if (riga ~ /^@c(omment)?[ \t]+endfile/) break else if (riga ~ /^@(end[ \t]+)?group/) continue else if (riga ~ /^@c(omment+)?[ \t]+/) continue if (index(riga, "@") == 0) { print riga > file_corrente continue } n = split(riga, a, "@") # if a[1] == "", vuol dire riga che inizia per @, # non salvare un @ for (i = 2; i <= n; i++) { if (a[i] == "") { # era un @@ a[i] = "@" if (a[i+1] == "") i++ } }
print join(a, 1, n, SUBSEP) > file_corrente } }
È importante notare l’uso della ridirezione ‘>’ .
L’output fatto usando ‘>’ apre il file solo la prima volta; il file resta
poi aperto, e ogni scrittura successiva è aggiunta in fondo al file.
(vedi la sezione Ridirigere l’output di print
e printf
).
Ciò rende agevole mischiare testo del programma e commenti esplicativi
(come è stato fatto qui) nello stesso file sorgente, senza nessun problema.
Il file viene chiuso solo quando viene trovato un nuovo nome di
file-dati oppure alla fine del file in input.
Quando si incontra un nuovo nome-file, invece di chiudere il file,
il programma memorizza il nome del file corrente in lista_file
.
Ciò rende possibile mischiare il codice per più di un file nel file
sorgente Texinfo in input. (Precedenti versioni di questo programma
chiudevano davvero il file. Ma, a causa della ridirezione
‘>’, un file le cui parti non erano tutte una di seguito all’altra
finiva per contenere errori.)
Una regola END
effettua la chiusura di tutti i file aperti, quando
l’elaborazione è stata completata:
END { close(file_corrente) # chiudi l'ultimo file for (f in lista_file) # chiudi tutti gli altri close(f) }
Per finire, la funzione fine_file_inattesa()
stampa un
appropriato messaggio di errore ed esce:
function fine_file_inattesa() { printf("extract: %s:%d: fine-file inattesa, o errore\n", FILENAME, FNR) > "/dev/stderr" exit 1 }
Successivo: Programma sed semplice, Precedente: Programma riordino diario, Su: Programmi vari [Contenuti][Indice]