Successivo: Programma anagram, Precedente: Programma sed semplice, Su: Programmi vari [Contenuti][Indice]
In
Come includere altri file nel proprio programma, abbiamo visto come gawk
preveda la
possibilità di includere file. Tuttavia, questa è un’estensione gawk
.
Questa sezione evidenzia l’utilità di rendere l’inclusione di
file disponibile per awk
standard, e mostra come farlo utilizzando
una combinazione di programmazione di shell e di awk
.
Usare funzioni di libreria in awk
può presentare molti vantaggi.
Incoraggia il riutilizzo di codice e la scrittura di funzioni di tipo
generale. I programmi sono più snelli e quindi più comprensibili.
Tuttavia, usare funzioni di libreria è facile solo in fase di scrittura di
programmi awk
; è invece complicato al momento di eseguirli,
rendendo necessario specificare molte opzioni -f. Se gawk
non è disponibile, non lo sono neppure la variabile d’ambiente AWKPATH
e
la possibilità di conservare funzioni awk
in una directory di
libreria (vedi la sezione Opzioni sulla riga di comando).
Sarebbe bello poter scrivere programmi nel modo seguente:
# funzioni di libreria @include getopt.awk @include join.awk … # programma principale BEGIN { while ((c = getopt(ARGC, ARGV, "a:b:cde")) != -1) … … }
Il programma seguente, igawk.sh, fornisce questo servizio.
Simula la ricerca da parte di gawk
della variabile d’ambiente
AWKPATH
e permette anche delle inclusioni nidificate (cioè,
un file che è stato incluso tramite
@include
può contenere ulteriori istruzioni @include
).
igawk
tenta di includere ogni file una volta sola, in modo che delle
inclusioni nidificate non contengano accidentalmente una funzione di libreria
più di una volta.
igawk
dovrebbe comportarsi esternamente proprio come gawk
.
Questo vuol dire che dovrebbe accettare sulla riga di comando tutti gli
argomenti di gawk
, compresa
la capacità di specificare più file
sorgenti tramite l’opzione -f
e la capacità di mescolare istruzioni da riga di comando e file di sorgenti di
libreria.
Il programma è scritto usando il linguaggio della Shell POSIX
(sh
).85
Il funzionamento è il seguente:
awk
, per quando il programma espanso sarà eseguito.
awk
, mette l’argomento
in una variabile di shell che verrà espansa. Ci sono due casi:
gawk
, ne risulta che il file viene
incluso nel programma al punto giusto.
awk
) sui contenuti della variabile
di shell per espandere le istruzioni
@include
. Il programma espanso è messo in una seconda variabile di
shell.
gawk
e tutti gli altri
argomenti originalmente forniti dall’utente sulla riga di comando (come p.es.
dei nomi di file-dati).
Questo programma usa variabili di shell in quantità: per immagazzinare
argomenti della riga di comando e
il testo del programma awk
che espanderà il programma dell’utente,
per il programma originale dell’utente e per il programma espanso. Questo
modo di procedere risolve potenziali
problemi che potrebbero presentarsi se si usassero invece dei file temporanei,
ma rende lo script un po’ più complicato.
La parte iniziale del programma attiva il tracciamento della shell se il primo argomento è ‘debug’.
La parte successiva esegue un ciclo che esamina ogni argomento della riga di comando. Ci sono parecchi casi da esaminare:
Quest’opzione termina gli argomenti per igawk
. Tutto quel che segue
dovrebbe essere passato al programma awk
dell’utente senza essere
preso in considerazione.
Questo indica che l’opzione successiva è propria di gawk
. Per
facilitare l’elaborazione degli argomenti, l’opzione -W è aggiunta
davanti agli argomenti rimanenti, e il
ciclo continua. (Questo è un trucco di programmazione della sh
.
Non è il caso di preoccuparsene se non si ha familiarità con il comando
sh
.)
Queste opzioni sono conservate e lasciate da gestire a gawk
.
Il nome-file è aggiunto alla variabile di shell programma
, insieme
a un’istruzione @include
.
Il programma di utilità expr
è usato per eliminare la parte
iniziale dell’argomento (p.es., ‘--file=’).
(La sintassi tipica di sh
richiederebbe di usare il comando
echo
e il programma di utilità sed
per far questo.
Sfortunatamente, alcune versioni di echo
valutano le sequenze
di protezione contenute nei loro argomenti, e questo potrebbe finire per
alterare il testo del programma.
L’uso di expr
evita questo problema.)
Il testo sorgente è aggiunto in fondo a programma
.
igawk
stampa il proprio numero di versione, esegue
‘gawk --version’ per ottenere l’informazione relativa alla versione di
gawk
, ed esce.
Se nessuno degli argomenti
-f, --file, -Wfile, --source,
o -Wsource è stato fornito, il primo argomento che non è un’opzione
dovrebbe essere il programma awk
. Se non ci sono argomenti rimasti
sulla riga di comando, igawk
stampa un messaggio di errore ed esce.
Altrimenti, il primo argomento è aggiunto in fondo a programma
.
In qualsiasi caso, dopo che gli argomenti sono stati elaborati,
la variabile di shell
programma
contiene il testo completo del programma originale
awk
.
Il programma è il seguente:
#! /bin/sh # igawk --- come gawk ma abilita l'uso di @include if [ "$1" = debug ] then set -x shift fi # Un ritorno a capo letterale, # per formattare correttamente il testo del programma n=' ' # Inizializza delle variabili alla stringa nulla programma= opts= while [ $# -ne 0 ] # ciclo sugli argomenti do case $1 in --) shift break ;; -W) shift # Il costrutto ${x?'messaggio qui'} stampa un # messaggio diagnostico se $x è la stringa nulla set -- -W"${@?'manca operando'}" continue ;; -[vF]) opts="$opts $1 '${2?'manca operando'}'" shift ;; -[vF]*) opts="$opts '$1'" ;; -f) programma="$programma$n@include ${2?'manca operando'}" shift ;; -f*) f=$(expr "$1" : '-f\(.*\)') programma="$programma$n@include $f" ;; -[W-]file=*) f=$(expr "$1" : '-.file=\(.*\)') programma="$programma$n@include $f" ;; -[W-]file) programma="$programma$n@include ${2?'manca operando'}" shift ;; -[W-]source=*) t=$(expr "$1" : '-.source=\(.*\)') programma="$programma$n$t" ;; -[W-]source) programma="$programma$n${2?'manca operando'}" shift ;; -[W-]version) echo igawk: version 3.0 1>&2 gawk --version exit 0 ;; -[W-]*) opts="$opts '$1'" ;; *) break ;; esac shift done if [ -z "$programma" ] then programma=${1?'manca programma'} shift fi # A questo punto, `programma' contiene il programma.
Il programma awk
che elabora le direttive @include
è immagazzinato nella variabile di shell progr_che_espande
. Ciò serve
a mantenere leggibile lo script. Questo programma awk
legge
tutto il programma dell’utente, una riga per volta, usando getline
(vedi la sezione Richiedere input usando getline
). I nomi-file in input e le istruzioni @include
sono gestiti usando una pila. Man mano che viene trovata una @include
,
il valore corrente di nome-file è
“spinto” sulla pila e il file menzionato nella direttiva @include
diventa il nome-file corrente. Man mano che un file è finito,
la pila viene “disfatta”, e il precedente file in input diventa nuovamente il
file in input corrente. Il processo viene iniziato ponendo il file originale
come primo file sulla pila.
La funzione percorso()
trova qual è il percorso completo di un file.
Simula il comportamento di gawk
quando utilizza la variabile
d’ambiente AWKPATH
(vedi la sezione Ricerca di programmi awk
in una lista di directory.).
Se un nome-file contiene una ‘/’, non viene effettuata la ricerca del
percorso. Analogamente, se il
nome-file è "-"
, viene usato senza alcuna modifica. Altrimenti,
il nome-file è concatenato col nome di ogni directory nella lista dei
percorsi, e vien fatto un tentativo per aprire il nome-file così generato.
Il solo modo di controllare se un file è leggibile da awk
è di
andare avanti e tentare di leggerlo con
getline
; questo è quel che
percorso()
fa.86
Se il file può essere letto, viene chiuso e viene restituito il valore di
nome-file:
progr_che_espande=' function percorso(file, i, t, da_buttare) { if (index(file, "/") != 0) return file if (file == "-") return file for (i = 1; i <= n_dir; i++) { t = (lista_percorsi[i] "/" file)
if ((getline da_buttare < t) > 0) { # found it close(t) return t }
} return "" }
Il programma principale è contenuto all’interno di una regola BEGIN
.
La prima cosa che fa è di impostare il vettore lista_percorsi
usato
dalla funzione percorso()
. Dopo aver diviso la lista usando come
delimitatore ‘:’, gli elementi nulli sono sostituiti da "."
,
che rappresenta la directory corrente:
BEGIN { percorsi = ENVIRON["AWKPATH"] n_dir = split(percorsi, lista_percorsi, ":") for (i = 1; i <= n_dir; i++) { if (lista_percorsi[i] == "") lista_percorsi[i] = "." }
La pila è inizializzata con ARGV[1]
, che sarà "/dev/stdin"
.
Il ciclo principale viene subito dopo. Le righe in input sono lette una dopo
l’altra. Righe che non iniziano con @include
sono stampate così come
sono.
Se la riga inizia con @include
, il nome-file è in $2
.
La funzione percorso()
è chiamata per generare il percorso completo.
Se questo non riesce, il programma stampa un messaggio di errore e continua.
Subito dopo occorre controllare se il file sia già stato incluso. Il vettore
gia_fatto
è indicizzato dal nome completo di ogni nome-file incluso
e tiene traccia per noi di questa informazione. Se un file viene visto più
volte, viene stampato un messaggio di avvertimento. Altrimenti il nuovo
nome-file è aggiunto alla pila e l’elaborazione continua.
Infine, quando getline
giunge alla fine del file in input, il file
viene chiuso, e la pila viene elaborata. Quando indice_pila
è minore
di zero, il programma è terminato:
indice_pila = 0 input[indice_pila] = ARGV[1] # ARGV[1] è il primo file for (; indice_pila >= 0; indice_pila--) { while ((getline < input[indice_pila]) > 0) { if (tolower($1) != "@include") { print continue } cammino = percorso($2) if (cammino == "") { printf("igawk: %s:%d: non riesco a trovare %s\n", input[indice_pila], FNR, $2) > "/dev/stderr" continue } if (! (cammino in gia_fatto)) { gia_fatto[cammino] = input[indice_pila] input[++indice_pila] = cammino # aggiungilo alla pila } else print $2, "incluso in", input[indice_pila], "era già incluso in", gia_fatto[cammino] > "/dev/stderr" } close(input[indice_pila]) } }' # l'apice chiude la variabile `progr_che_espande' programma_elaborato=$(gawk -- "$progr_che_espande" /dev/stdin << EOF $programma EOF )
Il costrutto di shell ‘comando << marcatore’ è chiamato here document (documento sul posto). Ogni riga presente nello script di shell fino al marcatore è passato in input a comando. La shell elabora i contenuti dell’here document sostituendo, dove serve, variabili e comandi (ed eventualmente altre cose, a seconda della shell in uso).
Il costrutto di shell ‘$(…)’ è chiamato sostituzione di comando. L’output del comando posto all’interno delle parentesi è sostituito nella riga di comando. Poiché il risultato è usato in un assegnamento di variabile, viene salvato come un’unica stringa di caratteri, anche se il risultato contiene degli spazi bianchi.
Il programma espanso è salvato nella variabile programma_elaborato
.
Il tutto avviene secondo le fasi seguenti:
gawk
con il programma che gestisce le @include
(il valore della variabile di shell
progr_che_espande
) leggendo lo standard input.
programma
.
L’input è passato a gawk
tramite un here document.
programma_elaborato
usando la sostituzione di comando.
L’ultima fase è la chiamata a gawk
con il programma espanso,
insieme alle opzioni originali e agli argomenti della riga di comando che
l’utente aveva fornito:
eval gawk $opts -- '"$programma_elaborato"' '"$@"'
Il comando eval
è una struttura della shell che riesegue
l’elaborazione dei parametri della riga di comando. Gli apici proteggono le
parti restanti.
Questa versione di igawk
è la quinta versione di questo programma.
Ci sono quattro semplificazioni migliorative:
@include
anche per i file specificati tramite l’opzione
-f consente di semplificare di molto la preparazione del programma
iniziale awk
; tutta l’elaborazione delle istruzioni @include
può essere svolta in una sola volta.
getline
all’interno della
funzione percorso()
quando si controlla se il file è accessibile
per il successivo uso nel programma principale semplifica notevolmente
le cose.
getline
nella regola BEGIN
rende possibile
fare tutto in un solo posto. Non è necessario programmare un ulteriore ciclo
per elaborare le istruzioni @include
nidificate.
sh
, il che rende più difficile la comprensione a chi non
abbia familiarità con il comando
sh
.
Inoltre, questo programma dimostra come spesso valga la pena di utilizzare
insieme la programmazione della sh
e quella di awk
.
Solitamente, si può fare parecchio senza dover ricorrere alla programmazione
di basso livello in C o C++, ed è spesso più facile fare certi tipi di
manipolazioni di stringhe e argomenti usando la shell, piuttosto che
awk
.
Infine, igawk
dimostra che non è sempre necessario aggiungere nuove
funzionalità a un programma; queste possono spesso essere aggiunte in
cima.87
Una spiegazione dettagliata del linguaggio della
sh
non rientra negli intenti di questo libro. Qualche spiegazione
sommaria viene fornita, ma se si desidera una comprensione più dettagliata, si
dovrebbe consultare un buon libro sulla programmazione della shell.
In alcune versioni molto datate di
awk
, il test ‘getline da_buttare < t’ può ripetersi in un ciclo
infinito se il file esiste ma è vuoto.
gawk
è in grado di elaborare istruzioni @include
al suo stesso interno, per
permettere l’uso di programmi awk
come script Web CGI.
Successivo: Programma anagram, Precedente: Programma sed semplice, Su: Programmi vari [Contenuti][Indice]