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 stringa
dove 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.

Considerare nuovamente il file /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$.

Si utilizzi il comando 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.

Si consideri ora il proprio numero di matricola, formato da 6 cifre. Si utilizzi il comando 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.

Tornare al file /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: Si provi a testare questa stringa ad esempio con:
$ 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é?

Si progetti una linea di comando con 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 \..