Torna indietro   Hardware Upgrade Forum > Software > Programmazione > Corsi, Tutorial e FAQ

Black Friday 2020! Siamo ''live'' con tutti gli sconti
Black Friday 2020! Siamo ''live'' con tutti gli sconti
Il Black Friday è arrivato e oggi è il vero giorno degli sconti. Abbiamo deciso di seguire ancora più da vicino tutte le offerte, gli sconti e le promozioni che Amazon ma anche gli altri store online realizzeranno in questa giornata. Un articolo in aggiornamento continuo che vi permetterà di avere sempre a vista le offerte per il Black Friday 2020.
Watch Dogs Legion: Ray Tracing e prestazioni con 15 schede video
Watch Dogs Legion: Ray Tracing e prestazioni con 15 schede video
Con Watch Dogs: Legion, NVIDIA e Ubisoft portano avanti la loro collaborazione che ormai perdura da qualche anno. In particolare, il nuovo titolo a mondo aperto supporta riflessi in Ray Tracing e DLSS che, tramite tecnologie di intelligenza artificiale, gestisce il super sampling di bordi e texture per migliorare la qualità e le prestazioni
Tophost, la soluzione ideale (e conveniente) per Wordpress
Tophost, la soluzione ideale (e conveniente) per Wordpress
Le offerte di Tophost spaziano dalle formule più semplici, pensate per chi vuole realizzare i primi siti amatoriali, ad altre dedicate più complete, che includono tutti gli strumenti necessari per mettere in piedi un e-commerce in pochi clic
Tutti gli articoli Tutte le news

Vai al Forum
Rispondi
 
Strumenti
Old 15-06-2010, 22:46   #1
MaxArt
Senior Member
 
L'Avatar di MaxArt
 
Iscritto dal: Apr 2004
Città: Livorno
Messaggi: 6602
[Tutorial] Regular Expressions

Regular Expressions
Questi oscuri garbugli di caratteri...



Sommario

Prima parte
Il classico problema
___Le regular expressions, come e quando
Le basi ed i primi esempi
___Sequenze speciali
___Quantificatori
___Alternanze e raggruppamenti
___Posizionamenti
___Fare a pezzi la stringa trovata!
___Backreferences
Modificatori (flags)
Sostituzioni
Dove posso provare le mie espressioni regolari?

Seconda parte
Espressioni regolari avanzate
___Lookarounds
___Quantificatori avidi, possisivi e svogliati
___Espressioni atomiche
___Catastrofe!
___Trucchi e funzionalità vari
Differenze con le RE tra i linguaggi
Links e riferimenti


Il classico problema
Vi sarà capitato, programmando, di dover effetuare delle ricerche all'interno di una stringa di una particolare sottostringa. Pratica comunissima, tanto che pressoché tutti i linguaggi di programmazione mettono a disposizione gli strumenti per farlo in maniera semplice e concisa.
Tuttavia, potrebbe anche esservi capitato di non dover ricercare una stringa precisa, ma qualcosa di più vago. Potrebbe anche essere semplicemente che si voglia cercare "pippo" all'interno di una stringa, ma che possa anche andare bene "Pippo", "PIPPO" o "pIpPo": normalmente, si converte tutta la stringa in minuscolo (o in maiuscolo) con le apposite funzioni e poi si cerca solo "pippo" (o "PIPPO").
Le cose si complicano se si vuole invece cercare una sottostringa tipo "Pippo ha XX anni", dove XX può essere un qualsiasi numero non negativo, nel qual caso si dovrebbe, nell'ordine,
  1. cercare la stringa "Pippo ha ";
  2. verificare che i caratteri successivi prima dello spazio successivo siano effettivamente cifre decimali;
  3. verificare che siano seguite dalla stringa " anni".
Le cose, allora, diventano complesse perché si vogliono evitare che casi come "Pippo ha corso fino a casa" o "Pippo ha 3 gatti" risultino in un "falso positivo". Il codice da scrivere si complica di conseguenza.

Non parliamo, poi, di dover andare a cercare cose come "Pippo", "PIPPO" ma non "pIPPO", oppure una data, o peggio un codice fiscale, se non, addirittura, un tag X/HTML ().
Ecco allora che vengono in nostro soccorso le Regular Expressions, che possono fare tutto questo e molto, molto di più.


Le regular expressions, come e quando
Le regular expressions (in italiano espressioni regolari, e spesso abbreviato in molti linguaggi come RegExp o RegEx) sono un concetto matematico sviluppato già negli anni '40, e sebbene la mia indole di matematico frema per illustrarvene i concetti, devo astenermi perché, a livello di programmazione, hanno utilità prossima allo zero Per chi si intende un po' di logica e di teoria degli insiemi la voce della Wikipedia relativa offre qualche spunto: http://it.wikipedia.org/wiki/Espressione_regolare
Le espressioni regolari (da ora in poi, RE) sono un aiuto importante per mantenere snello e compatto il proprio codice, riducendo ad una sola riga quel che normalmente avremmo fatto magari in decine di righe. I più comuni linguaggi di programmazione offrono pacchetti spesso molto completi e di facile reperibilità per le RE, quando non proprio forniti con le più comuni distribuzioni; alcuni linguaggi hanno addirittura supporto nativo alle RE (Javascript, Perl, PHP, Actionscript 3).
Non solo: spesso, molti editor di testo nonché ambienti di sviluppo offrono la possibilità di ricerca e sostituzione del testo tramite RE, e questo può essere uno strumento estremamente utile per risparmiarsi laboriose azioni magari fatte a mano.
Ma, se da una parte abbiamo uno strumento potente e flessibile subito a disposizione, dall'altra ci sono da pagare due scotti principali:
  1. condensando in pochi caratteri diverse righe di codice, il programma può risentirne parecchio in leggibilità e capacità di poterci intervenire per modifiche future (non è infatti raro che, con la prospettiva di dover modificare una RE, si opti - e a ragione - per riscriverla daccapo);
  2. le RE nascondono dietro di sé un codice nativo ma molto complesso, cioè quello che avremmo dovuto scrivere in sua vede: in poche parole, le RE possono rivelarsi lente, soprattutto se mal progettate.
Insomma, questo è per dirvi: non abusatene. Spesso un codice brillante può risultare più chiaro e veloce.
Ricordo, inoltre, che le RE sono implementate sì in svariati linguaggi ed ambienti, ma, complice il fatto che non sono mai state standardizzate, il supporto alle varie caratteristiche delle RE potrebbe essere più o meno completo, così come il comportamento (che si distingue in text-driven e regex-driven). Ogni volta che usare le RE con un linguaggio nuovo, consultate la parte relativa al modo in cui agiscono.


Le basi ed i primi esempi
I vari linguaggi di programmazione hanno diversi modi di rappresentare le RE. Spesso si tratta di stringhe che poi devono essere interpretate o semicompilate dal motore interno (Java, VB, C...), o nei casi di supporto nativo hanno una rappresentazione tutta propria, come nel caso di Javascript e Perl che le racchiudono tra due slash (/). In questa guida verranno rappresentate semplicemente con caratteri a larghezza fissa, senza apici di contorno, come questa: Pippo ha \d+ anni.

