Filtrare testo per sottostringhe
Come si è visto nella maggior parte degli esempi fino ad ora, è spesso utile essere in grado di filtrare un testo alla ricerca di qualche sottostringa. Ad esempio, l'esercizio di cercare Francesca dentro la Divina Commedia sarebbe stato più semplice nel caso fossimo in stati immediatamente capaci di filtrare solamente le linee contenenti la stringa di interesse.
Su tutti i sistemi Unix esiste un comando apposito per filtrare
questi testi, che si chiama grep
. L'acronimo sta per
generic regular expression print, e permette di selezionare
da un file di testo solo le righe che coincidono con una determinata
espressione regolare (probabilmente avrete parlato di espressioni
regolari a programmazione).
Nella sua incarnazione più semplice, il comando grep
legge dallo stdin, e scrive le righe filtrate sullo stdout. Ad esempio,
si provi ad eseguire il comando
$ grep stringadove
stringa
è una stringa di testo a scelta, e si cominci
a digitare del testo. Solamente le righe contenenti la stringa scelta
verranno stampate in risposta da grep
. In alternativa,
è possibile indicare a grep
un file da leggere come argomento,
e questo verrà usato al posto dello stdin.
/home/robol/dante.txt
utilizzato
in precedenza, e filtrarlo alla ricerca della parola Francesca. Quante volte
appare? Se volessimo conoscere anche la riga di testo in cui appare la parola,
potremmo chiedere a grep
di stampare i numeri di riga tramite il
flag -n
. Provare a confrontare il risultato con quello ottenuto
utilizzando less
.
Ora proviamo a svolgere un compito più da "matematico". Si utilizzi il comando
wget
per scaricare una copia del file disponibile all'indirizzo
https://people.cs.dm.unipi.it/robol/pi.txt che, come il nome suggerisce contiene
un po' di cifre di $\pi$.
wc
(Word Count)
per determinare quante cifre di $\pi$
sono incluse nel file scaricato. Il comando wc pi.txt
stamperà
tre numeri, cosa rappresentano? Si provi a guardare il manuale, e si determini
come farsi stampare solamente il numero di caratteri nel file.
grep
per determinare se le prime cifre di $\pi$ scaricate
contengono il proprio numero di matricola come sottostringa. In caso contrario,
qual è il massimo intero $N$ per cui le prima $N$ cifre del proprio numero di matricola
sono contenuto nelle prime cifre decimali di $\pi$?
Uso più avanzato di grep
Come vedremo anche nelle prossime lezioni, il comando grep
permette di filtrare il testo in modo piuttosto evoluto. Una prima opzione che
possiamo sperimentare è quella della ricerca case-insensitive,
che si attiva con il parametro -i
.
/home/robol/dante.txt
. Quante volte appare la parola
"Francesca" se non distinguiamo tra maiuscole e minuscole? Provare a contarle
e determinare la riga in cui si trovano. Come potremmo distinguere quando la
parola Francesca non appare come sottostringa di un'altra parola?
Il comando grep
permette di descrivere la stringa da trovare come
un'espressione regolare. Ad esempio, potremmo provare a scrivere un comando che
permetta di filtrare le righe contenenti un'ora, scritta nel formato HH:MM
.
In parole, vorremmo esprimere il concetto:
"Filtra ogni stringa composta da due numeri, seguiti dal simbolo :
, seguiti
da altri due numeri".
Possiamo tradurlo come un'espressione regolare compresa da grep in questo modo:
$ grep "[[:digit:]]\{2\}:[[:digit:]]\{2\}"I significati dei vari pezzi sono:
[[:digit:]]
matcha ogni cifra fra0
e9
.- Il simbolo
{2}
, a cui sono stati aggiunti degli escape per evitarlo di fare interpretare le graffe alla shell, significa L'espressione precedente deve essere ripetuta esattamente due volte. - Il simbolo
:
indica esattamente il testo:
- Il termine finale, uguale al primo, match due cifre.
$ echo "17:18" | grep "[[:digit:]]\{2\}:[[:digit:]]\{2\}" $ echo "1:87 test" | grep "[[:digit:]]\{2\}:[[:digit:]]\{2\}" $ echo "123:12" | grep "[[:digit:]]\{2\}:[[:digit:]]\{2\}"Il comando funziona come vi aspettate? In caso contrario, perché?
grep
che sia in grado di identificare
le date scritte come GG/MM/YYYY
; si ottengono punti bonus se si riesce a gestire
anche il caso in cui i mesi e i giorni possano essere scritto con una sola cifra.
Ci sono molte classi di caratteri già definite (che come al solito si trovano nel
manuale del comando), oppure se ne possono definire di nuove
semplicemente listandoli fra parentesi quadre; ad esempio, [agt]
matcha uno
qualunque dei tre caratteri specificati. È solitamente molto pratico il simbolo .
,
che matcha qualunque carattere che alfanumerico. Ovviamente, per matchare il punto è
necessario scrivere \.
.