Le espressioni regolari regexp sono alla base di tutto ciò che comprende la modifica, ricerca e sostituzione di una sequenza/stringa di caratteri;  risultano essenziali per l’utilizzo di applicazioni ed utilities da sempre sovrane nei sistemi UNIX e derivati come grep,sed,awk, oltre che essere alla base della programmazione e della conoscenza di bash. Nascono appunto per definire stringhe di caratteri e di metacaratteri. Per stringhe di caratteri si intendono caratteri semplici (numeri,lettere minuscole/maiuscole,accentate) e simboli ( .;*^[]{}/ etc..) .

Le regexp sono spesso utilizzate in vari linguaggi di programmazione (bash,perl,etc..) anche se a volte possono essere scritte e interpretate in maniera differente da un linguaggio all’altro in linea generale  il concetto di base legato alle regexp resta lo stesso. Come già detto in precedenza se ne fà largo uso in molti applicativi, tra cui sed.

Sed è un manipolatore di testo più che conosciuto da tutti gli utenti GNU/Linux che agendo su un file ricerca e all’occorrenza sostituisce stringhe e/o classi di caratteri e simboli definiti. Tali stringhe sono opportunamente suddivise per funzione. La regex singola legata all’utilizzo di sed si identifica perchè posta all’interno di due slash ( / ) come da esempio:

/regexp/

NB: se i caratteri contenuti negli slash risultano essere testo semplice, sarà ricercata esattamente quella sequenza di caratteri avente distinzione “case sensitive” (per caratteri minuscoli e MAIUSCOLI). Spesso però al testo semplice si aggiungono determinati simboli che definiscono meglio e più specificatamente la regex da ricercare.

NB: non necessariamene una regex deve essere definita negli slash. Infatti solitamente anche comandi comuni quale ad esempio potrebbe essere un semplice grep potrebbero richiedere l’utilizzo di una particolare classe di caratteri facente parte delle regexp (il cui significato vedremo in seguito). Esempio:

~$ grep [[:digit:]] file.test
abc=123

Per prima cosa, come già detto precedentemente è bene elencare i simboli utilizzati nelle regexp e il loro significato.

SIMBOLI:

*   "asterisco" - accetta zero o più ripetizioni del carattere che lo precede.
  "punto" - qualunque carattere.

^   "apice" - messo all'inizio di una regex impone che la stringa cominci con quello che segue l'apice. Se usato con le parentesi quadre e prima di un carattere assume significato di negazione (tranne ciò che segue).

$   "dollaro" - se inserito alla fine della regex impone che la stringa finisca con ciò che precede il dollaro.

+   "più" - accetta una singola o più ripetizioni del carattere che lo precede.

[ ] "parentesi quadre" - tutti i caratteri all'interno delle parentesi sono presi in considerazione.

[^ ] "parentesi e apice interno" - tutti i caratteri interni le parentesi sono esclusi.

NB: il simbolo / “slash” come già detto apre e chiude l’espressione. Il simbolo ** “slash rovesciato” serve invece per trattare uno dei caratteri speciali che lo segue come un normale carattere. Esempio: **$ ricerca il simbolo del dollaro nel nostro file o nella nostra stringa.

Esempi di Set di caratteri semplici definiti dalle parentesi quadre:

[abc]     -  cerca i caratteri a b c nella stringa.

[a-k]     -  si riferisce a tutti i caratteri compresi nel range tra la lettera a e la k.

[C-In-o]  -  come prima con distinzioni "case sensitive". Cerca caratteri di range C e I ed n e o.

[0-9]     -  tutti i numeri.

[a-z]     -  tutti le lettere minuscole.

[A-Z]     -  tutte le lettere MAIUSCOLE.

[a-z0-9]  -  tutte le lettere minuscole e i numeri.

Esempi di negazione e combinazioni di parentesi quadre:

[^A-D]    -  corrisponde a tutti i caratteri tranne quelli contenuti nel range da A a D.

[Pp][Il][Pp][Oo] - corrisponde a tutte le combinazioni maiuscole e minuscole: PIPO,pipo,Pipo,pIpo,PIpo,piPo,PIPo,pipO,piPo..etc.

Classi di caratteri:

Oltre ai simboli e i set di caratteri semplici (lettere,numeri) ci sono classi di caratteri definite che in determinati casi possono risultare estremamente utili:

[:alnum:]  -  corrisponde a tutti i numeri e le lettere (sia minuscole che MAIUSCOLE comprese quelle accentate).Equivalente di [A-Za-z0-9].

[:alpha:]  -  corrisponde a tutte le lettere comprese quelle accentate (sia minuscole che MAIUSCOLE). Equivalente di [A-Za-z].

[:digit:]  -  corrisponde a tutti i numeri. Equivalente di [0-9].

[:lower:]  -  corrisponde a tutte le lettere minuscole. Equivalente di [a-z].

[:upper:]  -  corrisponde a tutte le lettere MAIUSCOLE. Equivalente di [A-Z].

[:blank:]  -  corrisponde a tutti gli spazi o tab.

[:space:]  -  corrisponde agli spazi.

[:cntrl:]  -  corrisponde ai caratteri di controllo.

[:print:]  -  corrisponde ai caratteri di range ASCII 32 - 126 compresi gli spazi.

[:graph:]  -  come [:print:] corrisponde ai caratteri di range ASCII 33 - 126 ma esclude gli spazi.

[:punct:]  -  corrisponde a tutti i caratteri di punteggiatura.

[:xdigit:] -  corrisponde a tutti i caratteri che possono formare un numero esadecimale. Equivalente di [0-9A-Fa-f].

