Espressioni Regolari e Matching

La potenza di Perl nell'elaborazione di testi è dovuta al suo uso della espressioni regolari. Un'espressione regolare (spesso abbreviata in regex o regexp) è un pattern NdT: si mantiene la terminologia inglese per i concetti principali, come appunto pattern, matching, ecc. che descrive le caratteristiche di un pezzo di testo. Un motore di espressioni regolari interpreta i pattern e li applica per fare il match su pezzi di testo e modificarli.

La documentazione base di Perl sulle regex include un tutorial (perldoc perlretut), una guida di riferimento (perldoc perlreref) e un trattato completo (perldoc perlre). Il libro Mastering Regular Expressions di Jeffrey Friedl spiega la teoria e i meccanismi alla base del funzionamento delle espressioni regolari. Sebbene padroneggiare completamente le espressioni regolari sia un'impresa piuttosto ardua, anche una conoscenza limitata vi fornisce degli strumenti estremamente potenti.

Letterali

Le regex più semplici sono dei pattern costituiti da una sottostringa:

    my $nome = 'Castellamonte';
    say 'Ho trovato una stella!' if $nome =~ /stella/;

L'operatore di match (m//, abbreviato //) identifica una espressione regolare—in questo esempio, stella. Questo pattern non è una parola. Il suo significato è "il carattere s, seguito dal carattere t, seguito dal carattere e, ecc.". Ogni carattere nel pattern è un elemento indivisibile, o atomo. Può combaciare o non combaciare con un carattere in una stringa.

L'operatore di binding a regex (=~) è un operatore infisso (Posizione) che applica la regex del suo secondo operando a una stringa fornita dal suo primo operando. Quando viene valutato in contesto scalare, esso restituisce vero se il match ha successo. La forma negata dell'operatore di binding (!~) valuta a vero se il match non ha successo.

Ricordatevi index!

Potete anche usare la funzione predefinita index per cercare una sottostringa letterale in una stringa. Usare un motore di espressioni regolari per questo compito è come andare al negozio giù all'angolo a comprare il latte su un elicottero da combattimento—ma Perl vi permette di scegliere la forma che vi sembra più facilmente manutenibile.

L'operatore di sostituzione, s///, è sotto certi aspetti un operatore di raggruppamento (Posizione) con due operandi. Il suo primo operando è un'espressione regolare, di cui si vuole fare il match in una stringa quando è presente anche l'operatore di binding di regex. Il secondo operando è una sottostringa usata per sostituire la porzione di stringa riconosciuta col match. Per esempio, per curare una fastidiosa allergia estiva, potete scrivere:

    my $stato = 'Sto male.';
    $stato    =~ s/male/bene/;
    say $stato;

L'operatore qr// e la Combinazione di Regex

L'operatore qr// crea delle regex come oggetti di prima classe. Per usarle, interpolatele nell'operatore di match:

    my $stella = qr/stella/;
    say 'Ho trovato una stella!' if $nome =~ /$stella/;

... o combinate diversi oggetti regex in dei pattern complessi:

    my $stella = qr/stella/;
    my $monte  = qr/monte/;

    say 'Ho trovato una stella sopra un monte!'
        if $nome =~ /$stella$monte/;

    like( $nome, qr/$stella$monte/,
                   'Ho trovato una stella sopra un monte!' );

Come is, ma con il like di More

La funzione like di Test::More verifica se c'è un match tra il primo argomento e la regex fornita come secondo argumento.

Quantificatori

La potenza delle espressioni regolari è incrementata dall'uso dei quantificatori di regex, che vi permettono di specificare quante volte una componente della regex può comparire in una striga con cui viene fatto il match. Il più semplice è il quantificatore zero o uno, scritto ?:

    my $miao_o_mio = qr/mia?o/;

    like( 'miao', $miao_o_mio, "il match di 'miao' con /mia?o/ ha successo" );
    like( 'mio',  $miao_o_mio, "il match di 'mio' con /mia?o/ ha successo"  );

La presenza di un atomo in una espressione regolare seguito dal carattere ? significa "fai il match con questo atomo zero o una volta". L'espressione regolare qui sopra trova un match se zero o una occorrenza del carattere a precedono immediatamente un carattere o, quindi il match ha successo con le sottostringhe letterali miao e mio.