Sequenze speciali
Una RE sarà quindi composta da parti di stringhe, come Pippo ha e anni dell'esempio precedente, e da particolari sequenze di caratteri che vengono poi interpretate. Ad esempio, la sequenza \d+ significa "qualunque ripetizione di una o più cifre decimali". Vediamo dunque quali sono queste sequenze di caratteri speciali.
  • [...] Questa sequenza è in realtà un contenitore, che sta a significare "uno qualsiasi dei caratteri specificati qui dentro". Ad esempio, [abc] significa che viene ricercato il carattere 'a', oppure 'b', oppure 'c', mentre p[aeiou]zzo trova "pazzo", "pezzo", "pizzo", "pozzo" e "puzzo". Se vogliamo includere tutto l'alfabeto o una buona parte, si può anche scrivere [f-t] per includere tutti i caratteri compresi tra 'f' e 't', e lo stesso si può fare per i numeri, indicando ad esempio [0-4]. Naturalmente lo stesso si può fare con le lettere maiuscole, ed inoltre si possono combinare più intervalli di caratteri, come in [a-z0-9]. Se tra i caratteri si vuole ricercare anche il segno -, oppure le parentesi quadre, lo si deve far precedere da una backslash, così: [abc\-\[]. L'uso dell'escape con la barra rovesciata è prassi normale con le RE per ricercare tutti i caratteri considerati speciali compresa la backslash stessa.

    In alternativa, la sequenza [...]*si può usare anche per cercare tutti i caratteri che NON sono tra quelli specificati, facendo seguire alla [ il simbolo ^: ad esempio, [^abc] cerca tutti i caratteri che NON sono 'a', oppure 'b', oppure 'c'.
  • \d, \w, \s Indicano rispettivamente una cifra qualsiasi, un carattere alfanumerico qualsiasi più l'underscore, un carattere di spaziatura qualsiasi (spazio, tab, interruzioni di riga). \d è un'abbreviazione per [0-9], mentre \w ha lo stesso significato di [a-zA-Z0-9_], ma a differenza di queste espressioni le sequenze suddette si possono usare a loro volta all'interno delle parentesi quadre: [\d\s] è corretta, [[a-z][0-9]] no (insomma, le parentesi quadre non possono essere nidificate).
  • \D, \W, \S Indicano rispettivamente ogni carattere che NON sia una cifra, un carattere alfanumerico o underscore, una spaziatura.
  • \n, \r, \t Indicano rispettivamente il carattere di line feed (LF), carriage return (CR) e tabulazione.
  • \a, \e, \f, \v Indicano rispettivamente il carattere del beep (BELL), di escape (ESC), di form feed (FF) e vertical tab (VT). Li userete poco...
  • \xXX Serve ad indicare un carattere qualsiasi nella sua rappresentazione esadecimale. Ad esempio, \x41 rappresenta la 'A'.
  • \uXXXX Come sopra, ma questa volta XXXX è la rappresentazione esadecimale di un carattere Unicode. Alcuni linguaggi non supportano questa sintassi, oppure supportano l'alternativa \x{XXXX} (come il Perl).
  • . Serve a specificare la ricerca di un carattere qualsiasi, ad eccezione delle interruzioni di riga (o anche quelle se specificato dai flag).
Detto questo, è facile crearsi una RE che, ad esempio, ci consenta di trovare un codice fiscale all'interno di una stringa: per quanto visto, basta usare [A-Z][A-Z][A-Z][A-Z][A-Z][A-Z]\d\d[A-EHLMPR-T]\d\d[A-Z]\d\d\d[A-Z]. Semplice, no? Ma si può fare di meglio...
(Per ulteriori informazioni sulla sintassi dei codici fiscali, c'è sempre la fida Wikipedia: http://it.wikipedia.org/wiki/Codice_fiscale)


Quantificatori
Innanzitutto, tutte quelle ripetizioni di [A-Z] e \d si possono evitare. A questo proposito ci vengono incontro dei comodi quantificatori:
  • {...} Specifica che il carattere o gruppo di caratteri precedente dev'essere ripetuto un certo tot di volte. Ad esempio, \d{5} indica che si deve cercare una sequenza di esattamente 5 cifre decimali. È anche possibile specificare un numero minimo ed anche massimo di volte: \d{1,3} indica una sequenza da 1 a 3 cifre, mentre \d{2,} indica qualunque sequenza di almeno due cifre. Si badi che, con l'ultima sintassi, il match dell'espressione regolare si estende finché è possibile: dunque l'esempio riportato trova all'interno della stringa "Pippo è del 1990" tutta la sequenza "1990" e non solo "19". Per ottenere un comportamento come quest'ultimo si veda la parte relativa ai quantificatori greedy (di default), possessive e lazy.
  • ?, *, + Sono abbreviazioni rispettivamente per {0,1}, {0,} e {1,} (cioè, trovano rispettivamente una o nessuna ripetizione, un numero qualsiasi e almeno una ripetizione). Ad esempio, RegExp? trova sia "RegExp" sia "RegEx", mentre a+rgh! trova "argh!" ma anche "aaargh!", però non "rgh!".
A questo punto possiamo ridurre ulteriormente la nostra RE per i codici fiscali (oltretutto rendendone più veloce l'esecuzione), che diventa così [A-Z]{6}\d\d[A-EHLMPR-T]\d\d[A-Z]\d{3}[A-Z]. Poi possiamo sbizzarrirci come più vogliamo, ad esempio imponendo che il primo carattere sia per forza una consonante, ma per ora abbiamo raggiunto lo scopo (la validità del codice fiscale, verificando ad esempio la correttezza della data o del codice comunale, si può fare molto più semplicemente scrivendo poche righe di codice, ora che si sa che la sottostringa ha la forma di un codice fiscale).

Nota: fate attenzione ad usare correttamente i quantificatori! Un loro uso improprio, per quanto possa portare a risultati corretti, potrebbe avere conseguenze nefaste sulle performance del programma.


Alternanze e raggruppamenti
Ma se invece volessimo cercare due o più espressioni diverse tra loro? Ad esempio, ci interessano le stringhe del tipo "Pippo" ma anche "Pluto": dovremmo procedere a due ricerche? No, basta usare il carattere di alternanza, cioè la pipe (|), e creare un'espressione del tipo Pippo|Pluto. In questo modo, viene cercato "Pippo" oppure "Pluto", come volevamo.
Se invece fosse solo un pezzo di stringa a differire? Nel caso citato, "Pippo" e "Pluto" hanno le stesse lettere iniziali e finali. In questo caso sarebbe meglio utilizzare i raggruppamenti: essi non sono altro che un modo per intendere una sequenza di caratteri come un'unica entità dall'interprete delle RE. Per creare un gruppo basta includere le sequenze desiderate all'interno di parentesi tonde, ad esempio così: P(ipp|lut)o. In questo modo, l'interprete di RE cercherà ogni stringa che comincia per 'P', cui seguirà un gruppo che potrà essere "ipp" o "lut", cui segue una 'o'. Insomma, troverà "Pippo" e "Pluto", per l'appunto.
Le alternanze possono anche essere più di due, ad esempio avremmo potuto cercare P(ipp|lut|aperin)o, per includere anche "Paperino" nella nostra ricerca. Ma, cosa anche più importante, i raggruppamenti possono essere nidificati: la RE abc(def|ghi(123|456))ABC troverà tutte le seguenti stringhe: "abcdefABC", "abcghi123ABC" e "abcghi456ABC". Questo è utile, ad esempio, se volessimo cercare una data il cui anno può essere indicato da 2 o 4 cifre: \d\d-\d\d-(\d\d|\d{4}) è l'espressione regolare che fa al caso nostro, capace di trovare sequenze come "12-05-2004" o anche "22-04-85". (Ma anche "32-13-7865": come già detto, il controllo della correttezza della data può essere fatto tramite codice e non con le RE, anche se è possibile farlo. Io stesso ho creato una RE per trovare tutte le date comprese tra l'1-1-1901 e il 31-12-2099, senza bisogno di ulteriori controlli, e con una piccola modifica potrebbe essere usato per qualsiasi data, anche avanti Cristo. Se vi interessa, ditelo.)
Un altro grosso vantaggio dei raggruppamenti è la loro possibilità di essere usati in combinazione a quantificatori, in modo da ricercare una, nessuna o più ripetizioni dello stesso gruppo. Ad esempio, ba(na)+ troverà "bana", ma anche "banana", "bananana", "bananananana" e così via.


Posizionamenti
Cosa si può fare se vogliamo trovare delle espressioni solo in alcuni punti particolari del testo? Ad esempio, se volessimo trovare "Pippo" solo quando è all'inizio o alla fine di una stringa, oppure "ente" ma solo quando è alla fine di una parola? In questo caso intervengono delle sequenze di caratteri speciali che non indicano nessun carattere, ma servono per il posizionamento all'interno della stringa. Vediamoli.
  • ^ e $ Indicano rispettivamente l'inizio e la fine della stringa (o di una riga della stringa, nel caso di modalità multilinea - vedere più avanti la parte relativa ai flag). Ad esempio ^Pippo troverà "Pippo" quando è all'inizio della stringa. Oppure, ^\d+$ darà esito positivo solo quando la stringa è interamente composta da cifre decimali.
  • \A e \Z Sono simili a ^ e $, per non dire uguali, tranne nel caso in cui si usi la modalità multilinea, nel qual caso ^ e $ corrisponderanno all'inizio ed alla fine di ogni riga di testo, mentre \A e \Z indicheranno sempre l'inizio e la fine della stringa. L'unica eccezione (sia per \Z sia per $) si ha quando la stringa termina con un'andata a capo: in quel caso, $ e \Z indicheranno la posizione immediatamente prima di essa.
  • \b Indica la fine o l'inizio di una sequenza di caratteri alfanumerici (più l'underscore, come definito nella sequenza \w). In questo modo ente\b troverà "ente" in "dolcemente" ma non in "sentenza", perché dopo "ente" la parola continua.
  • \B Indica una qualsiasi posizione che NON sia l'inizio o la fine di una sequenza di caratteri alfanumerici. Dunque \Bente\B troverà "ente" in "sentenza", ma non in "dolcemente" o in "enteroclisma", così come \B-\B troverà '-' nella stringa "$$-$$" ma non in "foo-bar".
Si noti che le sequenze di posizionamento possono essere incluse anche all'interno di gruppi ed essere alternate ad altre sequenze. Ad esempio, (^|Topolino e )Pippo troverà "Pippo" quando è all'inizio della stringa, oppure "Topolino e Pippo" in qualsiasi parte del testo.


Fare a pezzi la stringa trovata!
Ebbene, abbiamo visto che \d\d-\d\d-\d{4} trova le date nel formato gg-mm-aaaa, mentre [A-Z][a-z]+ ha \d+ anni trova "Pippo ha 20 anni" ma anche "Paperone ha 75 anni". A questo punto possiamo procedere a recuperare i "pezzi" che ci servono, cioè i numeri indicanti giorno, mese e anno per la data, oppure nome ed età nel secondo esempio. Dovrebbe risultare piuttosto semplice, ma a volte può essere dispendioso, barboso e pure inutilmente complicato mettersi a fare ciò che la RE ha, in sostanza, già fatto. Ecco che in nostro soccorso arrivano i riferimenti.
In realtà, i riferimenti li abbiamo già creati negli esempi visti finora: vengono infatti ottenuti dai raggruppamenti tra parentesi tonde. Questi riferimenti tengono in memoria ("catturano") la sottostringa che ha un match con il gruppo. In questo modo, ([A-Z][a-z]+) ha (\d+) anni troverà sì "Pippo ha 20 anni", ma ci terrà comodamente in memoria le sottostringhe "Pippo" e "20" pronte per la nostra elaborazione!
I riferimenti vengono di solito salvati dai linguaggi in particolari oggetti, che di solito contengono sia tutta la stringa trovata, sia un elenco (solitamente un array) di stringhe contenenti i singoli riferimenti, e magari anche le loro posizioni iniziali e finali all'interno della stringa. I riferimenti sono numerati in ordine crescente a partire dal primo che viene trovato da sinistra, così "Pippo" sarà il primo riferimento, mentre "20" sarà il secondo. Il modo per reperire i riferimenti varia da linguaggio a linguaggio: in Java, ad esempio, un oggetto della classe Matcher consentirà, con il metodo find(), di trovare le corrispondenze e, una volta trovate, il metodo group(n) restituirà il riferimento nella posizione n, mentre start(n) e end(n) restituiranno la posizione del riferimento all'interno della stringa. In JavaScript, più semplicemente il metodo delle stringhe match(regex) restituirà un array il cui primo elemento è l'intera stringa trovata, cui seguiranno i diversi riferimenti catturati (non c'è indicazione della loro posizione, solo l'indicazione di inizio e fine della stringa trovata nell'oggetto globale RegExp o nell'array restituito da match).
Ma i gruppi sono stati usati anche per altre cose, ad esempio le alternanze: creeranno dei riferimenti anche in quel caso? La risposta è sì, anche se non vorremmo. Il modo per usare raggruppamenti senza creare riferimenti è quello di usare la sequenza (?:...) al posto di (...). Così, Il programma (\w+) contiene (\d+) bac(?:o|hi) troverà "Il programma helloWorld ha 1 baco" oppure "Il programma fooBar ha 7 bachi", ma i riferimenti saranno solo due: quello al nome del programma e quello al numero di bachi.
Perché evitare di creare riferimenti? La risposta dovrebbe essere ovvia: occupano memoria. Finché le espressioni sono così semplici non c'è problema, ma quando le cose si complicano sono dolori...
Un altro dubbio può essere: nel caso già visto di ba(na)+, ad esempio, viene creato un riferimento per ogni ripetizione di "na"? No. Il riferimento sarà sempre e solo uno, identificato da un numero assegnato in partenza, e conterrà solo l'ultima sottostringa "na" trovata.

Nota: che succede se una cattura viene fallita? Questo può capitare, ad esempio, nel caso di gruppi di cattura indicati in un'alternanza, come in (Pippo)|(Pluto) (sì, l'esempio è scemo). Nella stringa "Ciao Pippo!" la RE troverà "Pippo" e lo memorizzerà nella cattura con numero 1, ma non troverà "Pluto". La cattura 2 che valore avrà? Solitamente sarà un valore nullo, come null, nil o che altro in tanti linguaggi. In JavaScript e gli altri dialetti ECMAScript, invece, avrà valore "" (stringa vuota), così come una cattura del tipo (). Tenetelo a mente.


