Espressioni regolari

Da Hacknowledge.

Indice

[modifica] Cosa sono le Regex

Le regex (regular expressions) sono un potente strumento messo a disposizione dal perl (e in seguito da altri linguaggi) per la manipolazione dei dati. La scelta di Perl è stata quella di implementare nativamente il supporto per le regex per la gestione delle stringhe (confronti, sostituzioni ecc.), che sono quindi uno strumento indispensabile quando si vuole operare su dei buffer. Le regex sono uno strumento a un livello di astrazione molto più alto rispetto alle classiche funzioni C-style (strcmp, strpos, strstr...) ereditate anche da altri linguaggi, e che Perl ha scelto di non implementare, in luogo delle regex. Le regex operano quindi confrontando una stringa con un pattern stabilito dall'utente.

[modifica] Quick start

Partiamo da un esempio semplice:

if($test =~ m/Hacknowledge/)  {
  print "ok";
} else {exit;}
  • Il carattere ~ (tilde) identifica l'uso di una regex (!~ per la disuguaglianza, =~ per l'uguaglianza)
  • La prima lettera indica l'azione da eseguire (in questo caso la m sta per matching, e poiché è l'operatore più utilizzato può anche essere omesso)
  • Il carattere / delimita il pattern della regex
  • Le regex sono case sensitive di default (per avere un pattern case insensitive useremo alla fine della regex il carattere i)
  • In questo caso in qualsiasi parte della stringa si trovi la parola, la regex verrà comunque valutata come vera

[modifica] Esempio pratico

Ecco un semplice esempio di una regex che verifica una stringa, determinando quindi la validità di un codice fiscale:

#!/usr/bin/perl
print "Inserisci un codice fiscale"
$codfisc=<STDIN>;
 
if ($codfisc =~ /[A-Z]{6}[0-9]{2}[A-Z][0-9]{2}[A-Z][0-9]{3}[A-Z]/)   {
  print "Codice fiscale valido\n";
} else {
  print "Codice fiscale non valido\n";
}

[modifica] Gruppi di caratteri, negazione e ripetizioni

Con [A-Z] identifichiamo qualsiasi carattere ASCII compreso fra A e Z. G, per esempio, soddisferà quella condizione, mentre g no (il confronto in questo caso è case sensitive). Le parentesi quadre identificano quindi una classe di caratteri, e tutti i caratteri al loro interno soddisfano la regex. Ad esempio

[a-e]

verrà soddisfatta dai caratteri a,b,c,d,e. Invece

[ae]

verrà soddisfatta dai soli caratteri a ed e.

Posso usare questa notazione anche per negare un pattern. Ad esempio

if ($text =~ /[^x]/)

In questo caso il match risulterà verificato per qualsiasi stringa che non contiene la lettera x.

Il {6} dice che il pattern [A-Z] deve essere ripetuto esattamente 6 volte. Fra parentesi quadre specifico quindi un certo pattern quante volte voglio che si ripeta.

Si noti anche l'uso dell'operatore speciale =~, usato per verificare se una stringa coincide con un pattern regex (compreso fra / e /). Se avessi voluto invece di verificare l'uguaglianza verificare la disuguaglianza, avrei dovuto usare l'operatore !~.

Se invece avessi voluto implementare un controllo non case sensitive avrei dovuto specificare alla fine del pattern regex l'operatore i (case insensitive):

if ($codfisc =~ /[A-Z]{6}[0-9]{2}[A-Z][0-9]{2}[A-Z][0-9]{3}[A-Z]/i)   {

[modifica] Caratteri speciali

I seguenti caratteri sono considerati caratteri speciali dalle regex:

\ ( ) [ ] / . * + ?  ^ $ |

Per usarli all'interno di una regex come normali caratteri e non come caratteri speciali della regex, ho bisogno di specificarlo facendoli anticipare dal carattere \. Ad esempio, per verificare se in una stringa è presente un ? dovrò fare una cosa del genere:

if ($text =~ /\?/)  {

[modifica] Iteratori e condizioni

Le regex supportano varie forme di iteratori:

  • * - Il carattere o il gruppo di caratteri che precede può ricorrere da 0 a N volte
  • + - Il carattere o il gruppo di caratteri che precede può ricorrere da 1 a N volte (almeno una volta)
  • ? - Il carattere o il gruppo di caratteri che precede è opzionale
  • {N} - Il carattere o il gruppo di caratteri che precede può ricorrere esattamente N volte
  • {M,N} - Il carattere o il gruppo di caratteri che precede può ricorrere da M a N volte
  • ^ - La stringa inizia con il carattere o il gruppo di caratteri che segue
  • $ - La stringa finisce con il carattere o il gruppo di caratteri che precede
  • . - Carattere speciale. Identifica un carattere qualsiasi
  • | - Identifica un'alternativa fra due caratteri o fra due gruppi di caratteri (corrisponde a un or logico)

Esempi:

if ($text =~ /c*a/)  # ciao verifica la condizione, ma anche bau
if ($text =~ /c+a/)  # caro verifica la condizione, così come ccaro o cccaro, ma non ciao o bau
if ($text =~ /c+.*a/)  # caro verifica la condizione, ma anche ciao
if ($num =~ /[0-9]{2,3}/)  # il pattern identifica un qualsiasi numero da 10 a 999
if ($text =~ /^a/)  # il pattern identifica qualsiasi stringa che inizia con a
if ($text =~ /o$/)  # il pattern identifica qualsiasi stringa che finisce con o
if ($text =~ /.*/)  # identifica qualsiasi cosa
if ($text =~ /ciao|hello/)  # ciaomondo verifica la condizione, ma anche helloworld

[modifica] Combinazioni speciali

Le regex mettono a disposizione le seguenti combinazioni speciali:

  • \s - Identifica un qualsiasi carattere bianco (spazio, tab, \n)
  • \w - Identifica un qualsiasi carattere alfanumerico, compreso l'underscore _
  • \d - Identifica una qualsiasi cifra

[modifica] Caratteri non stampabili

Le regex supportano i seguenti caratteri di escape:

  • \n - new line (ASCII 0x0A)
  • \t - tab (ASCII 0x09)
  • \r - carriage return (ASCII 0x0D)
  • \a - alarm (ASCII 0x07)
  • \e - escape (ASCII 0x1B)
  • \f - form feed (ASCII 0x0C)
  • \v - vertical tab (ASCII 0x0B)
  • \xHH - carattere ASCII (1 byte) in formato esadecimale
  • \uHHHH - carattere Unicode (2 byte) in formato esadecimale

[modifica] Raggruppamenti

Una caratteristica utilissima delle regex è quella di poter anche estrapolare sottostringhe da un testo tramite i raggruppamenti. I raggruppamenti vengono fatti usando le parentesi tonde (). Quando Perl trova un raggruppamento all'interno di un pattern, e il testo soddisfa quel pattern, estrapola tutti i caratteri che rientrano all'interno di quel raggruppamento e li salva all'interno di variabili temporanee. Esempio:

$text="Hello world";
 
if ($text =~ /(\w+)\s(\w+)/)  {
  print "$1,$2\n";
}

L'output sarà

hello,world

Con il primo raggruppamento identifico qualsiasi gruppo di caratteri alfabetici finché non incontro un carattere di spazio. Incontrato quello, parte il secondo raggruppamento di caratteri alfabetici. I due raggruppamenti vengono salvati rispettivamente nelle variabili temporanee $1 e $2, che posso poi gestire come voglio. Altro esempio: regex che controlla se un dato indirizzo email è valido, e in caso affermativo estrapola dal suo interno username e dominio:

if ($mail =~ /([\w\d]+)@([\w\d]+).(\w+)/i)  {
 $user=$1;
 $domain=$2.'.'.$3;
 
 print "Username: $user\nDominio:$domain\n"; 
} else {
 print "$mail non e' un indirizzo valido\n";
}

[modifica] Sostituzioni

Un altro uso estremamente utile delle regex è per effettuare sostituzioni all'interno di un testo. La sintassi è questa:

if ($text =~ s/testo_da_cercare/testo_con_cui_sostituire/)

Esempio, il seguente codice sostituisce tutte le occorrenze di Hello in un testo con Ciao, con un pattern case insensitive:

if ($text =~ s/hello/ciao/i)

Da ricordare l'operatore g. Tale operatore, se usato, sostituisce tutte le occorrenze dell'espressione cercata all'interno del testo, mentre di default viene sostituita solo la prima. Ad esempio:

$str="ciao CIAO baby";
 
if ($str =~ s/ciao/hello/i)  {
        print "$str\n";
}

stamperà "hello CIAO baby" (sostituisce solo la prima occorrenza), mentre

$str="ciao CIAO baby";
 
if ($str =~ s/ciao/hello/ig)  {
        print "$str\n";
}

stamperà "hello hello baby", dato che con l'operatore g sostituisco tutte le occorrenze.

Strumenti personali