Il quantificatore uno o più, scritto +, trova un match solo se c'è almeno una occorrenza dell'atomo quantificato:

    my $qualche_a = qr/mia+o/;

    like( 'miao',    $qualche_a, "il match di 'miao' con /mia+o/ ha successo" );
    like( 'miaao',   $qualche_a, "anche quello di 'miaao'"                    );
    like( 'miaaao',  $qualche_a, "anche quello di 'miaaao'"                   );
    like( 'miaaaao', $qualche_a, "anche quello di 'miaaaao'"                  );

    unlike( 'mio',   $qualche_a, "invece quello di 'mio' fallisce" );

In teoria non c'è alcun limite massimo al numero di occorrenze dell'atomo quantificato per cui il match ha successo.

Il quantificatore zero o più, scritto *, trova un match se ci sono zero o più occorrenze dell'atomo quantificato:

    my $qualunque_a = qr/mia*o/;

    like( 'miao',    $qualunque_a, "il match di 'miao' con /mia*o/ ha successo" );
    like( 'miaao',   $qualunque_a, "anche quello di 'miaao'"                    );
    like( 'miaaao',  $qualunque_a, "anche quello di 'miaaao'"                   );
    like( 'miaaaao', $qualunque_a, "anche quello di 'miaaaao'"                  );
    like( 'mio',     $qualunque_a, "e anche quello di 'mio'"                    );

Per quanto ciò possa sembrare curioso, questo operatore vi permette di specificare componenti opzionali di una regex. Ricordatevi di usarlo con parsimonia: è uno strumento costoso e di scarsa precisione. La maggior parte delle espressioni regolari beneficia dell'uso dei quantificatori ? e + molto più che di *. Spesso precisare le vostre intenzioni aumenta la chiarezza.

I quantificatori numerici definiscono un numero specifico di possibili occorrenze di un atomo. {n} significa che il match deve avvenire esattamente n volte.

    # equivalente a qr/miao/;
    my $solo_una_a = qr/mia{1}o/;

    like( 'miao', $solo_una_a, "il match di 'miao' con /mia{1}o/ ha successo" );

{n,} fa il match con un atomo almeno n volte:

    # equivalente a qr/mia+o/;
    my $qualche_a = qr/mia{1,}o/;

    like( 'miao',    $qualche_a, "il match di 'miao' con /mia{1,}o/ ha successo" );
    like( 'miaao',   $qualche_a, "anche quello di 'miaao'"                       );
    like( 'miaaao',  $qualche_a, "anche quello di 'miaaao'"                      );
    like( 'miaaaao', $qualche_a, "anche quello di 'miaaaao'"                     );

{n,m} significa che il match deve avvenire almeno n volte e non più di m volte:

    my $poche_a = qr/mia{1,3}o/;

    like( 'miao',    $poche_a, "il match di 'miao' con /mia{1,3}o/ ha successo" );
    like( 'miaao',   $poche_a, "anche quello di 'miaao'"                        );
    like( 'miaaao',  $poche_a, "anche quello di 'miaaao'"                       );

    unlike( 'miaaaao', $poche_a, "invece quello di 'miaaaao' fallisce" );

Potete esprimere i quantificatori simbolici in termini di quantificatori numerici, ma la maggior parte dei programmi usa i primi molto più spesso dei secondi.

Greediness

I quantificatori + e * sono greedy (NdT avidi, aggressivi), nel senso che tentano di includere nel match il maggior numero possibile di caratteri della stringa in input. Questo punto è particolarmente pericoloso. Considerate un uso naïf del pattern .*, ovvero "zero o più caratteri, a capo escluso":

    # una regex mediocre
    my $pasto_caldo = qr/pasto.*caldo/;

    say 'Ho trovato un pasto caldo!'
        if 'Ti offro un pasto caldo' =~ $pasto_caldo;

    say 'Ho trovato un pasto caldo!'
         if 'l'impasto della torta deve essere caldo' =~ $pasto_caldo;

I quantificatori greedy iniziano con l'includere tutto nel match, e scartano i caratteri uno alla volta, solo quando diventa ovvio che non possono far parte del match.

Potete usare il modificatore di quantificatori ? per rendere parsimoniosi i quantificatori greedy:

    my $per_nulla_avido = qr/pasto.*?caldo/;