Backreferences
Il nostro problema ora diventi quello di voler cercare un semplice tag XML, aperto da una sequenza tipo <tag>, contenente un po' di testo ed infine chiuso dalla sequenza </tag>, ad esempio "<ciao>Pippo</ciao>". Una RE del tipo <\w+>(?:[^<]|<[^/])*</\w+> sembrerebbe che possa andare bene (esercizio: si presti attenzione alla parte compresa tra (?:...)* e si cerchi di capire cosa vuol dire). Tuttavia, è facile accorgersi che in caso di XML non ben formato, come ad esempio "<ciao>Pippo</saluto>", questa RE dapprima trova "<ciao>", poi trova "Pippo" e poi trova "</saluto>". Noi invece avremmo voluto che la corrispondenza fallisse, perché "</saluto>" non chiude il tag "<ciao>", e ci saremmo risparmiati un "falso positivo".
I gruppi di cattura ci possono dare una mano: creando dei riferimenti, li possiamo usare per indicare di star cercando qualcosa che è stato trovato in precedenza e catturato. Come già detto, ai gruppi di cattura viene assegnato un numero progressivo da 1 in poi, a partire da quello indicato più a sinistra: per farvi riferimento si usa una backreference, che si indica dalla sequenza \#, dove # è il numero assegnato alla cattura (può anche essere di due cifre, fino a 99). La nostra RE dovrà quindi catturare il nome del tag e riutilizzarlo dopo per indicare il tag di chiusura, in questo modo: <(\w+)>(?:[^<]|<[^/])*</\1>. Così siamo sicuri che l'eventuale tag di chiusura corrisponda a quello di apertura.

Note:
  1. Come detto, i riferimenti possono anche essere a due cifre. Attenzione, quindi, se dopo un riferimento ad una cifra si vuol far seguire un altro numero: se volessimo, ad esempio, cercare sequenze tipo "zerozero7" sbaglieremmo ad usare la RE (\w+)\17, perché faremmo un riferimento alla cattura 17, che non esiste. In questo caso si può inserire una sequenza vuota nel mezzo, tipo (?:) o .{0}, ottenendo (\w+)\1(?:)7. Macchinoso ma efficace.
  2. Alcuni linguaggi supportano i riferimenti alla catture posteriori al riferimento stesso: nell'esempio precedente, avremmo potuto scrivere anche \1(\w+)7, risparmiandoci la tediosa sequenza vuota. Ma in realtà, i "riferimenti a priori" hanno il loro vero senso solo all'interno nel caso di gruppi ripetuti.
  3. Alcuni linguaggi in più supportano anche i riferimenti nidificati, cioè riferimenti dentro al gruppo stesso cui fanno riferimento. Anche in questo caso, il vero senso si ha in caso di gruppi ripetuti: ad esempio (zero|\1sette)+ catturerà "zerozerosette" (ma anche "zerozerosettezerosettesette" o "zerozerozerosette": ricordare il dubbio alla fine del sottoparagrafo precedente).


