Cara lettrice[1],
è tanto che non ci sentiamo, vero? Spero che tu ti sia divertita ugualmente
e abbia imparato tante cose nuove leggendo BETA. D'altra parte, è noto che
le cose che scrivo io non le scrive nessun altro, e quindi se ti eri
appassionata alla serie "i comandi buffi di Unix" ti sarai trovata in crisi
di astinenza. Però di comandi buffi non è che ce ne siano più tanti, e
comunque avevo voglia di cambiare un poco. Ho pensato allora di iniziare
una miniserie sulle utilità Unix: quei programmi cioè che chiunque usi il
Sistema Operativo Più Bello del Mondo dà per scontate, e che usa come
costruzioni ausiliarie per creare, assieme ai mattoncini dei "comandi
buffi", quelle opere di alta scuola che suscitano sempre ammirazione in chi
ci guarda dietro le spalle.
Per cominciare, ho scelto di parlare di awk, forse la più misconosciuta di queste utilità. Anzi, i casi sono due: o non sai cosa sia, e io ti rispondo prontamente "un linguaggio interpretato che permette di manipolare dei file di testo"[2]; o lo sai, e sei già lì a chiedermi "Ma che diavolo me ne faccio, quando esiste perl che fa di tutto, di più?" La domanda è intelligente, tanto che sta persino nelle FAQ di comp.lang.awk[3]. Tra le risposte che si possono trovare lì e quelle mie personali, penso che le più azzeccate siano: (a) awk è più semplice; (b) ci mette meno tempo a caricarsi in memoria ed eseguire[4]; (c) non devi mettere i dollari $ davanti ai nomi delle variabili.
Detto questo, mi sembra opportuno ricordare subito che il nome awk non è stato scelto perché era bellino a pronunciarsi, ma perché i suoi autori originali sono stati - in ordine alfabetico - Alfred V. Aho, Brian W. Kernighan e Peter J. Weinberger. Saprai certamente chi è il signor K, l'amico di Dennis Ritchie; posso aggiungere per la tua cultura personale spicciola che il signor A è uno dei massimi esperti di linguaggi per calcolatore (ci ho fatto gli esami sui suoi libri...). Del signor W non so nulla, ma non penso che sia stato scelto semplicemente perché il programma uscisse fuori con un bel nome; mi fido insomma delle sue capacità.
Per terminare l'introduzione, mi preme soltanto ricordarti una cosa: non usare mai awk! Beh, se stai usando linux, in realtà awk è un link simbolico a gawk, quindi il problema non si pone. Ma negli unix commerciali la cosa è ben diversa. Il linguaggio ha subito infatti una profonda mutazione quando è stato standardizzato POSIX[5], e i programmi tipici che farò come esempio non è detto funzionino con il linguaggio originale. La cosa buffa è che, a parte l'onnipresente gawk che è arrivato alla versione 3.03 mentre sto scrivendo questo articolo, gli unix commerciali hanno l'awk POSIX, che si chiama nawk (new awk, per i pignoli). Non ho mai capito perché mai non abbiano potuto sostituire il vecchio awk... magari non volevano fare un dispiacere ai signori A, W e K. Infine, per complicare vieppiù le cose, esiste un'altra implementazione gratuita di awk, preparata da Michael Brennan e chiamata mawk. Un purista come me l'avrebbe chiamato bawk, ma tant'è.
Ad ogni modo, io parlerò dei comandi gawk, notando quando c'è qualcosa che non si trova sotto POSIX e sperando di ricordarmi di avvisare se qualcosa non è nemmeno nel "vecchio" awk.
Ma non farci troppo conto.
Per iniziare, come al solito, ti sparerò la lunga lista di opzioni del comando: ma prima è meglio che ti faccia un ripasso molto veloce su come funzionano tipicamente i comandi Unix.
L'unità fondamentale di misura unixara è il record, cioè un insieme di caratteri che finisce con un newline (Ctrl-L per noi). Questo record viene poi diviso magicamente in tanti campi (field). In pratica, chi scrive un programma unix pensa di avere un record per volta, a cui potrà accedere con $0, record formato dai campi $1, $2, ... Inoltre, il programma sarà scritto in modo che prenda il file da processare o nello standard input o tra gli ultimi parametri della riga di comando, e scriva il risultato nello standard output. E i comandi per processare il file? Beh, se di comandi ce ne sono tanti, li si scrive su un file e si dice al programma di leggerselo; ma se si ha fretta e i comandi sono uno o due, il metodo più comodo è scrivere i comandi dopo le opzioni, infilarli tra due apici ' ' per tenere tutto insieme[6], e vedere l'effetto che fa (spesso un errore di sintassi, almeno prima di avere letto i miei articoli :-)).
Adesso sei pronta a sciropparti la lista dei comandi. Quelli comuni a tutti non sono poi tanti: le due forme standard sono infatti
awk [ -F fs ] [ -v var=valore ] [--] 'programma' file ...quando il programma è interno alla riga di comando, e
awk [ -F fs ] [ -v var=valore ] -f progfile [--] file ...quando il programma è contenuto in un file. Come vedi, puoi usare quanti file di input vuoi, e ciò è bello. Puoi anche non averne nessuno, nel qual caso awk leggerà dallo standard input, e ciò è ancora più bello. Inoltre, anche se non l'ho indicato negli esempi, puoi anche avere tanti file di programma quanti vuoi: basta scrivere tante opzioni -f, e il programma si occuperà di concatenarli nell'ordine prima di leggerli. E che me ne faccio, dirai? Non potevo già concatenarli io? Beh, non ci sarebbe gusto a farsi tanti bei file di libreria. Puoi anche definirti delle variabili che verranno create e inizializzate prima di leggere il programma: è l'opzione -v, come sicuramente hai intuito. L'ultima opzione, -F, serve ad indicare il separatore dei campi. Se lo si omette, i campi sono divisi da un numero qualunque di spazi e tab, come conviene a un default che si rispetti: altrimenti si usa l'espressione regolare indicata. Sì: espressione regolare, non semplice carattere. Devo ammettere che non ho ben capito l'utilità della cosa: però posso garantire che funziona. Ho provato a scrivere in un file 1::2:::3: :5 e ho controllato che l'espressione ":\*" (con il backslash perché altrimenti la shell si lamenta) separa correttamente i campi, che sono "1", "2", "3", " " e "5".
Se hai deciso di usare gawk, hai poi a disposizione tutta una serie più o meno utile di opzioni, tutte della forma -W opzione (o anche --opzione se invece che POSIX preferisci lo stile GNU). Alcune sono standard, come -W version che stampa semplicemente il numero di versione; -W copyright (o copyleft che dir si voglia) che stampa la versione ridotta del copyright GNU; -W help (oppure usage) che dà l'elemco dei comandi, e -W posix per essere pienamente compatibili con lo standard POSIX. Altre sono specifiche: -W traditional elimina tutte le estensioni GNU, girando in "compatibility mode"; -W re-interval permette di usare le cosiddette interval expression come espressioni regolari (sono quelle che dicono "da m a n occorrenze di un'espressione", POSIX le ha volute ma in genere gli awk non ce l'hanno. Te ne parlerò meglio la prossima volta); -W lint avvisa di costrutti non portabili; -W lint-old avvisa di costrutti non portabili verso il vecchio awk originale[7].
Resta ancora da dire una cosa: se non si gira in compatibility mode e si danno opzioni ignote, queste vengono infilate nella variabile di ambiente ARGV da dove possono accedute dal programma.
Se hai già visto come è fatto un programma awk, avrai notato delle buffe frasi BEGIN e END, e ti sarai magari chiesta se il linguaggio assomiglia un po' al Pascal[8]. La risposta è no. Semmai, le affinità sono più con il C (e ti credo...). Il bello è che puoi mettere prima END e poi BEGIN, e non cambia nulla.
Ma procediamo con ordine: i programmi awk sono formati da una serie di blocchi della forma pattern-azione e da funzioni opzionali:
pattern { azione } function nome(lista_parametri) { azione }Come avrai capito, BEGIN e END sono in realtà dei pattern speciali, e nulla più: ecco perché la loro posizione relativa è ininfluente.
Come viene eseguito un programma awk? per prima cosa, vengono letti ordinatamente tutti i file di programma, siano essi stati definiti con -f o ce ne sia uno solo all'interno della riga di comando. gawk si distingue come al solito: se viene definita la variabile d'ambiente AWKPATH, i file vengono cercati in tutte le directory indicate in essa. Letto il programma, si fanno tutte le assegnazioni indicate dalle opzioni -v; poi si eseguono le azioni nel blocco (o nei blocchi!) BEGIN, nell'ordine in cui vengono trovate. Solo a questo punto si cominciano a leggere i file di input; per ogni record si guarda quali pattern vengono attivati, e si eseguono le azioni corrispondenti. Infine, finito l'input, si cercano i blocchi END e si eseguono questi ultimi. Ricordati solo che se ci sono solo blocchi BEGIN, l'input non viene letto...
Nei blocchi, possono mancare o il pattern o l'azione. Nel secondo caso, l'azione implicita è {print}, cioè viene stampata la riga di input; nel primo caso, l'azione viene eseguita per ogni riga di input.
Altri costrutti sintattici: i commenti iniziano con il carattere #, e terminano alla fine della riga; righe vuote sono considerate commenti; uno statement termina alla fine di una riga, a meno che non finisca in ",", "{", "?", ":", "&&", "||" o alla peggio con un bel backslash; si possono mettere più statement in una riga separandoli con un puntoevirgola, sia all'interno di un'azione che per separare le coppie pattern/azioni. Il bello è che, almeno nel caso di un programma scritto nella riga di comando, si possono giustapporre più coppie pattern-azioni senza infilarci un ";" in mezzo: provare per credere.
I pattern possono essere di varie forme:
Dei pattern speciali BEGIN e END (che non si possono mischiare con gli altri, e che devono per forza avere un'azione!) ho già parlato sopra. Per quasi tutte le altre forme di pattern, il significato è quello usuale: le espressioni regolari sono quelle di egrep; le espressioni regolari sono le stesse che vedremo nelle azioni (maggiore, minore...); abbiamo AND, OR e NOT logici e l'operatore ?: come in C, e le parentesi per definire l'ordine di valutazione. L'unico pattern un po' strano è l'ultimo, il range pattern; anch'esso non si può combinare con gli altri, e serve per attivare tutti i record a partire da uno che contiene il primo pattern a uno che contiene il secondo pattern (compreso). Se ad esempio abbiamo un file con una serie di programmi, tutti compresi tra una riga -- inizio e una -- fine, e del testo esplicativo tra i vari programmi, una riga di awk tipo '/^-- inizio/,/^-- fine/' mi permette di eliminare tutti questi commenti[9].
Già che ci sono, magari, potrebbe però essere utile rammentarti le principali espressioni regolari (evito quelle particolari GNU, mi paiono esagerate). Eccoti una tabellina da ritagliare:
Il buon POSIX ha anche definito alcune classi di caratteri: queste sono della forma [: nome :] e sono delle utili abbreviazioni. Ecco le classi che esistono, in rigoroso ordine alfabetico: [:alnum:] corrisponde ai caratteri alfanumerici, [:alpha:] agli alfabetici, [:blank:] a spazio o tab, [:cntrl:] ai caratteri di controllo, [:digit:] alle cifre, [:graph:] ai caratteri stampabili e visibili (lo spazio è stampabile, ma non visibile...), [:lower:] ai caratteri minuscoli, [:print:] a quelli stampabili (non i vari control, insomma), [:punct:] a quelli di punteggiatura, [:space:] ai generici caratteri di spaziatura (quindi anche il formfeed, tanto per dirne uno), [:upper:] ai caratteri maiuscoli e [:xdigit:] alle cifre esadecimali (0-9,A-F,a-f).
Se ti stai chiedendo perché mai abbiano inventato [:alpha:] quando si faceva più in fretta a scrivere [A-Za-z], significa che non sei una brava italiana. Queste classi di caratteri conoscono infatti il locale: ergo, la è viene riconosciuta come carattere alfabetico, anche se viene rappresentata a 8 bit. Un gran vantaggio, no?
Bene, per questa prima puntata direi che basta. In pratica, come hai visto, puoi già usare awk per lanciare i programmini banali, quelli per cui non ti viene voglia di usare perl. Ti basta sapere che se fai operazioni numeriche come in C e stampi con print o printf viaggi tranquilla. Ad esempio, per calcolare la somma in byte dei file di una directory, compreso lo spazio occupato dalle directory che wc -c omette, puoi lanciare
ls -l | awk 'END {print a}; {a+=$5}'dove mi sono divertito a iniziare dal fondo. Se invece ti capita di avere troppi processi sendmail che girano, e devi cancellarli tutti in un colpo, puoi usare
kill -1 `ps ax | grep "sendmail" | awk '{print $1}'`Nella prossima puntata ti insegnerò a leggere al volo questi comandi, oltre a parlare di azioni, variabili, vettori, funzioni e operatori predefiniti.[10]
Se intanto vuoi provare a divertirti da sola, ti lascio i riferimenti FTP delle tre versioni di awk: