PPP Copertina Rc5

Articoli


Realizzare dei "filtri"

I comandi e gli script solitamente utilizzati in "pipe" (cioè che accettano come standard input lo standard output di un altro comando o script) sono solitamente chiamati filtri (in analogia con le "tubazioni" che usano). Il tipico impiego di questi "filtri" è l’elaborazione dei testi. Di seguito vedremo qualche utile programma per la realizzazione dei filtri (la filosofia stessa di Unix prevede la disponibilità di numerosi piccoli programmi, che possono essere combinati tra loro per svolgere tutti i compiti "quotidiani", alla stregua dei "mattoncini" delle construzioni).

I principali comandi a disposizione dell'utente per realizzare filtri "ad hoc" sono:

Per "riga" si intende una sequenza di caratteri che terminata con il ritorno a capo; per "colonna" si intende un gruppo di caratteri separato dal successivo da un particolare delimitatore).

Di seguito vedremo sommariamente alcuni dei comandi citati in precedenza, in particolare cut, grep, tr e sed. Per una descrizione di sort riferirsi all'articolo "I comandi buffi di Unix" (rivista Beta, numero 8 ).
Per una descrizione di awk e perl, che vanno ben oltre i semplici "filtri" (solo il manuale di awk supera le 20 pagine!), riferirsi a testi specifici. Per awk un buon libro è quello di Alfred Aho, Brian Kernighan e Peter Weinberger (di tre autori di awk appunto) "The AWK Programming Language", edito dalla Addison-Wesley (il libro è ricco di numerosi esempi); in alternativa procurarsi (in versione Postscript) "The GAWK Manual" (la più versione recente dovrebbe essere la 0.15, Aprile '93).

 


CUT

Questo comando è relativamente semplice da utilizzare e permette di "ritagliare" il testo in colonne. In particolare la forma più semplice è cut -c lista file_in, che preleva solo le colonne specificate. La "lista" di colonne è una stringa che contiene i numeri delle colonne desiderate (per specificare un intervallo di utilizza il carattere "-"). Ad esempio per visualizzare solo i caratteri compresi tra la 5° colonna la 9°:

cut -c "5-9" file_input

Nel caso di testo tabellare, si utilizzano le opzioni -f (specificando sempre una lista di colonne) e -d (che specifica il delimitatore di colonna, di default è un numero qualsiasi di spazi e/o di tabulazioni). A esempio per visualizzare solo il primo e il quinto campo del file /etc/passwd, che corrispondono al login e al nome completo, si utilizza il comando (l'uso del quoting è in questo caso opzionale, perché né la virgola, né i due punti hanno un significato particolare per la shell):

cut -f "1,5" -d ":" /etc/passwd

 


GREP

L'utilizzo più semplice di questo utilissimo comando è di cercare all'interno di un file (o di più file) tutte le righe che contengono una particolare stringa. In realtà in Unix esiste il comando fgrep (su Linux in realtà è un link a grep -F) che è ottimizzato proprio per questo semplice compito.

Ad esempio per visualizzare le righe che contengono la stringa "include" in tutti i file C della directory corrente (se la stringa contiene spazi è necessario "quotarla"):

grep include *.c		# oppure fgrep include
*.c

Volendo è possibile invertire il risultato con l'opzione -v (subito dopo il comando), nell'esempio precedente grep visualizzerebbe tutte le righe tranne quelle che contengono "include".

Altre opzioni utili sono: -n (numera le righe che soddisfano il match, utile per cercarle dopo un editor), -y (disabilita il "case sensitive", ossia non effettua distinzione tra maiuscole e minuscole) , -w (effettua la il confronto solo su "parole") , -x (effettua il confronto sull'intera linea), -e (utilizza più criteri di confronto) e -f (legge i "pattern" da un file). Per una descrizione completa riferirsi alla pagina di manuale.

Per un utilizzo più evoluto di grep è necessario capire il concetto di "espressione regolare", che sono dei modelli (degli schemi) del testo da cercare. Il comando prende nome proprio da questo concetto, infatti grep significa proprio "global regular expression printer" (visualizzatore di espressioni regolari globali).

Il meccanismo è analogo a quello utilizzato dalla shell per identificare più file mediante i caratteri "jolly", in questo caso i caratteri con significato particolare vengono chiamati "metacaratteri". Purtroppo i metacaratteri delle espressioni regolari sono diversi dai corrispondenti di shell (o alcuni hanno un significato diverso all'interno della shell), per questo molte volte è necessario fare ricorso o al quoting o al carattere di escape.

I principali metacatteri sono: il punto "." (corrisponde al della shell e identifica un qualsiasi carattere non nullo), il carattere "^" (identifica l'inizio di una linea), il dollaro "$" (identifica la fine di una linea).

Ad esempio per cercare tutte le righe che cominciano con una parola di 4 lettere seguita da un punto:

grep "^....\." file_input

Oltre a questi tre metacaratteri esistono alcuni particolari costrutti come le "classi di caratteri" le "chiusure" e le "ripetizioni" delle espressioni.

Per identificare una classe (un gruppo) di caratteri, si utilizzano le parentesi quadre e per indicare una "gamma" di caratteri si utilizza il segno "-"; ad esempio [0-9] identifica tutti i numeri di una cifra. Per negare la condizione sui caratteri si utilizza (dentro le quadre) il carattere "^". È importante capire che la "gamma" si riferisce solo a singoli caratteri, quindi [1-20] non identifica tutti i numeri da 1 a 20, bensì corrisponde ai caratteri 1, 2 o 0 (zero). In Linux (e su molti altri Unix) sono disponibili alcuni costrutti particolari: [:digit:] (identifica tutti i numeri, corrisponde a [0-9]), [:lower:] (identifica i caratteri minuscoli, corrisponde a [a-z]) , [:upper:] (identifica i caratteri maiuscoli, corrisponde a [A-Z]) , [:alnum:] (identifica lettere e i numeri, corrisponde a [0-9A-Za-z]).

Per identificare "chiusure e ripetizioni", si utilizzano le parentesi graffe e una coppia di numeri {m,n}, dove m corrisponde al numero minimo di ripetizioni (di default 1) e n il numero massimo (di default "infinito"). Omettendo la virgola si intende che i due valori coincidono (m=n).

La chiusura mediante parentesi graffe, non è supportata da tutte le versioni di grep (in particolare su Linux per utilizzarle è necessario usare il comando grep -E) e soprattutto altri comandi che utilizzano le espressioni regolari non accettano questa sintassi.

L'esempio precedente può essere riscritto nel seguente modo:

grep "^.{3}\." file_input

Esistono alcuni metacaratteri che corrispondono ad una forma abbreviata (accettata dai grep standard): "*" (indica che l'espressione deve essere ripetuta da 0 più volte e corrisponde a {0,}), "+" (indica che l'espressione deve essere ripetuta da 1 più volte e corrisponde a {1,}), "?" (indica che l'espressione deve essere ripetuta 0 o una volta e corrisponde a {0,1}).

Ad esempio per visualizzare tutte le linee di un file tranne quelle vuote (compresi eventuali spazi):

grep -v "^ *$" file_input

Volendo utilizzare più espressioni regolari o si utilizza l'opzione -e prima di ciascuna espressione o si utilizza il comando egrep (in Linux è meglio utilizzare grep -E), che prevede l'utilizzo di più espressioni, separate dal carattere "|" (su alcuni sistemi devono essere racchiuse tra le parentesi tonde).

 


TR

Il comando tr (transliterate) permette di "tradurre" i caratteri di un file (o dallo standard input), ma anche di cancellare determinati caratteri. Anche questo comando può utilizzare le espressioni regolari (pur con alcuni limiti). È importate notare che tr lavora solo in pipe o con il reindirizzamento dello standard input.

L'utilizzo più semplice è convertire tutti i caratteri contenuti nel set1 nei corrispondenti caratteri di un nuovo set2. Per una corretta sostituzione la dimensione dei due set dovrebbe essere uguale: se set2 contiene più elementi, allora si limiterà a scartare quelli in eccedenza, ma se è set1 a contenerne di più il risultato può essere indeterminato (su Linux il GNU tr espande l'ultimo carattere del set2).

Ad esempio, per convertire tutte le maiuscole in minuscole, nel file "testo" (notate che in Linux si può fare a meno delle quadre e del quoting o, in alternativa, si può usare "[:upper:]" e "[:lower:]"):

tr "[A-Z]" "[a-z]" < testo

Opzionalmente, sono disponibili i comandi di chiusura (simili a quelli per le espressioni regolari), usati per definire caratteri ripetitivi nel set2: l'espressione [c*9] corrisponde a ripetere il carattere c per nove volte, mentre l'espressione [c*] adatta automaticamente il numero di caratteri per uguagliare quello del set1.

Un altro utilizzo di questo comodo comando è la rimozione di alcuni caratteri o dei caratteri ripetuti, mediante le opzioni -d e -s rispettivamente. A esempio per rimuovere tutti i numeri dal file "testo":

tr -d 0-9 < testo > nuovo_file

Volendo invece eliminare tutte le righe vuote, costituite da due ritorni a capo (ASCII \012) in successione:

tr -s "\012" < testo > nuovo_file

In alternativa alla notazione ottale vista nell'esempio precedente è possibile utilizzare alcune sequenze di escape particolari che definiscono alcuni caratteri non stampabili: \t (tabulazione), \n (newline), \r (carriage return), \f (form feed). Notare che in Unix ogni riga di testo è delimitata dal carattere di newline, mentre in DOS è delimitata dalla coppia newline e carriage return.

 


SED

L'editor di flusso permette di modificare agevolmente file di testo e supporta virtualmente tutti i comandi dell'editor di linea (ed). Lo stesso vi riprende in buona parte i comandi di questi due editor.

La sintassi generale è sed "comando" file, volendo passare più comandi contemporaneamente o si utilizza un file di comandi richiamato mediante il comando sed -f file_comandi file_input, oppure si utilizza l'espressione sed -e "com1" -e "com2" file.

Gli utilizzi più tipici di questo comando sono nella sostituzione di testo (poiché dispone di operazioni più evolute al semplice comando tr), nella cancellazione di testo o nella "riformattazione" del testo (ad esempio per convertire il "ritorno a capo" DOS con il "ritorno a capo" Unix). Di seguito vedremo solo qualche semplice esempio; per una descrizione completa e dettagliata fate riferimento al manuale (man sed).

Ad esempio per sostituire nel file in tutti i carattere numerici con un segno "-" e mandare il risultato in un nuovo file out (il risultato dell'operazione non può mai agire direttamente sul file di input):

sed "s/[0-9]/-/g" in > out

Il comando s/cerca/sost/opz corrisponde proprio alla sostituzione, mentre l'opzione finale /g serve a forzare la sostituzione "globale" (di default sed effettua al massimo una sostituzione per ogni riga).

Notare come sia necessario l'utilizzo delle virgolette (in molti casi è necessario anche l'utilizzo del carattere di escape). Il carattere di delimitazione dei comandi (nell'esempio "/") non è imposto a priori e può essere usato anche la virgola ",", in questo caso il quoting non sarebbe necessario (a meno di altri caratteri speciali). È possibile l'utilizzo delle "espressioni regolari" tipiche di grep, in particolare esistono gli caratteri speciali "^" (per indicare l'inizio della riga) e "$" (per indicare la fine della riga), mentre non sono disponibili le chiusure.

Uno dei limiti di è sed di non poter direttamente sostituire i caratteri speciali \n e \r. In questo caso bisogna combinare l'utilizzo di sed con quello di tr. Ad esempio la prima riga converte un file di testo DOS in un file di testo Unix, mentre la seconda effettua il viceversa (notare l'utilizzo del carattere temporaneo "#" per gestire la sostituzione di \r):

tr "\r" "#" < file_dos | sed "s/#$//" > file_unix
sed "s/$/#/" file_unix | tr "#" "\r" > file_dos

Oltre la sostituzione dei caratteri è possibile effettuare operazioni di cancellazione. Per cancellare (nello standard output) tutte le righe che iniziano con il carattere "#" (il commento di moltissimi file di configurazione e negli script di shell) è possibile utilizzare il comando sed "/^#/d" file_input. Ad esempio, volendo contare tutte le righe di "codice" effettivo (senza commenti e senza spazi) di uno script di shell, è possibile utilizzare il comando:

sed -e "/^#/d" -e -e "/^[ \t]*$/d" nome_script | wc
-l

Notare l'utilizzo dell'opzione -e (come il grep), per gestire più comandi sulla stessa riga di sed. Allo stesso modo è possibile passare un "file di comandi" mediante l'opzione -f (in questo caso un unico file può contenere numerosi comandi).

La sintassi generica di un comando sed è la seguente (le quadre indicano un argomento opzionale):

[riga_iniziale[,riga_finale]]operazione[argomento]

Se non si specifica l'intervallo delle righe, allora vengono considerate tutte le righe. Ad esempio per visualizzare solo le prime 4 righe (in alternativa al comando head), si può utilizzare il seguente comando (notare l'utilizzo dell'opzione -n per disattivare l'output e del comando p per stampare le righe desiderate):

sed -n 1,4p file_input

di Andrea Mauro


PPP Copertina Rc5