Modificatori (flags)
Con una semplice opzione è anche possibile cambiare il modo in cui il motore applica le RE. Ad esempio, gli si può dire di ignorare le differenze tra maiuscole e minuscole. I modificatori vengono solitamente abbreviati con una singola lettera minuscola. Vediamoli:
  • i ignore-case. Semplicemente la RE non fa distinzione tra lettere maiuscole e minuscole.
  • m multi-line. In questa modalità, i caratteri speciali ^ e $ corrisponderanno all'inizio e alla fine di ogni riga piuttosto che di tutta la stringa.
  • s single-line o dot-all. In questo caso, invece, il carattele speciale . (il punto) corrisponderà anche alle andate a capo.
  • x free-space. Utile per poter scrivere RE in maniera più comprensibile, questa flag indica al motore RE di ignorare ogni spazio bianco (comprese le andate a capi) e sarà possibile addirittura inserire commenti all'interno della RE, facendoli precedere da un simbolo #.
  • g global. La RE andrà a trovare tutti i match possibili all'interno della stringa e non si fermerà alla prima. Non sempre questa modalità ha un senso, tant'è vero che alcuni linguaggi neanche la supportano. Il suo maggiore utilizzo è principalmente nelle sostituzioni (si veda dopo).
Si noti che i modificatori non sono esclusivi, e non lo sono neppure multi-line e single-line: un modificatore sm, semplicemente, farà corrispondere ^ e $ ad inizio e fine di ogni riga, e al punto anche le andate a capo.
La modalità free-space può essere molto comoda per le RE più complesse ed usate di comune. Quella per la data, ad esempio, si può scrivere così:
Codice:
( \d \d? )    # Trova e cattura il giorno
[ \- \/ ]     # Trova il carattere di separazione
( \d \d? )    # Trova e cattura il mese
[ \- \/ ]     # Altro separatore
( \d{4} )     # Trova e cattura l'anno
Questa RE avrà lo stesso effetto della più oscura (\d\d?)[\-\/](\d\d?)[\-\/](\d{4}). Per indicare gli spazi, se non si vuole usare \s si possono usare i costrutti [ ] (aperta quadra, spazio, chiusa quadra) o \ (backslash seguita da uno spazio).


Sostituzioni
Ora che abbiamo i riferimenti (e magari anche le loro posizioni) per noi diventa facile manipolare i dati. Un altro annoso problema che potremmo incontrare è quello della sostituzione di sottostringhe all'interno di una stringa. Solitamente abbiamo da parte la posizione iniziale e finale della stringa trovata, per cui il problema si semplifica... ma può essere ulteriormente semplificato grazie ai comuni metodi di sostituzione che vengono offerti dai vari linguaggi ed ambienti di sviluppo.
Supponiamo, ad esempio, di voler trasformare una data nel formato gg-mm-aa nel corrispondente gg/mm/19aa. Normalmente, quello che faremmo è, una volta che la RE ha trovato la corrispondenza, prendere la stringa iniziale, prendere la sottostringa dall'inizio fino all'inizio della corrispondenza, concatenarla con la stringa che abbiamo creato con la data nel formato che vogliamo, ed infine concatenarla con il resto della stringa. Noioso, vero?
Invece è comune che i linguaggi mettano a disposizione i metodi replace(string, string) (o qualcosa di simile), ma che abbiano un overload anche nel metodo replace(regex, string), in modo che la sostituzione avvenga con i match trovati con le RE. In questo caso, la stringa di sostituzione può contenere le indicazioni ai riferimenti trovati (backreferences), che vengono solitamente rappresentate da sequenze come "$X" oppure "\X", dove X indica il numero del riferimento trovato. (Nei linguaggi ho trovato più comune l'uso di "$X", mentre negli editor mi è parso più comune "\X". Consultate l'help relativo, comunque.)
Dunque, per ottenere il nostro scopo possiamo procedere con replace() usando (\d\d)-(\d\d)-\(\d\d) come RE, e "$1/$2/19$3" come stringa da sostituire. In questo modo, una data come "12-08-82" viene trasformata subito in "12/08/1982". Si noti che i riferimenti, nella stringa di sostituzione, vengono spesso numerati a partire da 1 e non da 0 come ci si potrebbe aspettare da prassi comune nei linguaggi di programmazione.
In alcuni linguaggi, soprattutto quelli che non supportano un metodo replaceAll() o simile (tipo il JavaScript), può tornare utile usare RE con flag global, in modo da ottenere una sorta di "sostituisci tutto".


Dove posso provare le mie espressioni regolari?
Solitamente, un editor di testo che le supporti potrebbe fare a caso vostro. Se volete sapere quali supportano le RE, questa pagina di Wikipedia vi può aiutare:
http://en.wikipedia.org/wiki/Compari...Basic_features
Personalmente, uso Notepad++. Se invece volete qualcosa di graficamente più carino e che dia subito un colpo d'occhio dei risultati trovati, forse questo sito ha quello che cercate: http://www.gskinner.com/RegExr/


**********************
Il contenuto di questo post è rilasciato con licenza Creative Commons Attribution-Noncommercial-Share Alike 2.5
Creative Commons Attribution-Noncommercial-Share Alike 2.5
__________________
HWU Rugby Group :'( - FAQ Processori - Aurea Sectio - CogitoWeb: idee varie sviluppando nel web

Ultima modifica di MaxArt : 15-06-2010 alle 22:50.
MaxArt è offline   Rispondi citando il messaggio o parte di esso
Old 15-06-2010, 22:46   #2
MaxArt
Senior Member
 
L'Avatar di MaxArt
 
Iscritto dal: Apr 2004
Città: Livorno
Messaggi: 6602
Espressioni Regolari avanzate
Quello che abbiamo visto finora è una guida di base, che già consente di fare parecchio con le espressioni regolari. Ma le RE possono fare ancora di più, ed inoltre si possono apprendere diversi trucchetti per migliorare le performance. Perché le basi delle RE si imparano in fretta, ma molto più ostico è creare delle RE che funzionino bene e che non uccidano le performance del nostro codice: spesso è facile creare delle RE la cui complessità è O(2^n) rispetto alla lunghezza della stringa. Immaginatevi le conseguenze...