NB: Allo stesso modo dei set di caratteri semplici, anche in questo caso  le classi di carattere vanno specificate e racchiuse in altre parentesi quadre []. Di conseguenza una classe esempio [:alnum:] và definita come:

[[:alnum:]]

Esempi di utilizzo di regexp all’interno delle specifiche di sed:

NB: chiarite le specifiche per simbologia e classi è bene fare qualche esempio di espressione mirata all’utilizzo di sed come da inizio post.

Esempi caratteri e simboli:


/./              - corrisponde a qualunque stringa contenente almeno un carattere.

/^#/             - qualunque stringa comincia con il carattere # (solitamente usato per definire un commento).

/ *$/            - qualunque stringa che finisce (definito con $) con uno o più spazi (spazi ripetuti definiti da *).

/[fui]/          - qualunque stringa che contiene anche solo una delle lettere f u ed i minuscole.

/^[fui]/         - qualunque stringa che comincia con uno dei caratteri f u ed i minuscoli.

/[^f]/           - qualunque stringa che non contiene la lettera f minuscola.

/^linux$/        - qualunque stringa che comincia con la parola linux e finisce con la stessa. Se ci sono stringhe contenenti altre parole e/o caratteri (prima e dopo) non saranno prese in considerazione.

/^[Ll]inux/      - qualunque stringa comincia con la parola linux con iniziale sia MAIUSCOLA che minuscola.

/^[Ll]inux+$/    - come l'esempio precedente ma comprende una o più ripetizioni della lettera x. Esempio: Linuxx , linuxxx, linuxxxxxx.

Esempi con set di caratteri:

/^[A-Z]+$/       - qualunque stringa composta da lettere MAIUSCOLE con almeno 1 carattere (lettere accentate escluse).

/^[a-z]+$/       - come il precedente esempio ma con lettere minuscole.

/^[0-9]+$/       - ancora uguale al precedente con i numeri.

Esempi con caratteri speciali (slash rovesciato):

/^[0-9]{8}$/    - qualunque riga che comincia con dei numeri ripetuti per 8 volte e che finisce con essi definito dal simbolo $ (esempio si date espresse in sequenza 16012010).

/^l{5}$/        - qualunque stringa contenente solo la lettera l minuscola ripetuta 5 volte e nient'altro (lllll).

/x{10}/         - qualunque stringa contenente la lettera x ripetuta almeno 10 volte (linux Linuxxxxxxxxxxxxx pippo).

/x{2}$/         - qualsiesi stringa contenente la lettera x ripetuta almeno due volte e nient'altro dopo di essa (linux Linuxxx).

Fatta una panoramica generale con qualche esempio utile, passiamo all’utilizzo base di queste espressioni. Prendiamo un file di testo semplice con delle stringhe esempio fatte da noi ed esercitiamoci attraverso l’uso di sed. Quindi:

~$ echo 12456 > prova.txt
~$ echo lettere_miste: f g h l o ; punteggiatura .:;, tab: 000000 >> prova.txt
~$ echo linuxxxxx : questo è un esempio >> prova.txt
~$ echo accentate: è ò à >> prova.txt
~$ echo Linux >> prova.txt

Salvato il nostro file, con stringhe esempio, non resta che provare a ricercare quelle interessate con la regexp giusta seguendo la sintassi di sed:

~$ sed -n -e '/regexp_da_inserire_qui/p' prova.txt

NB: Con il parametro finale p dopo gli slash / sed si limiterà a stampare ad output il risultato della ricerca delle stringhe corrispondenti la regexp scritta da voi nel file esempio.

Allo stesso modo si possono operare sostituzioni di parti di testo:

~$ sed -e 's/regexp/testo_da_sostituire_alla_regexp/g' prova.txt

In specifico, prendendo in esempio il file prova.txt creato precedentemente:

~$ sed -i.backup -e 's/^[Ll]inux/GNU/Linux/g' prova.txt

NOTA:  bisogna fare un distinguo tra la fase di ricerca e quella di sostituzione. Se operiamo su una regexp per una semplice ricerca, sed si limiterà a stampare a schermo tutte le stringhe che iniziano o contengono la regexp da noi imposta. Esempio: ’/^Linux/p’ .Al contrario se operiamo su una sostituzione del tipo:

's/^[Ll]inux/GNU/Linux/' 

Sed sostituirà la parola Linux (iniziale sia MAIUSCOLA che minuscola) con GNU/Linux solo a patto che sia la prima parola della stringa.

NB: Come si nota dall’esempio lo slash rovesciato farà in modo di trattare lo slash successivo / come un normale carattere e non come la fine della regexp. In più operando su tale file e modificandolo creerà una copia di backup prova.txt.backup con le vecchies tringhe non sostituite.

NB: questo post non entra nelle specifiche del comando sed ma si limita ad una panoramica generalizzata di ciò che sed utilizza (regexp) per effettuare manipolazioni e sostituzioni di flussi di testo, così come avviene per awk,grep,ls o per gli script bash da noi generati. Tali specifiche saranno spiegate in post successivi riguardo le singole utilities.

Considerazioni Personali: talvolta ci sono più modi per arrivare allo scopo. Il miglior modo per imparare è sperimentare. Il post, non è altro che una sorta di annuario di ciò che è stato ampiamente descritto da link ufficiali di riferimento (advanced bash scripting) comprensivi di  qualche esempio personale mirato. Divertitevi, visto che il mondo delle regexp è molto più ampio di ciò che ci si aspetta (come direbbe un caro amico: questo argomento è una vera materia!). Alla prossima.

# End