Quando elabora un quantificatore non-greedy, il motore di espressioni regolari preferisce il più corto dei match potenziali e aggiunge altri caratteri a quelli già identificati dal pattern .*? solo se con quelli correnti il match fallirebbe. Dato che * accetta zero o più occorrenze, potenzialmente il numero minimo di caratteri con cui il pattern d'esempio fa il match è zero:

    say 'Ho trovato un pasto caldo'
    if 'vorreiunpastocaldo' =~ /$per_nulla_avido/;

Usate +? per fare il match di una o più occorrenze in modo non-greedy:

    my $per_nulla_avido_bis = qr/pasto.+?caldo/;

    unlike( 'vorreiunpastocaldo', $per_nulla_avido_bis );

    like( 'vorrei un pasto caldo', $per_nulla_avido_bis );

Il modificatore di quantificatori ? si applica anche al quantificatore ? (zero o un match) e ai quantificatori numerici. In ogni caso, il suo effetto è quello di fare sì che la regex includa nel match il minor numero possibile di caratteri di input.

I pattern greedy .+ e .* possono essere allettanti, ma sono pericolosi. Un appassionato di cruciverba che deve riempire le quattro caselle del 7 Verticale ("La regina dei fiori") riceverà un sacco di proposte sbagliate con il pattern:

    my $sette_verticale   = qr/r$solo_lettere*a/;

Dovrebbe scartare Armenia, Bretagna e Brasilia molto prima che il programma suggerisca rosa. Non solo queste parole sono troppo lunghe, ma il match inizia in mezzo ad esse. Una comprensione di prima mano del funzionamento della greediness può aiutare, ma non può sostituire l'esperienza acquisita con un gran numero di test su dati reali.

Ancore di Regex

Le ancore di regex forzano il motore di regex a iniziare o terminare un match in una posizione assoluta. L'ancora di inizio stringa (\A) prescrive che l'inizio del match coincida con l'inizio della stringa:

    # riconosce anche "ricarica", "relax" e "ripartire"
    my $sette_verticale = qr/\Ar${solo_lettere}{2}a/;

L'ancora di fine linea (\Z) richiede che la fine del match coincida con la fine di una linea nella stringa.

    # riconosce anche "rara", ma e` sicuramente un bel passo avanti
    my $sette_verticale = qr/\Ar${solo_lettere}{2}a\Z/;

L'ancora di limite parola (\b) trova un match solo al limite tra un carattere di una parola (\w) e un carattere che non appartiene alla parola (\W). Usate una regex con questa ancora per riconoscere rosa e scartare invece carica:

    my $sette_verticale = qr/\br${solo_lettere}{2}a\b/;

Metacaratteri

Perl interpreta alcuni caratteri nelle espressioni regolari come metacaratteri, ovvero caratteri che rappresentano qualcosa di diverso dalla loro interpretazione letterale. I metacaratteri danno a chi sa usare le regex una potenza che va ben al di là del match di sottostringhe. Il motore di regex tratta tutti i metacaratteri come atomi.

Il metacarattere . significa "fai il match con qualunque carattere eccetto il carattere di a capo". Ricordate l'avvertimento dato sopra; molti principianti lo dimenticano. Una semplice ricerca con regex—che ignora le migliorie possibili con l'uso delle ancore—per il 7 Vericale potrebbe essere /r..a/. Naturalmente, ci sono sempre diversi modi di ottenere la risposta corretta:

    for my $parola (@parole)
    {
        next unless length( $parola ) == 4;
        next unless $parola =~ /r..a/;
        say "Potrebbe essere: $parola";
    }

Se le potenziali soluzioni in @parole non contengono solo delle semplici parole in italiano, potreste ottenere dei falsi positivi. Infatti, . riconosce anche la punteggiatura, la spaziatura e i numeri. Siate specifici! Il metacarattere \w rappresenta tutti i caratteri alfanumerici (Stringhe e Unicode) e il carattere di sottolineatura:

        next unless $parola =~ /r\w\wa/;