Lookarounds
Supponiamo di avere una stringa al cui interno vogliamo identificare una... stringa, cioè una sequenza di caratteri racchiusa tra apici. È un caso comune se ci capita di dove fare il parsing di un codice o proto-codice, o semplicemente di un elenco di valori che possono essere anche delle stringhe delimitate da apici. Un'espressione regolare adeguata potrebbe essere '[^']*'. Non è difficile da interpretare: si cerca una stringa che inizi con un apice, prosegua con una sequenza di qualsivoglia lunghezza di caratteri che non siano apici, e poi si conclude con un apice.
Ma supponiamo che all'interno della stringa sia permesso inserire degli apici, purché adeguatamente preceduti da una backslash: in fondo, gli apici ci servono anche come apostrofi. Si potrebbe, infatti, avere qualcosa tipo 'un\'automobile', e la nostra RE si fermerebbe al solo 'un\'. Quello che dobbiamo fare è, allora, cercare di includere questi apici con escape, e per farlo il modo migliore sono i lookarounds, cioè delle sequenze di simboli che ci consentono di specificare da cosa dev'essere preceduta (lookbehind) o succeduta (lookahead) un gruppo perché esso venga considerato. Si noti che l'argomento dei lookaround non viene incluso nel gruppo di ricerca, ed è questo il loro punto fondamentale.
  • (?=...) positive lookahead. Specifica che la sequenza di caratteri dev'essere seguita dall'argomento compreso tra (?= e ).
  • (?!...) negative lookahead. Questa volta, si specifica che non debba essere seguita dall'argomento del lookahead.
  • (?<=...) positive lookbehind. Il gruppo verrà considerato solo se preceduto dall'argomento del lookbehind.
  • (?<!...) negative lookbehind. Il contrario di sopra.
Quello che ci serve nel nostro esempio è un positive lookbehind. Si noti che i lookahead venno specificati dopo il gruppo in esame, mentre i lookbehind prima. Dunque, la nostra espressione regolare diventa '(?:[^']|(?<=\\)')*'. Esaminiamola, in modalità free-space:
Codice:
'        # indica che la stringa deve iniziare con un apice
(?:      # dentro questo gruppo ci mettiamo i caratteri che vogliamo, cioè tutti tranne gli apici, a meno che non siano preceduti da \
[^']     # questo vuol dire "tutti i caratteri che non siano apici"
|        # quest'alternanza ci serve per dire che vogliamo includere anche gli apici preceduti da \
(?<=\\)' # questa sequenza significa "gli apici preceduti da \" (ricordiamo che le backslash devono essere a loro volta precedute da backslash)
)        # chiudiamo il gruppo e diciamo di cercare la sequenza più lunga possibile
'        # infine, la stringa deve finire con un apice.
Adesso dovrebbe risultare tutto chiaro.

Nota: in termini computazionali, i lookarounds sono piuttosto dispendiosi. Quello che fanno, in effetti, è andare ad esplorare le parti precedenti o successive al punto raggiunto e verificare una condizione in più, e poi tornare indietro. Conoscendo meglio il modo in cui funzionano le RE, però, ci si rende conto che molto spesso se ne può fare a meno. Se avessimo usato la RE '(?:[^']|\\')*' ci saremmo accorti che non avrebbe funzionato ma, semplicemente cambiando l'ordine dall'alternanza, ci si accorge che '(?:\\'|[^'])*' invece va bene. Dal punto di vista logico questo può lasciarci confusi, perché dovrebbe essere un or e quindi una sorta di operatore simmetrico. Qual è la differenza?
Il fatto è che nel primo caso il motore RE dapprima cerca tutti i caratteri che non siano degli apici: nella stringa 'un\'automobile', quindi, trova "u", poi trova "n", poi trova "\" ed infine trova l'apice. A questo punto il gruppo [^'] non va bene e allora prova con l'altro, cioè \\': ma anche questo non va bene. Dunque, il motore conclude che il gruppo (?:[^']|\\') non va più bene, quindi torna indietro di un carattere e verifica se il carattere dopo è un apice... ed in effetti lo è. Quindi produce solo 'un\' come risultato.
Nell'altro caso, invece, il motore RE cerca per prima cosa se siamo di fronte ad una sequenza \', e poi se è un qualsiasi carattere che non sia un apice. Quindi, dopo aver passato il primo apice, non trova "\'" ma trova "u"; poi non trova ancora "\'" ma trova "n"; infine trova "\'"! Di conseguenza la ricerca non si blocca e può andare avanti, trovando correttamente tutta la stringa 'un\'automobile'.
Quindi, il consiglio generale è: usando le alternanze, mettere sempre per primi i gruppi più lunghi, o si rischia che il motore RE si blocchi anzitempo.

Trucco: i lookahead, come detto, sono utili per trovare gruppi seguiti da un altro gruppo specifico, senza che questo venga incluso nel risultato. Ad esempio, se volessimo cercare un tag di chiusura HTML, potremmo cercare i simboli < che siano seguiti dalla /, dal nome del tag e poi da >. Una RE tipo <(?=/div>) farebbe a caso nostro. Come visto, in questi casi il lookahead va messo dopo il simbolo '<'.
È utile far notare che i lookahead, però, possono messere anche prima di un certo gruppo. In effetti, i lookaround non sono per forza legati ad un gruppo di riferimento: semplicemente dicono al motore delle RE di effettuare una verifica, in avanti o indietro. Cosa vuol dire, quindi, mettere un lookahead prima di un gruppo? In sostanza, signfica verificare che ciò che segue soddisfi la condizione specificata. Ad esempio, la RE \b(?=\w{10}\b)\w*ente\b va a cercare tutte le parole che finiscono in "ente", dopo però aver verificato che fossero lunghe 10 caratteri (sì, lo so, \b\w{6}ente\b avrebbe prodotto lo stesso risultato, ma era un esempio).
Si noti che i lookahead usati in questo modo possono anche essere più di uno, da usare in una sorta di and logico.


Quantificatori avidi, svogliati e possessivi
Supponiamo di avere una stringa come
"12,$4,25a,2 4,65=,3pippo"
e di voler isolare i singoli valori separati da virgole. I valori possono essere numeri, ma anche lettere o altri simboli, in un totale che sarebbe dispendioso contenere in una classe [...]. A questo punto, usiamo il carattere universale . (il punto) e procediamo con la semplice RE (.*), ottenendo come risultato "12,$4,25a,2 4,65=,". Cos'è successo? Semplicemente, il motore RE, non appena ha trovato la prima virgola, non ha pensato che fosse la fine di quel che stavamo cercando, ma invece l'ha considerata un carattere qualsiasi (quindi presa dal .) ed ha proseguito. Dunque è giunto fino in fondo alla stringa, è dovuto tornare indietro di un carattere, verificare che dopo ci fosse una virgola e fallendo, quindi di nuovo tornare indietro di un carattere e così via, per 7 volte, finché non ha infine trovato una virgola. Ecco spiegato il risultato finale.
Il fatto è che il quantificatore * è "avido" (greedy), e tende ad andare avanti a ripetere il gruppo finché gli è possibile. Tuttavia, ci sono modi per differire questo comportamento. Il primo di questo è rendere il quantificatore "svogliato" (lazy), tramite l'aggiunta di un punto interrogativo immediatamente dopo: l'espressione, quindi, diventerà (.*?), e funzionerà proprio al caso nostro.
Un quantificatore lazy fa agire il motore in maniera diversa: esso andrà avanti per il numero minimo di ripetizioni necessarie per non interrompersi. Nell'esempio, quindi, si è detto dapprima detto "cerca le sequenze qualsiasi seguite e fermati quando dopo c'è una virgola", mentre dopo "cerca le sequenze qualsiasi e fermati alla prima virgola trovata". Dunque, il motore dapprima verificherà che ci sia una virgola (fallendo), quindi troverà '1' e verificherà di nuovo la presenza di una virgola (fallendo di nuovo), poi troverà '2' e finalmente la virgola: la stringa di match sarà dunque "12," ed il gruppo catturato "12", proprio come volevamo.

Infine, c'è un altro tipo di quantificatore: quello possessivo (possessive), che si ottiene ponendo un '+' dopo il quantificatore. La sua filosofia si può riassumere in: o tutto, o niente!
I quantificatori possessivi sono simili a quelli avidi, ma c'è una sottile differenza che li rende sia potenzialmente molto più efficienti, sia più difficili da usare correttamente. Operativamente parlando, i quantificatori avidi tengono memoria di "dove sono stati", pronti a ritornarvi in caso di fallimento. Nel caso visto (.*), si è notato che il motore ha dovuto fare il backtracking di ben 7 caratteri prima di ottenere un risultato positivo. Il fenomeno può diventare poco gestibile nel caso di stringhe molto più lunghe.
I quantificatori possessivi, invece, non tengono conto delle posizioni precedenti e dunque non effettuano backtracking in caso di fallimento. Cosa succede, dunque? Semplice: vanno avanti con la stringa, ignorando completamente il pezzo preso in esame.
Vediamo come questo si traduce al lato pratico. La potenzialità dei quantificatori possessivi esce tutta quando il motore RE fallisce la corrispondenza. Supponiamo di avere una stringa che assomiglia ad un XML, tipo
"<persona><nome>Pippo</nome><eta>anni<100</eta></persona>",
e di volerne isolare i singoli tag. La nostra RE potrebbe essere <[^<>]*>: in questo caso troverebbe, nell'ordine, "<persona>", "<nome>", "</nome>", "<eta>", "</eta>" e "</persona>". Tutto bene? Sembra di sì. Un problema, però, si presenta quando il motore RE arriva a quel "<100", dove trova un altro '<' (quello di "</eta>") prima di trovare un '>': in questo caso, il motore ritorna indietro di uno, poi di due, infine di tre caratteri, ogni volta verificando che dopo venga un '>' e sempre fallendo, per poi rinunciare del tutto e proseguire nell'analisi della stringa. Un sacco di calcoli per qualcosa che, in fondo, era già ben evidente...
Un quantificatore possessivo, invece, appena nota che dopo il "100" c'è un altro '<', rinuncia da subito e riprende l'analisi direttamente da "</eta>". I risultati sono uguali, ma le performance sono nettamente diverse. I veri miglioramenti, a direi il vero, si ottengono soprattutto nel caso di raggruppamenti nidificati, in cui i backtracking possono essere numerosi e potrebbero inficiare molto le prestazioni.

Nota: alcuni motori RE si accorgono quando un gruppo sottoposto a quantificatore ed i caratteri successivi sono mutualmente esclusivi, rinunciando da subito al backtracking e di fatto rendendo automaticamente possessivi i quantificatori. Questo, ad esempio, è il caso del framework .NET. L'assenza di quantificatori possessivi, quindi, potrebbe essere benissimo una scelta voluta per non includere una caratteristica inutile.


Gruppi atomici
Un altro metodo per ottimizzare le RE è quello di usare i gruppi atomici. Tali gruppi vengono racchiusi entro il costrutto (?>...) e, al pari di (?:...) e dei lookarounds, non costituiscono un gruppo di cattura. Si usano solitamente in congiunzione a quantificatori ed alternanze, ed hanno un comportamento che per certi versi ricorda i quantificatori possessivi, nel senso che in caso di fallimento i gruppi atomici non effettuano backtracking. L'esempio per cui sono usati più spesso è quello della ricerca delle parole. Si supponga che si vogliano cercare le parole "atono", "atomi" e "atomica" in un testo: la RE \b(atomica|atomi|atono)\b farebbe al caso nostro (è una RE un po' scema, ma ricordiamoci che le RE si possono anche generare, e non è facile creare un algoritmo che generi RE veramente "intelligenti"). Se nella stringa s'incontra una delle tre parole, va tutto bene; se tuttavia si incontra una parola tipo "atomicamente", il motore RE prima troverà che "atomica" va bene ma poi non c'è la fine della parola, quindi tornerà indietro e proverà prima "atomi" e poi "atono", fallendo in entrambi i casi.
Se avessimo usato un gruppo atomico, una volta che si è visto che "atomica" poteva andare bene ma che poi non c'è la fine della parola, il motore RE non avrebbe provato le altre due alternative e sarebbe andato avanti. Il vantaggio è un sacco di tempo e calcoli risparmiati.

