![]() |
![]() |
![]() |
Utilità Unix: awk (parte I)Cara lettrice[1], awk: un nome, un acronimoPer 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. Opzioni e funzionamentoPer 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. Pattern ed espressioni regolariSe 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? Chi ben comincia...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:
|
|