Il metacarattere \d riconosce le cifre (sempre all'interno di Unicode):

    # un riconoscitore di numeri di carta di credito non molto robusto
    next unless $numero =~ /\d{4}-\d{4}-\d{4}-\d{4}/;
    say "Il tuo numero di carta di credito e`: $numero";

Usate il metacarattere \s per riconoscere la spaziatura, che si tratti di un semplice carattere di spazio, di un carattere di tabulazione, di un carriage return, di un form-feed o di un a capo:

    my $due_parole_di_tre_lettere = qr/\w{3}\s\w{3}/;

Metacaratteri Negati

Questi metacaratteri hanno delle corrispondenti forme negate. Usate \W per riconoscere qualunque carattere eccetto un carattere di parola. Usate \D per riconoscere un carattere che non sia una cifra. Usate \S per riconoscere qualunque cosa tranne un carattere di spaziatura. Usate \B per iniziare il match in qualunque posizione eccetto che ai limiti di una parola.

Classi di Caratteri

Quando nessuno dei metacaratteri descritti sopra è sufficientemente specifico, potete definire la vostra classe di caratteri racchiudendoli tra parentesi quadre:

    my $vocali_ascii   = qr/[aeiou]/;
    my $forse_un_gatto = qr/g${vocali_ascii}tto/;

Attenzione all'Interpolazione

Senza l'uso delle parentesi graffe, il parser Perl interpreterebbe il nome di variabile come $vocali_asciitto, il che potrebbe causare un errore di compilazione relativo all'uso di una variabile sconosciuta oppure interpolare il contenuto di una variabile esistente $vocali_asciitto nella regex.

Il carattere trattino (-) vi permette di specificare una serie di caratteri contigui in una classe, come in questa regex $solo_lettere_ascii:

    my $solo_lettere_ascii = qr/[a-zA-Z]/;

Per includere il trattino in una classe, posizionatelo all'inizio o alla fine:

    my $punteggiatura_utile = qr/[-!?]/;

... o fatene l'escape:

    my $caratteri_con_lineette = qr/[|=\-_]/;

Usate l'accento circonflesso (^) come primo elemento di una classe di caratteri per specificare "qualunque cosa eccetto questi caratteri":

    my $non_una_vocale_ascii = qr/[^aeiou]/;

Metacaratteri nelle Classi di Caratteri

Usate un accento circonflesso in qualunque posizione eccetto la prima per includerlo in una classe di caratteri. Per includere un trattino in una classe di caratteri negata, posizionatelo subito dopo l'accento circonflesso o alla fine, o fatene l'escape.

Cattura

Le espressioni regolari vi permettono di raggruppare e catturare parti del match per usarle in seguito. Per estrarre un numero telefonico americano nella forma (202) 456-1111 da una stringa:

    my $prefisso          = qr/\(\d{3}\)/;
    my $numero_locale     = qr/\d{3}-?\d{4}/;
    my $numero_telefonico = qr/$prefisso\s?$numero_locale/;

Notate in particolare l'escape delle parentesi dentro al $prefisso. Le parentesi sono caratteri speciali nelle espressioni regolari di Perl 5. Esse raggruppano degli atomi in unità più grandi e servono anche a catturare delle porzioni della stringa con cui si fa il match. Per riconoscere le parentesi in modo letterale, fatene l'escape con il backslash come è stato fatto in $prefisso.

Catture con Nome

Perl 5.10 ha introdotto le catture con nome, che vi permettono di catturare porzioni di match durante l'applicazione di un'espressione regolare e accedervi in seguito; ad esempio, potete catturare un numero telefonico in una stringa contenente informazioni su un vostro contatto:

    if ($info_contatto =~ /(?<tel>$numero_telefonico)/)
    {
        say "Ho trovato un numero telefonico $+{tel}";
    }

Le regex possono sembrare un'accozzaglia di segni di punteggiatura se non ne raggruppate le diverse parti in pezzi separati. La sintassi delle catture con nome è la seguente:

    (?<nome cattura> ... )

Le parentesi tonde racchiudono la cattura. Il costrutto ?< nome >, che segue immediatamente la parentesi aperta, assegna un nome alla cattura. Il resto della cattura è un'espressione regolare.

Quando il match di un pattern contenente la cattura ha successo, Perl memorizza la parte di stringa che corrisponde al pattern interno alla cattura nella variabile globale predefinita %+. In tale hash, la chiave è il nome della cattura e il valore è la rispettiva porzione di stringa.

Catture Numerate

Perl supporta le catture numerate dall'alba dei tempi:

    if ($info_contatto =~ /($numero_telefonico)/)
    {
        say "Ho trovato un numero telefonico $1";
    }

Questa forma di catture non fornisce un nome identificativo e non usa %+ per la memorizzazione. Invece, le sottostringhe catturate vengono memorizzate da Perl in una serie di variabili globali predefinite. La parte di match della prima cattura trovata finisce in $1, la seconda in $2 e così via. Il conteggio del numero di una cattura avviene quando si incontra la sua parentesi aperta; quindi dopo la prima parentesi aperta inizia la memorizzazione in $1, dopo la seconda in $2 e così via.

Anche se la sintassi per le catture con nome è più verbosa di quella per le catture numerate, in genere risulta più chiara. Contare le parentesi aperte è un lavoro noioso e la complessità di combinare diverse regex ognuna delle quali contiene delle catture numerate diventa proibitiva. Le catture con nome aumentano il grado di manutenibilità delle regex—sebbene ci possano essere collisioni di nome, esse sono relativamente rare. Per minimizzare i rischi, usate le catture con nome solo nelle regex più esterne.

In contesto lista, il match di una regex restituisce una lista di sottostringhe catturate:

    if (my ($numero) = $info_contatto =~ /($numero_telefonico)/)
    {
        say "Ho trovato un numero telefonico $numero";
    }

Le catture numerate sono utili anche per semplici sostituzioni in cui le catture con nome risulterebbero troppo verbose:

    my $ordine = 'Pasta per vegani!';

    $ordine =~ s/(\w+) per vegani/$1 per vegetariani/;
    # oppure
    $ordine =~ s/(?<cibo>\w+) per vegani/$+{cibo} per vegetariani/;

Raggruppamento e Alternativa

In tutti gli esempi precedenti i quantificatori erano applicati a semplici atomi. In realtà è possibile applicarli a qualunque elemento di una regex:

    my $maiale  = qr/maiale/;
    my $fagioli = qr/fagioli/;

    like( 'maiale con fagioli', qr/\A$maiale?.*?$fagioli/,
         'forse maiale, sicuramente fagioli' );

Se espandete manualmente la regex, il risultato potrebbe sorprendervi:

    my $maiale_e_fagioli = qr/\Amaiale?.*fagioli/;

    like( 'maiale con fagioli', qr/$maiale_e_fagioli/,
        'forse maiale, sicuramente fagioli' );
    like( 'mai fagioli', qr/$maiale_e_fagioli/,
         'un momento... non c'e` il fillochinone!' );

A volte essere specifici migliora l'accuratezza dei pattern:

    my $maiale  = qr/maiale/;
    my $con     = qr/con/;
    my $fagioli = qr/fagioli/;

    like( 'maiale con fagioli', qr/\A$maiale? $con? $fagioli/,
        'forse maiale, forse con, sicuramente fagioli' );

Alcune regex devono riconoscere una cosa oppure un'altra. Il metacarattere da usare in questi casi è quello di alternativa (|):

    my $riso    = qr/riso/;
    my $fagioli = qr/fagioli/;

    like( 'riso',    qr/$riso|$fagioli/, 'Ho trovato il riso'  );
    like( 'fagioli', qr/$riso|$fagioli/, 'Ho trovato i fagioli' );

Il metacarattere di alternativa indica che il match deve essere fatto con l'uno o l'altro dei frammenti adiacenti. Ricordate che l'alternativa ha una precedenza (Precedenza) minore delle sequenze di atomi:

    like(   'riso',    qr/riso|fagioli/, 'Ho trovato il riso'   );
    like(   'fagioli', qr/riso|fagioli/, 'Ho trovato i fagioli' );
    unlike( 'risf',    qr/riso|fagioli/, 'Ho trovato un ibrido' );

Anche se viene naturale interpretare riso|fagioli come la stringa ris, seguita dal carattere o oppure f, seguito da agioli, le alternative includono sempre l'intero frammento fino al delimitatore di regex più vicino, che può essere l'inizio o la fine del pattern, una parentesi di raggruppamento, un altro carattere di alternativa oppure una parentesi quadra.

Per ridurre la possibilità di confusione, usate delle variabili per dare dei nomi ai frammenti ($riso|$fagioli) oppure raggruppate le possibili alternative in gruppi non-catturanti:

    my $contiene_amido = qr/(?:pasta|patate|riso)/;

La sequenza (?:) raggruppa una serie di atomi senza definire una cattura.

Non Catturata Per Proteggere Voi

Una espressione regolare convertita in stringa viene automaticamente racchiusa in un raggruppamento non-catturante; qr/riso|fagioli/ diventa la stringa (?:riso|fagioli).

Altre Sequenze di Escape

Per fare il match letterale di un metacarattere, fatene l'escape con un backslash (\). L'avete visto fare negli esempi sopra, dove \( si riferiva a una parentesi tonda aperta e \] si riferiva a una parentesi quadra chiusa. \. si riferisce al carattere letterale punto anziché all'atomo "fai il match con qualunque cosa eccetto un carattere di a capo esplicito".

È probabile che vi succeda di dover fare l'escape del metacarattere di alternativa (|), del metacarattere di fine linea ($) e dei quantificatori (+, ?, *).

I caratteri di disattivazione dei metacaratteri (\Q e \E) disattivano l'interpretazione dei metacaratteri nella parte di regex che si trova tra di essi. Possono essere particolarmente utili quando ricevete l'espressione regolare da una sorgente sulla quale avete il controllo quando scrivete il programma:

    my ($testo, $testo_letterale) = @_;

    return $testo =~ /\Q$testo_letterale\E/;

Il parametro $testo_letterale potrebbe contenere qualunque cosa—per esempio la stringa ** ALLARME **. Nel frammento delimitato da \Q e \E, Perl interpreta la regex come \*\* ALLARME \*\* e quindi tenta di riconoscere letteralmente dei caratteri asterisco, anziché applicare dei quantificatori greedy.

Sicurezza Delle Regex

Siate prudenti quando elaborate delle espressioni regolari provenienti da dell'input utente non attendibile. Un esperto di regex malintenzionato potrebbe provocare un attacco di denial-of-service contro il vostro programma.

Asserzioni

Le ancore di regex come \A, \b, \B e \Z sono una forma di asserzione di regex che richiede alla stringa di soddisfare certe condizioni. Queste asserzioni non fanno il match di singoli caratteri della stringa. Indipendentemente dal contenuto della stringa, il match della regex qr/\A/ avrà sempre successo.

Le asserzioni di lunghezza zero fanno il match di un pattern. La loro peculiarità è che non consumano la porzione della stringa che riconoscono. Per esempio, se vi serve trovare un'auto e nulla di più, potete usare un'asserzione di limite di parola:

    my $auto_solitaria = qr/auto\b/;

... ma se volete trovare un'auto senza avere problemi con la polizia, potreste usare un'asserzione di lunghezza zero con negazione in avanti:

    my $auto_sicura = qr/auto(?!velox)/;

Il costrutto (?!...) riconosce la stringa auto solo se non è seguita immediatamente dalla stringa velox.

L'asserzione di lunghezza zero in avanti:

    my $auto_problematica = qr/auto(?=velox)/;

... riconosce la stringa auto solo se è seguita immediatamente dalla stringa velox. Sebbene una normale espressione regolare possa ottenere lo stesso risultato, considerate una regex per trovare tutte le parole del dizionario che iniziano con auto e vi evitano guai con la polizia:

    my $auto_sicura = qr/auto(?!velox)/;

    while (<$parole>)
    {
        chomp;
        next unless /\A(?<auto>$auto_sicura.*)\Z/;
        say "Trovata un'auto sicura '$+{auto}'";
    }

L'asserzione di lunghezza zero non consuma alcun carattere della stringa in esame, lasciando tutto il match al frammento ancorato <.*\Z>. Altrimenti, la parte catturata consisterebbe solo nella porzione auto della stringa in esame.

Per asserire che la vostra auto non occorre all'inizio di una linea, potete usare l'asserzione di lunghezza zero con negazione all'indietro. Le asserzioni di questo tipo devono avere lunghezza prefissata; quindi, non potete usare dei quantificatori:

    my $auto_media = qr/(?<!\A)auto/;

Il costrutto (?<!...) contiene un pattern di lunghezza fissa. Potete anche richiedere che l'auto si trovi sempre dopo un carattere di spazio con un'asserzione di lunghezza zero all'indietro:

    my $auto_spaziale = qr/(?<=\s)auto/;

Il costrutto (?<=...) contiene un pattern di lunghezza fissa. Questo approccio può esservi utile quando combinate un match globale di regex con il modificatore \G, ma è improbabile che vi troviate spesso ad usare questa funzionalità avanzata.

Una nuova funzionalità delle regex di Perl 5 è l'asserzione keep \K. Questa asserzione di lunghezza zero all'indietro può essere associata a un pattern di lunghezza variabile:

    my $auto_spaziale = qr/\s+\Kauto/;

    like( 'la mia auto e` andata nello spazio', $auto_spaziale );
    like( 'la  mia  auto  e`  andata  nello  spazio  doppio',
         $auto_spaziale );

\K è particolarmente utile per certe sostituzioni che rimuovono la parte finale di un pattern:

    my $esclamazione = 'Ho visto l\'autovelox!';
    $esclamazione    =~ s/auto\K\w+!/./;

    like( $esclamazione, qr/\bauto\./,
                          "Forse non c'era da preoccuparsi tanto!" );

Modificatori di Regex

Potete utilizzare svariati modificatori per cambiare il comportamento degli operatori delle espressioni regolari. Tali modificatori compaiono alla fine degli operatori di match, di sostituzione e qr//. Per esempio, per attivare l'insensibilità tra maiuscole e minuscole in un match:

    my $fiore = 'CaMeLiA';

    like( $fiore, qr/Camelia/,  'Che bel fiore!'  );
    like( $fiore, qr/Camelia/i, 'tasto shift fuori uso' );

Il primo like() fallisce, perché le due stringhe contengono lettere diverse. Invece il secondo like() ha successo, perché il modificatore /i fa sì che la regex ignori la distinzione tra maiuscole e minuscole. Nella seconda regex, M e m sono considerate equivalenti grazie al modificatore.

Potete anche incorporare i modificatori di regex in un pattern:

    my $trova_un_gatto = qr/(?<felino>(?i)gatto)/;

La sintassi (?i) attiva l'insensibilità tra maiuscole e minuscole solo nel raggruppamento in cui è racchiuso: in questo caso, una cattura con nome. Potete attivare diversi modificatori in questo modo. Per disattivare degli specifici modificatori fateli precedere dal carattere trattino (-):

    my $trova_un_razionale = qr/(?<numero>(?-i)Raz)/;

Il modificatore di multilinea, /m, permette alle ancore \A e \Z di fare un match ad ogni occorrenza del carattere di a capo contenuta nella stringa.

Il modificatore /s tratta la stringa in esame come un'unica linea, quindi il metacarattere . riconosce anche il carattere di a capo. Damian Conway suggerisce un espediente mnemonico per cui /m modifica il comportamento di molti metacaratteri nella regex, mentre /s modifica il comportamento di un singolo metacarattere.

Il modificatore /r fa sì che un'operazione di sostituzione restituisca il risultato della sostituzione, lasciando inalterata la stringa originale. Se la sostituzione ha successo, il risultato è una copia modificata dell'originale. Se la sostituzione fallisce (perché il pattern non viene riconosciuto), il risultato è una copia identica all'originale:

    my $stato      = 'Sono affamato di crostata.';
    my $nuovostato = $stato =~ s/crostata/torta/r;
    my $statocopia = $stato
                   =~ s/fegato e cipolle/wurstel/r;

    is( $stato, 'Sono affamato di crostata.',
        'la stringa originale dovrebbe essere inalterata' );

    like( $nuovostato,   qr/torta/,   'voglio la torta'  );
    unlike( $statocopia, qr/wurstel/, 'niente insaccati' );

Il modificatore /x vi permette di inserire spaziatura e commenti aggiuntivi nei pattern. Quando questo modificatore è attivo, il motore di regex ignora la spaziatura e i commenti. Spesso la regex risultante è molto più leggibile:

    my $regex_attrib = qr{
        \A                    # inizio linea

        (?:
          [;\n\s]*            # spaziatura e punto e virgola
          (?:/\*.*?\*/)?      # commenti C
        )*

        ATTR

        \s+
        (   U?INTVAL
          | FLOATVAL
          | STRING\s+\*
        )
    }x;

Questa regex non è per nulla semplice, ma i commenti e la spaziatura migliorano la sua leggibilità. Anche se componete una regex da frammenti precompilati, il modificatore /x può comunque migliorare il vosto codice.

Il modificatore /g fa il match di una regex globalmente nell'intera stringa. Il suo uso ha senso nelle sostituzioni:

    # un po' di pace in Via col Vento
    my $contenuto = slurp( $file );
    $contenuto    =~ s/Rossella O'Hara/Camomilla Calmante/g;

Quando è usato con un match—e non una sostituzione—il metacarattere \G vi permette di elaborare una stringa un pezzo alla volta all'interno di un ciclo. Il match di \G ha successo alla posizione dove è terminato il match precedente più recente. Per memorizzare in modo ordinato i numeri telefonici americani contenuti in un file poco strutturato, potreste scrivere:

    while ($contenuto =~ /\G(\w{3})(\w{3})(\w{4})/g)
    {
        push @numeri, "($1) $2-$3";
    }

Ricordate che l'ancora \G riprende all'ultima posizione della stringa riconosciuta dal match dell'iterazione precedente. Se il match precedente terminava con un quantificatore greedy come .*, il match successivo avrà a disposizione una parte più ridotta di stringa. Anche le asserzioni in avanti possono esservi utili.

Il modificatore /e vi permette di scrivere codice Perl 5 arbitrario nella parte destra di un'operazione di sostituzione. Se il match ha successo, il motore di regex esegue il codice e usa il valore restituito come valore per la sostituzione. L'esempio di sostituzione globale visto prima potrebbe essere reso più semplice con del codice come quello che segue:

    # un po' di pace in Via col Vento
    $sequel  =~ s{Rossella( O'Hara)?}
                 {
                    'Camomilla' . defined $1
                                ? ' Calmante'
                                : ''
                 }ge;

Ogni occorrenza aggiuntiva del modificatore /e provoca un'altra valutazione del risultato dell'espressione, ma solo gli appassionati di golf in Perl si spingono oltre l'uso di /ee.

Matching Intelligente

L'operatore di match intelligente, ~~, confronta due operandi e restituisce un valore vero se corrispondono. L'indeterminatezza della definizione dimostra l'intelligenza dell'operatore: il tipo di confronto dipende dai tipi di entrambi gli operandi. given (Given/When) effettua implicitamente un match intelligente.

L'operatore di match intelligente è un operatore infisso:

    say 'Concordano (per qualche ragione)' if $operandos ~~ $operandod;

Il tipo di confronto dipende generalmente in primo luogo dal tipo dell'operando destro e in secondo luogo dell'operando sinistro. Per esempio, se l'operando destro è uno scalare con una componente numerica, il confronto sarà basato sull'uguaglianza numerica. Se l'operando destro è una regex, il confronto userà un grep o un match di pattern. Se l'operando destro è un array, il confronto effettuerà un grep o un match intelligente ricorsivo. Se l'operando destro è un hash, il confronto verificherà l'esistenza di una o più chiavi. Una tabella di dimensioni scoraggianti in perldoc perlsyn fornisce molti più dettagli sui confronti che il match intelligente può effettuare.

Una proposta importante per la versione 5.16 suggerisce di semplificare il match intelligente in modo sostanziale. Più i vostri operandi sono complicati, più è probabile che i risultati siano poco chiari. Se evitate di confrontare oggetti e vi limitate a semplici operazioni con scalari o al più tra uno scalare e un aggregato, otterrete risultati migliori.

Ciò detto, il match intelligente può essere utile:

    my ($x, $y) = (10, 20);
    say 'Numericamente diversi' unless $x ~~ $y;

    my $z = '10 piccoli indiani';
    say 'Pseudo-numericamente uguali' if $x ~~ $z;

    # match di espressione regolare
    my $ago = qr/ago/;

    say 'Match di pattern' if 'ago' ~~ $ago;

    say 'Grep su array' if @pagliaio ~~ $ago;

    say 'Grep su chiavi di hash' if %hashiago ~~ $ago;

    say 'Grep su array' if $ago ~~ @pagliaio;

    say 'Gli elementi dell'array esistono come chiavi di hash'
         if %hashiago    ~~ @pagliaio;

    say 'Match intelligente tra elementi di array' if @mucchio_di_paglia ~~ @pagliaio;

    say 'Grep su chiavi di hash' if $ago ~~ %hashiago;

    say 'Gli elementi dell'array esistono come chiavi di hash' 
        if @pagliaio  ~~ %hashiago;

    say 'Chiavi di hash identiche' if %hashiago ~~ %mappago;

Il match intelligente funziona anche quando uno degli operandi è un riferimento a uno dei tipi di dati elencati:

    say 'Chiavi di hash identiche' if %hashiago ~~ \%hashiago;