Nota: bisogna fare attenzione con i gruppi atomici. Se al posto di*\b(?>atomica|atomi|atono)\b avessimo usato \b(?>atomi|atomica|atono)\b, pur essendo la parola "atomica" nel nostro testo non sarebbe stata trovata la corrispondenza. Questo perché la RE avrebbe prima trovato che "atomi" poteva starci, ma poi non avrebbe trovato la fine della parola e dunque non avrebbe provato le altre due alternative, tra cui c'era proprio la parola "atomica".


Performance catastrofiche
Ora vediamo in che senso un'espressione regolare può avere performance davvero disastrose. I veri problemi cominciano quando si fa uso di quantificatori non limitati superiormente (come *, + e {n,}). Supponiamo di* cercare una sequenza di lettere maiuscole seguita da almeno una cifra. La RE \b([A-Z]+\d+)\b fa il lavoro che ci serve. In effetti, una riga di testo come "AS400" viene elaborata senza rallentamenti. I veri problemi cominciano, come sempre, quando le corrispondenze falliscono: in questi casi, è possibile che si scatenino una serie di backtracking che aumentano esponenzialmente con la lunghezza della stringa. Se ad esempio la stringa fosse stata "AUTHENTICAMD", il motore RE avrebbe prima trovato tutto "AUTHENTICAMD", poi avrebbe non trovato una cifra e dunque sarebbe tornato indietro di un carattere, avrebbe riprovato a trovare una cifra e così via, eseguendo un backtracking ben 12 volte prima di accorgersi che non c'è nulla da fare.
Ma fin qui va ancora bene. Supponiamo invece la seguente situazione: abbiamo delle righe di valori separati da virgole, e noi vogliamo delle righe contenenti esattamente tre valori. Inoltre, non sappiamo come sono formati i nostri valori: in questo caso sarebbe comodo usare il carattere universale . (il punto). Ovviamente, però, l'espressione ^(.*,){3}$ (con flag multi-line) non va bene perché la sequenza .* ci "mangia" subito tutta la stringa, perché si tratta di un quantificatore avido: usiamo dunque la forma svogliata, ottenendo*^(.*?,){3}$ . Cosa succede, dunque, in caso di fallimento? Ipotizziamo ad esempio che nella riga i valori non siano tre ma quattro o più, tipo "asino,banana,cane,dattero,elefante": allora il motore RE troverà subito "asino,banana,cane," e poi si accorgerà che non c'è la fine della riga. Quindi, il punto troverà la virgola, poi cercherà la virgola (fallendo) e andando avanti, cosicché l'ultimo gruppo catturato diventerà "cane,dattero," e non più solo "cane,". Alla fine arriverà a "cane,dattero,elefante", non troverà la virgola e tornerà indietro. Sì, ma dove?
In questa tornata è stato "espanso" l'ultimo gruppo, ma a questo punto verrà espanso il secondo, che diventerà "banana,cane," ed il terzo prima "dattero," e poi non troverà la fine della riga, quindi il secondo gruppo diventa "banana,cane,dattero," e via dicendo, con una quantità di backtracking evidentemente crescente in maniera esponenziale.
Il nostro problema, in questo caso, è quel dannato punto che ci "mangia" la virgola: se avessimo usato [^,] al posto del punto, sarebbe andato tutto liscio.
Le tecniche per evitare i backtracking esponenziali sono diverse:
  • usare gruppi di caratteri mutualmente esclusivi quando si tratta di individuare qualcosa tra separatori. Ad esempio, "([^"]*)" può essere decisamente meglio di "(.*?)" per individuare qualcosa tra doppi apici;
  • fare uso di quantificatori possessivi e di gruppi atomici, se possibile;
  • ripensare completamente alla RE che si è creata: in questo conta molto l'esperienza.


Trucchi e funzionalità vari
Ecco varie funzionalità offerte dalle ultime caratteristiche sviluppate per le RE.


Gruppi con nomi
Abbiamo visto che i gruppi catturati vengono referenziati da sinistra a destra (in "ordine di apparizione") con un numero progressivo da 1 in poi. Tuttavia, usare i numeri per i riferimenti può confondere le idee, e sarebbe bello poter chiamare i gruppi con un loro nome.
Il Python è stato il primo linguaggio ad offrire questa funzionalità: un gruppo con nome può essere indicato con (?<...>...), dove la parte tra parentesi angolate è il nome del gruppo. Il gruppo può essere referenziato con la sintassi (?P=...) oppure con la vecchia sintassi \# (con # un numero), perché anche i gruppi con nomi ricevono un numero di assegnazione.
Il framework .NET permette i gruppi con nomi, con qualche differenza. Come sequenza, oltre a quella già vista si può usare anche (?'...'...) che è più utile con linguaggio ASP (dove si fa abbondante uso di < e >). I riferimenti si fanno con la sintassi \k<...> o \k'...' indifferentemente. Inoltre, col framework .NET è possibile assegnare lo stesso nome a più gruppi, qualora si escludano a vicenda con un'alternanza. Se, infatti, volessimo catturare la cifra che segue una "a" seguita da 0-3, oppure una "b" seguita da 4-7, possiamo scrivere a(?<cifra>[0-3])|b(?<cifra>[4-7]) senza che questo ci dia un errore di compilazione.


