NOME
perlfaq6 - Espressioni Regolari ($Revision: 1.13 $, $Date: 2005/11/19 00:21:49 $)
DESCRIZIONE
Questa sezione è soprendentemente breve poiché il resto delle FAQ è inquinato da risposte riguardanti le espressioni regolari. Per esempio, la decodifica di una URL oppure il controllo se una stringa è un numero sono operazione gestite tramite espressioni regolari, ma quelle risposte si trovano da altre parti (in perlfaq9: "Come decodifico o creo quei %-codici sul web?" e perlfaq4: "Come si fa a determinare se uno scalare è un numero/naturale/intero/in virgola mobile?", per essere precisi).
Come posso sperare di usare le espressioni regolari senza creare codice illeggibile e non manutenibile?
Ci sono tre tecniche che posso rendere le espressioni regolari manutenibili e comprensibili.
- Commenti all'esterno dell'espressione regolare
-
Descrivete quello che fate e come lo fate, usando i normali commenti del Perl
# trasforma una linea nella prima parola di essa, seguita da # un segno di "due punti" e il numero di caratteri del resto della # linea s/^(\w+)(.*)/ lc($1) . ":" . length($2) /meg;
- Commenti interni all'espressione regolare
-
Il modificatore
/x
fa in modo che gli spazi vengano ignorati all'interno di un'espressione regolare (fatta eccezione per le classi di caratteri), inoltre permette di usare i normali commenti. Come potete immaginare, gli spazi e i commenti aiutano molto./x
vi consente di trasformare questo:s{<(?:[^>'"]*|".*?"|'.*?')+>}{}gs;
in questo:
s{ < # parentesi angolare aperta (?: # raggruppamento senza la creazione di riferimento # all'indietro [^>'"] * # 0 o piu` cose che non sono un >, ne' un ' ne' un " | # o altrimenti ".*?" # una sezione tra doppi apici (match non-greedy) | # o altrimenti '.*?' # una sezione tra apici singoli (match non-greedy) ) + # il tutto una o piu` volte > # parentesi angolare chiusa }{}gsx; # sostituisci con nulla, cioe` cancella
Non è chiaro come un brano di prosa, ma è molto utile per descrivere il significato di ciascuna parte del pattern.
- Delimitatori differenti
-
Benché normalmente pensiamo a pattern delimitati da caratteri
/
, essi possono essere delimitati da quasi qualunque carattere. perlre descrive questo aspetto. Ad esempio, l'istruziones///
usata sopra usa le parentesi come delimitatori. Scegliere un altro delimitatore può evitare la necessità di marcare il carattere affinché sia interpretato letteralmente:s/\/usr\/local/\/usr\/share/g; # cattiva scelta del delimitatore s#/usr/local#/usr/share#g; # scelta migliore
Sto avendo dei problemi nel fare il match su più di una linea. Cosa c'è di sbagliato?
O non avete più di una linea nella stringa che state prendendo in considerazione (verosimilmente), oppure non state usando il modificatore/i corretto nel vostro pattern (forse).
Ci sono molti modi per ottenere dei dati con più linee in una stringa. Se volete che ciò avvenga automaticamente mentre leggete l'input, vorrete impostare $/ (verosimilmente a '' per i paragrafi oppure ad undef
per l'intero file) per darvi la possibilitaà di leggere più di una riga alla volta.
Leggete perlre: vi aiuterà a decidere quale tra /s
e /m
(oppure entrambi) potreste voler usare: /s
permette al punto di includere i ritorni a capo e /m
permette al segno d'omissione (^
, NdT) ed al simbolo del dollaro di effettuare il match in prossimità di un ritorno a capo, non solo alla fine della stringa. Dovete tuttavia assicurarvi di avere veramente una stringa con più linee.
Per esempio, questo programma individua le parole duplicate, anche se esse sono ripartite tra le interruzioni di linea (ma non se lo sono tra le interruzioni dei paragrafi). Per questo esempio non ci serve /s
, perché non stiamo usando il punto in un'espressione regolare che vogliamo attraversi i confini di linea. Non ci serve neppure /m
perché non vogliamo che il segno d'omissione o il simbolo del dollaro effettuino il match in un qualsiasi punto all'interno del record vicino ai ritorni a capo. Ma è imperativo che $/ sia impostato a qualcosa di diverso rispetto al valore predefinito, altrimenti non riusciremo mai a leggere un record composto da più linee.
$/ = ''; # memorizza piu` dell'intero paragrafo, non solo una linea
while ( <> ) {
while ( /\b([\w'-]+)(\s+\1)+\b/gi ) { # la parola inizia con un carattere alfanumerico
print "$1 e` un duplicato, al paragrafo $.\n";
}
}
Qui c'è il codice che trova le frasi che iniziano con "From " ["da", NdT] (che dovrebbe essere storpiato da molti programmi di posta):
$/ = ''; # memorizza piu` dell'intero paragrafo, non solo una linea
while ( <> ) {
while ( /^From /gm ) { # /m fa si` che ^ effettui un match vicino a \n
print "primo from nel paragrafo $.\n";
}
}
Qui c'è il codice che trova ogni cosa tra START [inizio, NdT] ed END [fine, NdT] nel paragrafo:
undef $/; # memorizza l'intero file, non solo una linea od un paragrafo
while ( <> ) {
while ( /START(.*?)END/sgm ) { # /s fa si` che il . attraversi i confini di linea
print "$1\n";
}
}
Come posso tirar fuori le linee tra due pattern che sono loro stessi su linee diverse?
Potete usare l'esotico operatore ..
(documentato in perlop):
perl -ne 'print if /INIZIO/ .. /FINE/' file1 file2 ...
Se volevate il testo e non le linee, avreste dovuto usare:
perl -0777 -ne 'print "$1\n" while /INIZIO(.*?)FINE/gs' file1 file2 ...
Ma se volete occorrenze innestate [inglese ``nested'', NdT] di INIZIO
e FINE
, vi scontrerete con il problema descritto nella domanda contenuta in questa sezione a proposito della ricerca di corrispondenze di testo bilanciato.
Ecco un altro esempio d'uso di ..
:
while (<>) {
$nell_header = 1 .. /^$/;
$nel_corpo = /^$/ .. eof();
# ora scegliete
} continue {
reset if eof(); # mette a posto $.
}
Ho messo un'espressione regolare in $/ ma non ha funzionato. Cosa c'è di sbagliato?
Fino a Perl 5.8.0, $/ deve essere una stringa. Questo potrebbe cambiare nel 5.10, ma non ci sperate. Fino ad allora, potete usare questi esempi se davvero ne avete bisogno.
Se avete File::Stream, questo è semplice.
use File::Stream;
my $stream = File::Stream->new(
$filehandle,
separator => qr/\s*,\s*/,
);
print "$_\n" while <$stream>;
Se non avete File::Stream, dovete fare un po' più di lavoro.
Potete usare la forma a quattro argomenti di sysread per aggiungere ininterrottamente in un buffer. Dopo che avete aggiunto al buffer, controllate se avete una linea completa (usando la vostra espressione regolare).
local $_ = "";
while( sysread FH, $_, 8192, lunghezza ) {
while( s/^((?s).*?)il_vostro_pattern/ ) {
my $record = $1;
# qui vanno le cose da fare.
}
}
Potete fare la stessa cosa con il foreach ed un match usando il flag c e l'anchor \G, se non vi importa che il vostro intero file sia in memoria alla fine.
local $_ = "".
while( sysread FH, $_, 8192, lunghezza ) {
foreach my $record ( m/\G((?s).*?)il_vostro_pattern/gc ) {
# qui vanno le cose da fare.
}
substr( $_, 0, pos ) = "" if pos;
}
Come posso effettuare delle sostituzioni non tenendo conto di maiuscole e minuscole nel lato sinistro e tenendone invece conto nel lato destro?
Ecco una deliziosa soluzione a-la Perl di Larry Rosler. Essa sfrutta le proprietà a livello di bit dello xor sulle stringhe ASCII
$_= "questo e` un TEsT";
$vecchio = 'test';
$nuovo = 'successo';
s{(\Q$vecchio\E)}
{ uc $nuovo | (uc $1 ^ $1) .
(uc(substr $1, -1) ^ substr $1, -1) x
(length($nuovo) - length $1)
}egi;
print;
E qui sotto forma di subroutine, modellata in base a quanto sopra:
sub preserva_maiuscole_minuscole($$) {
my ($vecchio, $nuovo) = @_;
my $maschera = uc $vecchio ^ $vecchio;
uc $nuovo | $maschera .
substr($maschera, -1) x (length($nuovo) - length($vecchio))
}
$a = "questo e` un TEsT";
$a =~ s/(test)/preserva_maiuscole_minuscole($1, "successo")/egi;
print "$a\n";
Questo stampa:
questo e` un SUcCESSO
Come alternativa, per mantenere le maiuscole/minuscole della parola sostituita se è più lunga dell'originale, potete usare questo codice, di Jeff Pinyan:
sub preserva_maiuscole_minuscole {
my ($partenza, $arrivo) = @_;
my ($lp, $la) = map length, @_;
if ($la < $lp) { $partenza = substr $partenza, 0, $la }
else { $partenza .= substr $arrivo, $lp }
return uc $arrivo | ($partenza ^ uc $partenza);
}
Questo cambia la frase in "questo e` un SUcCesso".
Tanto per mostrare che i programmatori C possono scrivere C in un qualunque linguaggio di programmazione, se preferite una soluzione maggiormente in stile C, il seguente script fa sì che le sostituzioni abbiano le stesse maiuscole/minuscole, lettera per lettera, dell'originale (Può anche capitare che questo venga eseguito circa il 240% più lentamente di quello che fa la soluzione a-la Perl). Se la sostituzione ha più caratteri della stringa che si sta sostituendo, le maiuscole/minuscole dell'ultimo carattere sono usate per il resto della sostituzione.
# Originale di Nathan Torkington, rimaneggiato da Jeffrey Friedl
#
sub preserva_miuscole_minuscole($$)
{
my ($vecchio, $nuovo) = @_;
my ($stato) = 0; # 0 = nessun cambiamento; 1 = lc; 2 = uc
my ($i, $lungvecchio, $lungnuovo, $c) = (0, length($vecchio), length($nuovo));
my ($lung) = $lungvecchio < $lungnuovo ? $lungvecchio : $lungnuovo;
for ($i = 0; $i < $lung; $i++) {
if ($c = substr($vecchio, $i, 1), $c =~ /[\W\d_]/) {
$stato = 0;
} elsif (lc $c eq $c) {
substr($nuovo, $i, 1) = lc(substr($nuovo, $i, 1));
$stato = 1;
} else {
substr($nuovo, $i, 1) = uc(substr($nuovo, $i, 1));
$stato = 2;
}
}
# termina con ogni nuovo rimanente (per quando nuovo e` piu` lungo di vecchio)
if ($lungnuovo > $lungvecchio) {
if ($stato == 1) {
substr($nuovo, $lungvecchio) = lc(substr($nuovo, $lungvecchio));
} elsif ($state == 2) {
substr($nuovo, $lungvecchio) = uc(substr($nuovo, $lungvecchio));
}
}
return $nuovo;
}
Come si può far fare a \w
un match del set di caratteri nazionale?
Inserite use locale;
nel vostro script. La classe di caratteri \w è presa dal locale corrente.
Consultate perllocale per dettagli.
Come si può creare una versione localizzata di /[a-zA-Z]/
?
Potete utilizzare la sintassi POSIX per le classi di caratteri /[[:alpha:]]/
, documentata in perlre.
Indipendentemente dal locale in cui vi trovate, i caratteri alfabetici sono i caratteri in \w senza le cifre e l'underscore. A Livello di espressione regolare, ciò significa /[^\W\d_]/
. Il suo complemento, i caratteri non-alfabetici, è rappresentato da tutto ciò che è contenuto in \W con aggiunte le cifre e l'underscore, o /[\W\d_]/
.
Come si può fare il quote di una variabile da usare in una regex?
Il parser del Perl espanderà i riferimenti a $variabile e @variabile in espressioni regolari a meno che il delimitatore sia un singolo quote [un apice, NdT]. Ricordate inoltre che il lato destro di una sostituzione s///
viene considerato come una stringa doppiamente quotata (delimitata da virgolette, NdT) (si veda perlop per maggiori dettagli). Ricordate anche che ogni carattere speciale delle regex avrà affetto, a meno che non si preceda la sostituzione con \Q. Di seguito un esempio:
$stringa = "Placido P. Octopus";
$regex = "P.";
$stringa =~ s/$regex/Polyp/;
# $stringa e` ora "Polypacido P. Octopus"
Siccome .
è un carattere speciale per quanto riguarda le espressioni regolari, e può trovare qualsiasi carattere, la regex P.
qui ha trovato il <Pl> contenuto nella stringa originale.
Per effettuare l'escaping del significato speciale di .
, utilizziamo \Q
:
$stringa = "Placido P. Octopus";
$regex = "P.";
$stringa =~ s/\Q$regex/Polyp/;
# $string e` ora "Placido Polyp Octopus"
L'utilizzo di \Q
fa si che il <.> contenuto nell'espressione regolare sia trattato come un carattere regolare, cosicché P.
possa trovare una P
seguita da un punto.
A cosa serve realmente /o
?
L'uso di una variabile in un match di un'espressione regolare forza una rivalutazione (e probabilmente ricompilazione) ogni qualvolta ci si imbatte nell'espressione regolare. Il modificatore /o
vincola la regex la prima volta che viene usata. Questo avviene sempre in una espressione regolare costante, ed infatti il pattern viene compilato nel formato interno al momento della compilazione dell'intero programma.
L'uso di /o
è irrilevante a meno che l'interpolazione di variabili non venga utilizzata nel pattern e, se è così, il motore delle regex non saprà né si interesserà di eventuali variazione delle variabili dopo la primissima valutazione del pattern.
/o
viene usato spesso per ottenere un ulteriore grado di efficienza, non eseguendo le valutazioni successive quando sapete che la cosa non avrà importanza (perché sapete che le variabili non cambieranno), oppure, più di rado, quando non volete che la regex si accorga se le variabili cambiano.
Per esempio, ecco un programma "paragrep":
$/ = ''; # modalita` paragrafo
$pat = shift;
while (<>) {
print if /$pat/o;
}
Come faccio ad usare un'espressione regolare per togliere da un file i commenti in stile C?
Benché ciò possa effettivamente essere realizzato, è molto più difficile di quanto potreste pensare. Per esempio, questo programma di una sola riga
perl -0777 -pe 's{/\*.*?\*/}{}gs' pippo.c
funzionerà in molti casi, ma non in tutti. Vedete, è troppo ingenuo per certi tipi di programmi C, in particolare quelli che contengono ciò che sembrano essere commenti all'interno di stringhe incluse tra virgolette. Per questo, avreste bisogno di qualcosa come questo, creato da Jeffrey Friedl ed in seguito modificato da Fred Curtis.
$/ = undef;
$_ = <>;
s#/\*[^*]*\*+([^/*][^*]*\*+)*/|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|.[^/"'\\]*)#defined $2 ? $2 : ""#gse;
print;
Questo, naturalmente, potrebbe essere più leggibile con il modificatore /x
, aggiungendovi spazi e commenti. Eccolo espanso, per cortese concessione di Fred Curtis.
s{
/\* ## Inizio di un commento /* ... */
[^*]*\*+ ## Non-* seguito da 1-o-piu` *
(
[^/*][^*]*\*+
)* ## 0-o-piu` cose che non iniziano con /
## ma terminano con '*'
/ ## Fine di un commento of /* ... */
| ## OPPURE diverse cose che non sono commenti:
(
" ## Inizio di una stringa " ... "
(
\\. ## Carattere preceduto da \
| ## OPPURE
[^"\\] ## Non "\
)*
" ## Fine di una stringa " ... "
| ## OPPURE
' ## Inizio di una stringa ' ... '
(
\\. ## Carattere preceduto da \
| ## OPPURE
[^'\\] ## Non '\
)*
' ## Fine di una stringa ' ... '
| ## OPPURE
. ## Qualsiasi altro carattere
[^/"'\\]* ## Caratteri che non sono l'inizio di un commento, non cominciano una stringa, non sono il carattere \
)
}{defined $2 ? $2 : ""}gxse;
Una piccola modifica rimuove anche i commenti C++:
s#/\*[^*]*\*+([^/*][^*]*\*+)*/|//[^\n]*|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|.[^/"'\\]*)#defined $2 ? $2 : ""#gse;
Posso usare le espressioni regolari di Perl per fare il match di testo bilanciato?
Storicamente, le espressioni regolari di Perl non erano in grado di fare il match di testo bilanciato. Dalle più recenti versioni di perl, inclusa la 5.6.1, sono state aggiunte caratteristiche sperimentali che rendono possibile la sua realizzazione. Consultate la documentazione per il costrutto (??{ }) nelle recenti pagine di perlre per vedere un esempio di parentesi bilanciate che eseguono un match. Assicuratevi di prestare speciale attenzione agli avvertimenti presenti nel manuale prima di utilizzare questa caratteristica.
CPAN contiene molti moduli che possono essere utili per effettuare match di testo dipendenti dal contesto. Damian Conway fornisce alcuni utili pattern in Regexp::Common. Il modulo Text::Balanced fornisce una soluzione generale a questo problema.
Una delle comuni applicazioni del match di testo bilanciato è lavorare con XML e HTML. Sono a disposizione molti moduli che supportano queste esigenze. Due esempi sono HTML::Parser e XML::Parser. Ce ne sono molti altri.
Un'elaborata subroutine (esclusivamente per ASCII a 7 bit) per estrarre singoli caratteri bilanciati ed eventualmente annidati, come `
e '
, {
e }
oppure (
e )
, può essere trovata all'indirizzo http://www.cpan.org/authors/id/TOMC/scripts/pull_quotes.gz .
Il modulo C::Scan su CPAN contiene anch'esso tali subroutine per uso interno, ma non sono documentate.
Cosa significa che un'espressione regolare è avida (greedy)? Come posso evitarlo?
La maggior parte delle persone pensano che le espressioni regolari avide effettuino più match possibili. Tecnicamente parlando, sono effettivamente i quantificatori (?
, *
, +
, {}
) ad essere avidi, piuttosto che l'intero pattern; il Perl preferisce un'avidità locale e un'immediata gratificazione rispetto ad una avidità generale. Per ottenere versioni non avide degli stessi quantificatori, usate (??
, *?
, +?
, {}?
).
Un esempio:
$s1 = $s2 = "Ho molto molto freddo";
$s1 =~ s/mo.*o //; # Ho freddo
$s2 =~ s/mo.*?o //; # Ho molto freddo
Notate come la seconda sostituzione abbia fermato il match non appena ha trovato "o ". Il quantificatore *?
in realtà dice al motore delle espressioni regolari di trovare una corrispondenza il più presto possibile e di passare il controllo a qualsiasi cosa si trovi nella linea che segue, come fareste se steste giocando con una patata bollente.
Come faccio ad elaborare ciascuna parola di ogni linea?
Usate la funzione split:
while (<>) {
foreach $parola ( split ) {
# qui fate qualcosa con $parola
}
}
Notate che non si tratta di una parola nel senso della lingua inglese (e nemmeno di quella italiana ovviamente, NdT): è soltanto un raggruppamento di caratteri consecutivi che non sono spazi.
Per lavorare solo con sequenze alfanumeriche (che includono il carattere underscore (``_'', NdT) ), potreste considerare l'utilizzo di
while (<>) {
foreach $parola (m/(\w+)/g) {
# qui fate qualcosa con $parola
}
}
Come posso stampare un sommario in base alla frequenza delle parole oppure delle linee?
Per fare questo, dovete analizzare sintatticamente ogni parola nel flusso di input. Faremo finta che per parola intendiate un gruppo di caratteri alfabetici, trattini o apostrofi, piuttosto che l'idea di parola senza spazi, data nel quesito precedente:
while (<>) {
while ( /(\b[^\W_\d][\w'-]+\b)/g ) { # non prende "`pecora'"
$visti{$1}++;
}
}
while ( ($parola, $conta) = each %visti ) {
print "$conta $parola\n";
}
Se volete fare la stessa cosa per le linee, non dovreste aver bisogno di un'espressione regolare:
while (<>) {
$visti{$_}++;
}
while ( ($linea, $conta) = each %visti ) {
print "$conta $linea";
}
Se volete che l'output sia ordinato, consultate perlfaq4: "Come si ordina un hash (opzionalmente per valore invece che per chiave)?"
Come posso fare un match approssimato?
Date un'occhiata al modulo String::Approx disponibile su CPAN.
Come posso fare, in maniera efficiente, un match di più espressioni regolari alla volta?
(contributo di brian d foy )
Evitando di chiedere al Perl di compilare una espressione regolare ogni qualvolta volete effettuarne il match. In questo esempio, perl deve ricompilare l'espressione regolare per ogni iterazione del ciclo foreach(), visto che non ha modo di conoscere cosa sarà $pattern.
@pattern = qw( foo bar baz );
LINE: while( <> )
{
foreach $pattern ( @pattern )
{
print if /\b$pattern\b/i;
next LINE;
}
}
L'operatore qr// comparì nel perl 5.005. Esso compila una espressione regolare, ma non la usa. Quando utilizzate la versione precompilata della espressione regolare, il perl compie meno lavoro. In questo esempio, si è inserito un map() per convertire ogni pattern nella sua forma precompilata. Il resto dello script è lo stesso, ma più veloce.
@pattern = map { qr/\b$_\b/i } qw( pippo pluto paperino );
LINE: while( <> )
{
foreach $pattern ( @pattern )
{
print if /\b$pattern\b/i;
next LINE;
}
}
In alcuni casi, potreste essere in grado di comporre diversi pattern in una singola espressione regolare. Tuttavia fate attenzione a situazioni che richiedono il backtracking.
$espres_reg = join '|', qw( pippo pluto paperino );
LINE: while( <> )
{
print if /\b(?:espres_reg)\b/i;
}
Per maggiori dettagli sull'efficienza delle espressioni regolari, consultate Mastering Regular Expressions di Jeffrey Freidl. Egli spiega come funziona il motore delle espressioni regolari e perché alcuni pattern sono sorprendentemente inefficienti. Una volta che avete capito come il perl usa le espressioni regolari, potete regolarle per le singole situazioni.
Perché non mi funzionano le ricerche del limite di una parola effettuate con \b
?
(contributo di brian d foy)
Assicuratevi di conoscere cosa significhi davvero \b: è il confine tra un carattere di una parola, \w, e qualcosa che non è il caratter di una parola. Questa cosa che non è il carattere di una parola potrebbe essere \W ma può essere anche l'inizio o la fine della stringa.
Non è il confine tra spazi e non-spazi, e non sono quelle cose tra le parole che usiamo per creare frasi.
Con il gergo delle espressioni regolari, un confine di parola (\b) è una "zero width assertion" [asserzione di ampiezza zero, NdT], che sta a significare che non rappresenta un carattere nella stringa ma una condizione in una certa posizione.
Per l'espressione regolare, /\bPerl\b/, ci deve essere un confine di parola prima della "P" e dopo la "l". Fino a che qualcosa di diverso da un carattere di parola precede la "P" e segue la "l", il pattern effetuerà il match. Queste stringhe effettuano il match con /\bPerl\b/.
"Perl" # nessun carattere di parola prima di P o dopo l
"Perl " # come il precedente (lo spazio non e` un carattere di parola)
"'Perl'" # il carattere ' non e` un carattere di parola)
"Perl's" # nessun carattere di parola prima di P, nessun carattere di parola dopo "l"
Queste stringhe non effettuano il match con /\bPerl\b/.
"Perl_" # _ e` un carattere di parola!
"Perler" # nessun carattere di parola prima di P ma uno dopo l
Tuttavia non dovete usare \b per effettuare il match di parole. Potete cercare i caratteri non-parola con ai lati dei caratterei di parola. Queste stringhe effettuano il match /\b'\b/.
"don't" # il carattere ' ha ai lati "n" e "t"
"qep'a'" # il carattere ' ha ai lati "p" e "a"
Queste stringhe effettueranno il match con /\b'\b/.
"foo'" # non c'e` alcun carattere di parola dopo la non-parola '
Per specificare che non ci dovrebbe essere un confine di parola, potete anche usare il complementare di \b, \B.
Nel patter /\Bam\B/, ci deve essere un carattere di parola prima della "a" e dopo la "m". Queste stringhe effettueranno il match con /\Bam\B/:
"llama" # "am" ha ai lati dei caratteri di parola
"Samuel" # stessa cosa
Queste non stringhe effettueranno il match con /\Bam\B/
"Sam" # non c'e` confine di parola prima di "a", ma ce n'e` uno dopo "m"
"I am Sam" # "am" ha ai lati dei caratteri di non-parola
Perché utilizzare $&, $` o $' rallenta il mio programma?
(contributo di Anno Siegel)
Quando Perl si accorge che vi occorre una di queste variabili, ovunque nel vostro programma, le fornisce in ogni operazione di 'pattern matching' [corrispondenza/ricerca di uno schema, NdT]. Questo significa che su ogni pattern match, l'intera stringa verrà copiata, una parte di essa in $`, un'altra in $& e un'altra in $'. Dunque la penalità è estremamente severa con stringhe lunghe e pattern di cui si ha spesso un match. Se potete, evitate di usare $&, $' e $`, ma se non potete evitarlo, una volta che le avete usate allora usatele quanto volete, perché ne avete già pagato il prezzo. Tenete presente che alcuni algoritmi apprezzano molto l'uso di queste variabili. A partire dalla versione 5.005, la variabile $& non è così "costosa" quanto le altre due.
A cosa serve \G
in un'espressione regolare?
Usate l'anchor \G
per iniziare il prossimo match sulla stessa stringa dove è finito l'ultimo match. Il motore delle espressioni regolari, con questa anchor, non può tralasciare alcun carattere per trovare il prossimo match, dunque \G
è analogo all'anchor dell'inzio di stringa, ^
. L'anchor \G
viene usato tipicamente con il flag g. Esso utilizza il valore di pos() per decidere la posizione da cui far partire il match seguente. Come l'operatore di match che esegue match consecutivi, esso aggiorna pos() con la posizione del successivo carattere dopo l'ultimo match (oppure il primo carattere del match successivo, dipende da come preferite vedere la cosa). Ogni stringa ha il proprio valore di pos().
Supponiamo vogliate fare un match di coppie consecutive di cifre in una stringa come "1122a4" e finire di cercare quando trovate caratteri che non sono cifre. Volete trovare 11
e 22
ma la lettera <a> appare tra 22
e 44
e volete fermarvi alla a
. Una semplice ricerca delle coppie di cifre salta la a
e trova 44
.
$_ = "1122a44";
my @coppie = m/(\d\d)/g; # qw( 11 22 44 )
Se utilizzate l'anchor \G, forzate la ricerca dopo 22
ad iniziare dalla a
. L'espressione regolare non può effettuare alcun match lì, poiché non trova una cifra, e dunque la ricerca successiva fallisce e l'operatore di matching ritorna le coppie che ha trovato in precedenza.
$_ = "1122a44";
my @coppie = m/\G(\d\d)/g; # qw( 11 22 )
Potete anche usare l'anchor \G
in un contesto scalare. Avete ugualmente bisogno del flag g
.
$_ = "1122a44";
while( m/\G(\d\d)/g )
{
print "Trovato $1\n";
}
Dopo che il match fallisce alla lettera a
, il perl reinizializza pos() ed il successivo match sulla stessa stringa parte all'inizio.
$_ = "1122a44";
while( m/\G(\d\d)/g )
{
print "Trovato $1\n";
}
print "Trovato $1 dopo il while" if m/(\d\d)/g; # trova "11"
Potete disabilitare il reinizializzarsi di pos() in caso di fallimento utilizzando il flag c
. Match successivi iniziano dove finisce l'ultimo match che ha avuto successo (il valore di pos()) anche se nel frattempo è fallito un match sulla stessa stringa. In questo caso, il match dopo il ciclo while() inizia alla a (dove si è fermato l'ultimo match) e poiché non usa alcuna anchor, può tralasciare la a
per trovare "44".
$_ = "1122a44";
while( m/\G(\d\d)/gc )
{
print "Trovato $1\n";
}
print "Trovato $1 dopo il while" if m/(\d\d)/g; # trova "44"
Tipicamente si usa l'anchor \G
con il flag c
quando si vuole tentare un match differente se ne fallisce uno, come in un analizzatore lessicale. Jeffrey Friedl offre questo esempio che funziona sul 5.004 o successivi.
while (<>) {
chomp;
ANALIZZATORE_SINTATTICO: {
m/ \G( \d+\b )/gcx && do { print "numero: $1\n"; redo; };
m/ \G( \w+ )/gcx && do { print "parola: $1\n"; redo; };
m/ \G( \s+ )/gcx && do { print "spazio: $1\n"; redo; };
m/ \G( [^\w\d]+ )/gcx && do { print "altro: $1\n"; redo; };
}
}
Per ogni linea, il ciclo dell'ANALIZZATORE_SINTATTICO prima cerca di effettuare il match di una serie di cifre seguite da un confine di parola. Questo match deve iniziare alla posizione dove l'ultimo match ha terminato (oppure all'inizio della stringa nel primo match). Poiché m/ \G( \d+\b )/gcx
utilizza il flag c
, se la stringa non soddisfa l'espressione regolare, il perl non reinizializza pos() ed il match successivo inizia alla stessa posizione per tentare un pattern differente.
Le espressioni regolari del Perl sono DFA o NFA? Sono aderenti allo standard POSIX?
Nonostante sia vero che le espressioni regolari del Perl assomiglino ai DFA (Deterministic Finite Automata, Automa Deterministico a Stati Finiti) del programma egrep(1), in pratica sono implementate come NFA (Non-deterministic Finite Automata, Automa Non-deterministico a Stati Finiti) per consentire il backtracking e il backreferencing (*). E non sono nello stile di POSIX, perché queste garantiscono il comportamento del caso peggiore in tutti i casi (sembra che qualcuno preferisca garanzie di coerenza, anche quando ciò che viene garantito è la lentezza). Consultate il libro "Mastering Regular Expressions" [Padroneggiare le espressioni regolari, NdT] (O'Reilly) di Jeffrey Friedl per tutti i dettagli che potete mai sperare di conoscere sulla questione (il riferimento completo al libro si trova nella pagina perlfaq2).
(*) NdT: il backreferencing è l'utilizzo di riferimenti a subpattern usati in precedenza nella stessa espressione, come in /(d)1/
.
Cosa c'è di sbagliato ad usare grep o map in un contesto vuoto?
Il problema è che grep costruisce una lista, senza badare al contesto. Questo significa che lascerete a Perl tutti i grattacapi relativi alla costruzione di una lista, per poi buttarla via. Se la lista è grande, sprecherete tempo e spazio. Se il vostro obiettivo è quello di iterare nella lista allora usate un ciclo for opportuno.
In perl più vecchi del 5.8.1, anche map soffre di questo problema. Ma a partire da perl 5.8.1 questo problema è stato risolto, e map riconosce il contesto in cui viene utilizzato - in contesto vuoto non viene costruita alcuna lista.
Come posso effettuare il match di stringhe contenenti caratteri multibyte?
A partire da Perl 5.6, il Perl ha avuto un certo livello di supporto ai caratteri multibyte. Si raccomanda il Perl 5.8 o successivo. Le raccolte di caratteri multibyte supportate includono Unicode e le codifiche precedenti (per questioni di compatibilità attraverso il modulo Encode. Consultate perluniintro, perlunicode, ed Encode.
Se siete è fermi alle vecchie versioni di Perl, potete fare Unicode con il modulo Unicode::String
e la conversione di caratteri usando i moduli Unicode::Map8
e Unicode::Map
. Se si stanno usando codici giapponesi, potreste provare ad utilizzare jperl 5.005_03.
Infine, il seguente insieme di approcci viene fornito da Jeffrey Friedl, il cui articolo sul numero #5 di The Perl Journal parla di questo stesso argomento.
Supponiamo abbiate qualche strano codice Marziano nel quale le coppie di lettere maiuscole ASCII codificano le singole lettere Marziane (cioè i due byte "CV" formano una singola lettera Marziana, come pure fanno i due byte "SG", "VS", "XX", ecc.). Gli altri byte rappresentano singoli caratteri, proprio come in ASCII.
Dunque, la stringa in marziano "Io sono CVSGXX!" utilizza 15 byte per codificare i dodici caratteri 'I', 'o', ' ', 's', 'o', 'n', 'o', ' ', 'CV', 'SG', 'XX', '!'.
Ora, diciamo che si vuole cercare il singolo carattere /GX/
. Perl non conosce il Marziano, dunque troverà i due byte "GX" nella stringa "Io sono CVSGXX!", anche se quel carattere non è lì: gli assomiglia solamente perché "SG" è vicino a "XX", ma non c'è alcun vero "GX". Questo è un grosso problema.
Qui di seguito ci sono alcuni modi, tutti gravosi, per occuparsi di ciò:
$marziano =~ s/([A-Z][A-Z])/ $1 /g; # Ci si assicura che i byte
# "marziani" adiacenti
# non siano ancora adiacenti
print "GX trovato!" if $marziano =~ /GX/;
Oppure così:
@caratteri = $marziano =~ m/([A-Z][A-Z]|[^A-Z])/g;
# quanto scritto sopra e` concettualmente
# simile a: @caratteri = $testo =~ m/(.)/g;
#
foreach $carattere (@caratteri) {
print "GX trovato!", last if $carattere eq 'GX';
}
Oppure così:
while ($marziano =~ m/\G([A-Z][A-Z]|.)/gs) {# \G E<egrave> probabilmente inutile
print "GX trovato!\n", last if $1 eq 'GX';
}
Ecco un altro modo, un po' meno ripido, per risolvere il problema, opera di Bejamin Goldberg, che usa una zero-width negative look-behind assertion [asserzione negativa di ampiezza zero con ricerca all'indietro, NdT]
print "GX trovato!\n" if $marziano =~ m/
(?<![A-Z])
(?:[A-Z][A-Z])*?
GX
/x;
Questa soluzione ha successo se il carattere "marziano" GX è contenuto nella stringa, mentre fallisce in tutti gli altri casi. Se non vi garba l'utilizzo di (?!>), una zero-width negative look-behind assertion, potete sostituire (?!<[A-Z]) con (?:^|[^A-Z]).
Questo sistema ha l'effetto collaterale di inserire la cosa sbagliata din $-[0] e $+[0], ma di solito si può aggirare questo problema.
Come faccio a trovare corrispondenze ad un pattern inserito dall'utente?
Beh, se si tratta veramente di un pattern, allora usate semplicemente:
chomp($pattern = <STDIN>);
if ($linea =~ /$pattern/) { }
Alternativamente, poiché non è garantito che quella che l'utente ha inserito sia un'espressione regolare valida, catturate l'eccezione in questo modo:
if (eval { $linea =~ /$pattern/ }) { }
Se quello che volete fare in realtà si riduce alla ricerca di una stringa, e non di un pattern, allora dovreste usare la funzione index(), che è fatta per la ricerca di stringhe, oppure, se non potete fare a meno di usare un pattern dove non serve, assicuratevi di usare \Q
...\E
, documentato in perlre.
$pattern = <STDIN>;
open (FILE, $input) or die "Non posso aprire il file $input: $!";
while (<FILE>) {
print if /\Q$pattern\E/;
}
close FILE;
AUTORE E COPYRIGHT
Copyright (c) 1997-2005 Tom Christiansen, Nathan Torkington e altri autori menzionati. Tutti i diritti riservati.
Questa documentazione è libera; potete ridistribuirla e/o modificarla secondo gli stessi termini applicati al Perl.
Indipendentemente dalle modalità di distribuzione, tutti gli esempi di codice in questo file sono rilasciati al pubblico dominio. Potete, e siete incoraggiati a farlo, utilizzare il presente codice o qualunque forma derivata da esso nei vostri programmi per divertimento o per profitto. Un semplice commento nel codice che dia riconoscimento alle FAQ sarebbe cortese ma non è obbligatorio.
TRADUZIONE
Versione
La versione su cui si basa questa traduzione è ottenibile con:
perl -MPOD2::IT -e print_pod perlfaq6
Per maggiori informazioni sul progetto di traduzione in italiano si veda http://pod2it.sourceforge.net/ .
Traduttore
Traduzione a cura di Michele Beltrame.
Revisore
Revisione a cura di Michele Beltrame.
1 POD Error
The following errors were encountered while parsing the POD:
- Around line 905:
Non-ASCII character seen before =encoding in 'è'. Assuming CP1252