perlfaq6 - Espressioni Regolari ($Revision: 1.14 $, $Date: 2006-02-11 03:50:44 $)
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).
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'istruzione s/// 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
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";
}
}
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 $.
}
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;
}
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;
}
Inserite use locale; nel vostro script. La classe di caratteri \w è
presa dal locale corrente.
Consultate perllocale per dettagli.
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_]/ .
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.
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;
}
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;
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.
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.
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
}
}
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)?''
Date un'occhiata al modulo String::Approx disponibile su CPAN.
(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.
(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
(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 partire dal Perl 5.6.1, le variabili speciali @- e @+ possono rimpiazzare come funzionalità
$`, $& e $'. Questi array contengono dei puntatori all'inizio e fine di ogni match (consultate perlvar
per l'intera faccenda), dunque vi danno essenzialmente la stessa informazione ma senza rischiare di
copiare troppe stringhe.
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 C<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.
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/ .
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.
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.
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;
Copyright (c) 1997-2006 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.
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/ .
Traduzione a cura di Michele Beltrame.
Revisione a cura di Michele Beltrame.
Mon Jun 11 22:02:15 2012
|