Commenti
Oltre alla modalità free-space, diversi motori RE supportano i commenti "in linea" delle RE. Basta includerli tra la sequenza (?# e la parentesi chiusa.
Generalmente, i linguaggi che supportano la modalità free-space supportano anche i commenti in linea, con la rilevante eccezione del Java.


Modificatori locali
Tutti gli interpreti di RE consentono di specificare le modalità, come visto nei paragrafi precedenti. Non tutti, però, consentono di applicare i modificatori solo a parte della RE. Per quelli che lo consentono, il gioco è semplice: basta indicare tra in mezzo a (?...) i modificatori che si vogliono usare, e questi verranno applicati sono a partire dalla destra della sequenza.
Come si è detto, i modificatori sono indicati dalle varie lettere: 'i' per ignore-case, 'm' per multi-line e così via. Per "spegnere" le modalità basta indicarle dopo il segno '-' (meno). Dunque, per attivare la modalità ignore-case si deve usare (?i), mentre per disattivarla ed attivare invece quella single-line si usa (?s-i).
In alternativa, anziché "accendere" e "spegnere" i modificatori, si può inserire la parte interessata dal modificatore direttamente all'interno della sequenza (?...:...), dove tra ? e : si mettono i modificatori, e tra : e ) la parte interessata.


Espressioni condizionali
Alcuni linguaggi supportano dei particolari costrutti di branching, per a seconda del risultato di un test viene applicata una parte della RE oppure un'altra. Si tratta delle espressioni condizionali, e possono esistere in varie forme.
La forma più utile a mio avviso è quella che verifica se un gruppo di cattura è andato a buon fine: ha la sintassi (?(#)...|...), dove # indica il numero della cattura, l'espressione prima del simbolo '|' è l'espressione da applicare se il gruppo # è stato catturato, mentre quello dopo è la parte da applicare se invece non è stato catturato. Ad esempio, la RE (Grande\s)(?(1)Punto|Panda) troverà corrispondenza in "Grande Punto" e in "Panda", ma non in "Grande Panda".
Un'altra forma di espressione condizionale consiste nell'usare i lookarounds: semplicemente, al posto di (#) si mette un lookaround che rappresenti il nostro test. Ad esempio, (?(?=\w{1,6}\b)\w*|.{3}) significa che si prenderanno tutte le lettere se ciò che viene dopo è una parola di 6 lettere, altrimenti si prendono i primi 3 caratteri.

Nota: i più esperti avranno notato che quest'ultima forma di espressione condizionale è quasi del tutto inutile, perché lo stesso identico effetto si può ottenere con (?:(lookaround)...|...). In effetti il significato profondo di quella sintassi sfugge anche a me, ma suppongo che ce ne sia uno...


Differenze tra i vari linguaggi
Questa tabella riassume il supporto alle varie caratteristiche delle RE presenti nei vari linguaggi e con i pacchetti più comuni.
Ne risulta alla fine che il supporto alle RE di XSLT/XPath/XQuery è molto povero, con quello di JavaScript appena migliore (ma ci si rende conto che è sufficiente per la stragrande maggioranza delle applicazioni), mentre il più completo risulta essere il framework .NET, seguito a ruota da Java e dal motore PCRE.



Links e riferimenti

RegExr: Online Regular Expression Testing Tool
Sito per testare le proprie RE. Manca delle funzionalità più avanzate, come gruppi atomici.
http://www.gskinner.com/RegExr/

Regular-Expressions.info
Un'autentica miniera d'oro di informazioni. C'è tutto quello che c'è in questa guida e molto di più. Manca solo un accenno alle RE ricorsive.
http://www.regular-expressions.info/



**********************
Il contenuto di questo post è rilasciato con licenza Creative Commons Attribution-Noncommercial-Share Alike 2.5
Creative Commons Attribution-Noncommercial-Share Alike 2.5
__________________
HWU Rugby Group :'( - FAQ Processori - Aurea Sectio - CogitoWeb: idee varie sviluppando nel web

Ultima modifica di MaxArt : 15-06-2010 alle 22:49.
MaxArt è offline   Rispondi citando il messaggio o parte di esso
Old 15-06-2010, 22:51   #3
MaxArt
Senior Member
 
L'Avatar di MaxArt
 
Iscritto dal: Apr 2004
Città: Livorno
Messaggi: 6602
Post di servizio, non si sa mai che mi venisse in mente di aggiungere altro...
Le espressioni regolari sono molto potenti e si stanno avvicinando sempre di più ad essere un vero e proprio linguaggio di programmazione, ma... per quanto io ne faccia largo uso, invito sempre alla cautela nel loro utilizzo.
__________________
HWU Rugby Group :'( - FAQ Processori - Aurea Sectio - CogitoWeb: idee varie sviluppando nel web
MaxArt è offline   Rispondi citando il messaggio o parte di esso
Old 16-06-2010, 00:54   #4
banryu79
Senior Member
 
L'Avatar di banryu79
 
Iscritto dal: Oct 2007
Città: Padova
Messaggi: 4131
Un applauso per il lavoro encomiabile.
Penso però che sarebbe stato più appropriato aprire il thread nella sottosezione Tutorials .
Ci penserà un moderatore a spostare il tutto e cancellare questo mio post!
__________________

As long as you are basically literate in programming, you should be able to express any logical relationship you understand.
If you don’t understand a logical relationship, you can use the attempt to program it as a means to learn about it.
(Chris Crawford)
banryu79 è offline   Rispondi citando il messaggio o parte di esso
Old 16-06-2010, 09:40   #5
MaxArt
Senior Member
 
L'Avatar di MaxArt
 
Iscritto dal: Apr 2004
Città: Livorno
Messaggi: 6602
Quote:
Originariamente inviato da banryu79 Guarda i messaggi
Un applauso per il lavoro encomiabile.
Penso però che sarebbe stato più appropriato aprire il thread nella sottosezione Tutorials .
Ci penserà un moderatore a spostare il tutto e cancellare questo mio post!
Non posso postare direttamente nella sezione delle guide
Quindi si posta qui, poi cionci sposta.
Cancellare il tuo post, e perché? La discussione può proseguire qui. Mica tutto si esaurisce alla guida: si possono fare domande e chiedere spiegazioni.
__________________
HWU Rugby Group :'( - FAQ Processori - Aurea Sectio - CogitoWeb: idee varie sviluppando nel web
MaxArt è offline   Rispondi citando il messaggio o parte di esso
Old 16-06-2010, 10:16   #6
cionci
Senior Member
 
L'Avatar di cionci
 
Iscritto dal: Apr 2000
Città: Vicino a Montecatini(Pistoia) Moto:Kawasaki Ninja ZX-9R Scudetti: 29
Messaggi: 53963
Lasciamolo qualche giorno qui. Intanto l'ho già aggiunto ai collegamenti
cionci è offline   Rispondi citando il messaggio o parte di esso
Old 16-06-2010, 18:42   #7
fero86
Senior Member
 
Iscritto dal: Oct 2006
Città: Roma
Messaggi: 1383
be', l'argomento a me non interessa molto ma i complimenti te li meriti tutti perché mi sembra un lavoro molto ben fatto!
fero86 è offline   Rispondi citando il messaggio o parte di esso
Old 03-04-2012, 12:40   #8
Blackskull182
Member
 
L'Avatar di Blackskull182
 
Iscritto dal: Mar 2007
Messaggi: 205
Grazie mille
__________________
Tutti gli imbecilli della Borghesia che pronunciano continuamente le parole: immorale, immoralità, moralità nell’arte e altre bestialità mi fanno pensare a Louise Villedieu, puttana da cinque franchi, che accompagnandomi una volta al Louvre, dove non era mai stata, si mise ad arrossire, a coprirsi la faccia, e tirandomi a ogni momento per la manica, mi domandava davanti alle statue e ai quadri immortali come si potesse esporre pubblicamente simili indecenze. (Charles Baudelaire)
Blackskull182 è offline   Rispondi citando il messaggio o parte di esso
Old 01-05-2013, 14:23   #9
pl1957
Junior Member
 
Iscritto dal: Jul 2008
Messaggi: 18
Riprendo l'argomento per una cosa che mi sta facendo ammattire .
Ma, prima, un GRAZIE così

Devo usare, sotto ubuntu, il comando rename che fa uso delle espressioni regolari.

Ho la seguente situazione: un gruppo di file ai quali devo aggiungere in testa al nome una stringa ma solo se questa stringa non è già presente (e che può essere in un qualsiasi punto del nome) e solo se nel nome file è contenuta una certa parola.

Esempio:
Nomi dei file attuali:
ABC-nome1
ABC-nome2-PAROLA
ABCGHI-nome3-DEF-PAROLA
ABCGHIMNPXYZ-PAROLA-nome4
ABCDEF-PAROLA-nome5

Devo aggiungere, se non già presente, la stringa DEF se nel nome file compare PAROLA.

Di sicuro è che la stringa DEF appare nel nome file sempre prima di PAROLA.

Quindi:
nome1 non va cambiato (non compare PAROLA),
nome2 deve diventare DEFABC-nome2-PAROLA,
nome3 non va cambiato (appare già DEF),
nome4 deve diventare DEFABCGHIMNPXYZ-PAROLA-nome4,
nome5 non va cambiato (appare già DEF).

Ho provato con i vari lookaround, ma senza successo; probabilmente perché "DEF" può essere in qualsiasi punto.

Chi può aiutarmi?

P.S.: Buon 1 maggio!
pl1957 è offline   Rispondi citando il messaggio o parte di esso
Old 02-05-2013, 00:35   #10
MaxArt
Senior Member
 
L'Avatar di MaxArt
 
Iscritto dal: Apr 2004
Città: Livorno
Messaggi: 6602
Di solito cerco di evitare i quantificatori lazy, ma nel caso di "cerca solo se la stringa (non) contiene xyz" sono la soluzione più semplice. L'uso di due lookahead fa al caso tuo, anche se può essere un po' pesante:

(?=.*?PAROLA)(?!.*?DEF)(.+)

Da sostituire con "DEF$1". Testa su http://www.gskinner.com/RegExr/
__________________
HWU Rugby Group :'( - FAQ Processori - Aurea Sectio - CogitoWeb: idee varie sviluppando nel web
MaxArt è offline   Rispondi citando il messaggio o parte di esso
Old 02-05-2013, 06:37   #11
pl1957
Junior Member
 
Iscritto dal: Jul 2008
Messaggi: 18
Testato a terminale con le opzioni -v-n per simulare senza fare casini.
Funziona alla grandissima. Grazie!
pl1957 è offline   Rispondi citando il messaggio o parte di esso
Old 23-05-2013, 15:55   #12
DioBrando
Senior Member
 
Iscritto dal: Jan 2003
Città: Milano - Udine
Messaggi: 9411
Per quanto riguarda il framework .NET, confermo, è davvero completo.

Di recente ho dovuto fare un lavoro di ottimizzazione delle espressioni regolari utilizzate su un'applicazione MVC piuttosto complessa e ho scoperto opzioni davvero interessanti, tra cui la possibilità di compilare ed utilizzarle in un assembly ad hoc oppure on the fly, oltre all'interpretazione usata by design dal runtime.
Nella Base Class Library, è l'oggetto Regex ad identificare il motore che si fa carico di manipolare le espressioni regolari.


Consiglio la lettura (avida) dei 3 articoli scritti su MSDN.
Parte 1.
DioBrando è offline   Rispondi citando il messaggio o parte di esso
Old 09-12-2018, 11:23   #13
lcd86
Member
 
L'Avatar di lcd86
 
Iscritto dal: Aug 2006
Messaggi: 105
sostituzione regex con la stessa

buongiorno
vorrei chiedere come sostituire la stessa regex impostata come origine e cambiare solo una parte della stessa come risultato

la regex è questa

\d?\dx\d\d (\p{Ll} in questo caso è
numero numero x numero numero spazio seguito da un carattere minuscolo

ecco io vorrei convertire solo il carattere minuscolo in maiuscolo e lasciare il resto così come è o altrimenti cercare qualsiasi soluzione alternativa che restituisce lo stesso risultato

grazie
lcd86 è offline   Rispondi citando il messaggio o parte di esso
Old 12-12-2018, 17:53   #14
lcd86
Member
 
L'Avatar di lcd86
 
Iscritto dal: Aug 2006
Messaggi: 105
Quote:
Originariamente inviato da lcd86 Guarda i messaggi
buongiorno
vorrei chiedere come sostituire la stessa regex impostata come origine e cambiare solo una parte della stessa come risultato

la regex è questa

\d?\dx\d\d (\p{Ll} in questo caso è
numero numero x numero numero spazio seguito da un carattere minuscolo

ecco io vorrei convertire solo il carattere minuscolo in maiuscolo e lasciare il resto così come è o altrimenti cercare qualsiasi soluzione alternativa che restituisce lo stesso risultato

grazie
nessuno mi può aiutare? accetto consigli e suggerimenti
lcd86 è offline   Rispondi citando il messaggio o parte di esso
Old 13-12-2018, 12:02   #15
Ed_Bunker
Senior Member
 
L'Avatar di Ed_Bunker
 
Iscritto dal: Jan 2004
Città: Montignoso(MS)
Messaggi: 8785
a occhio e croce quell'espressione regolare mi sembra sintatticamente scorretta: cosa sarebbe quella parentesi tonda aperta (senza relativa parentesi chiusa) ?
__________________
- Ɔ Ʒ И Ƨ Ω Я Ʒ Đ -
Ed_Bunker è offline   Rispondi citando il messaggio o parte di esso
Old 14-12-2018, 14:07   #16
lcd86
Member
 
L'Avatar di lcd86
 
Iscritto dal: Aug 2006
Messaggi: 105
Quote:
Originariamente inviato da Ed_Bunker Guarda i messaggi
a occhio e croce quell'espressione regolare mi sembra sintatticamente scorretta: cosa sarebbe quella parentesi tonda aperta (senza relativa parentesi chiusa) ?
hai ragione, ho fatto un copia incolla ad una precedente espressione per i gruppi ed è effettivamente sbagliata... considerala senza quella parentesi aperta... a dire la verità.. l'ho copiata così grezza senza niente solo per dare una idea di quello che ho scritto, ma non so se devo scriverla in altri modi...

mi puoi aiutare sulla richiesta che avevo fatto?

Ultima modifica di lcd86 : 14-12-2018 alle 14:10. Motivo: aiuto alla richiesta
lcd86 è offline   Rispondi citando il messaggio o parte di esso
Old 19-12-2018, 11:01   #17
lcd86
Member
 
L'Avatar di lcd86
 
Iscritto dal: Aug 2006
Messaggi: 105
nessuno mi può aiutare?

cercavo semplicemente la sintassi per la conversione minuscolo della prima lettera in maiuscolo di una espressione regolare...

Ultima modifica di lcd86 : 20-12-2018 alle 10:06.
lcd86 è offline   Rispondi citando il messaggio o parte di esso
 Rispondi


Black Friday 2020! Siamo ''live'' con tutti gli sconti Black Friday 2020! Siamo ''live'' con tutti gli ...
Watch Dogs Legion: Ray Tracing e prestazioni con 15 schede video Watch Dogs Legion: Ray Tracing e prestazioni con...
Tophost, la soluzione ideale (e conveniente) per Wordpress Tophost, la soluzione ideale (e conveniente) per...
Sony Xperia 5 II, ecco il migliore smartphone Sony del 2020. La recensione Sony Xperia 5 II, ecco il migliore smartphone So...
Samsung Galaxy Book S: il notebook compatto con CPU Intel Lakefield Samsung Galaxy Book S: il notebook compatto con ...
Il Black Friday di Unieuro: Apple AirPod...
MakeAmazonPay è la campagna inter...
YouTube in 8K arriva anche sulle Android...
Il Regno Unito alle prese con l'e-waste:...
Apple prepara il ritorno di Force Touch?...
Windows 10 20H2 quasi sul 10% dei comput...
Il Black Friday di Lenovo: le offerte su...
La Francia tira dritto e batte cassa con...
Super Smash Bros.: Nintendo interviene p...
Il Black Friday va avanti nel weekend co...
Re-Factory: Renault dedica un intero sit...
Syneto punta su cloud e cybersecurity
Windows funziona sui Mac con chip M1: un...
Photokina sospesa fino a nuovo ordine. A...
Sigma presenterà tre obiettivi per Sony ...
Opera Portable
Opera 72
FurMark
3DMark
GPU Caps Viewer
Zoom Player Free
CrystalDiskMark
OCCT
HWMonitor
K-Lite Codec Pack Update
K-Lite Mega Codec Pack
K-Lite Codec Pack Full
K-Lite Codec Pack Standard
K-Lite Codec Pack Basic
Process Lasso
Tutti gli articoli Tutte le news Tutti i download

Strumenti

Regole
Non Puoi aprire nuove discussioni
Non Puoi rispondere ai messaggi
Non Puoi allegare file
Non Puoi modificare i tuoi messaggi

Il codice vB è On
Le Faccine sono On
Il codice [IMG] è On
Il codice HTML è Off
Vai al Forum


Tutti gli orari sono GMT +1. Ora sono le: 12:50.


Powered by vBulletin® Version 3.6.4
Copyright ©2000 - 2020, Jelsoft Enterprises Ltd.
Served by www1v