ToolZen ToolZen
Developer checker client

Regex Tester

Test regular expressions with real-time match highlighting, capture group display, replace output, and multi-flag support.

Advertisement (Top Banner)
/ /

Flags: g = global, i = case-insensitive, m = multiline, s = dotAll, u = unicode

Use $1, $2… for capture groups

Advertisement (Bottom)

Un'espressione regolare è un piccolo programma, e come ogni programma è più facile da correggere se la si guarda girare. Scrivi un pattern, incolla del testo, e i match si accendono mentre digiti. Qui gira il motore regex di JavaScript, e la cosa conta: i dialetti regex differiscono più di quanto ci si aspetti.

Certi pattern possono bloccare per ore, per questo qui girano in un worker

Il motore regex di JavaScript fa backtracking, e il backtracking può esplodere. L'esempio da manuale è (a+)+b: contro una stringa di trenta "a" senza nessuna "b", il motore prova ogni modo di ripartire quei trenta caratteri tra il quantificatore interno e quello esterno — oltre un miliardo di percorsi — prima di concludere che non c'è match. Aggiungi cinque caratteri e ci mette trenta volte tanto.

Non è teoria. Esegui quel pattern in un tester ingenuo e la scheda del browser si blocca del tutto, perché una regex in esecuzione non è interrompibile dall'interno di JavaScript. Questo strumento quindi valuta il tuo pattern in un Web Worker, su un thread separato, con un budget di un secondo. Se il pattern lo sfonda, il worker viene ucciso e ti viene detto che il pattern è patologico, invece di lasciarti guardare la pagina morire. La scheda resta reattiva per tutto il tempo.

La lezione vale ben oltre questa pagina: se pattern forniti dagli utenti arrivano a un motore regex sul tuo server, quel motore si può bloccare allo stesso modo. Ha un nome — ReDoS — e ha messo fuori uso servizi veri.

Il flag g fa sì che la tua regex si ricordi le cose

È il bug regex di JavaScript che costa più ore di debug, e quando ti colpisce sembra magia nera. Un oggetto regex col flag g mantiene una proprietà lastIndex, e sia .test() sia .exec() la fanno avanzare. Quindi chiamare lo stesso test due volte sulla stessa stringa dà risposte diverse.

Provalo in una console: const re = /a/g; re.test("a") restituisce true, poi re.test("a") restituisce false, poi di nuovo true. Alterna all'infinito. La prima chiamata trova il match in posizione 0 e imposta lastIndex a 1; la seconda cerca a partire dalla posizione 1, non trova nulla, e riazzera lastIndex a 0. Niente è rotto: la regex si comporta esattamente come da specifica, ed è la specifica a essere sorprendente.

Le regole pratiche: non conservare mai una regex col flag g in una costante a livello di modulo per poi riusarla con .test(). Se ti serve solo una risposta sì/no, togli la g. Se ti serve, costruisci la regex da capo a ogni uso, oppure riazzera tu lastIndex. Questo strumento aggira tutto costruendo una regex nuova a ogni tasto premuto.

JavaScript non è PCRE

Un pattern preso da una risposta su Perl, PHP o Python spesso funzionerà anche qui, e ogni tanto no, in modi facili da non notare. JavaScript non ha gruppi atomici né quantificatori possessivi: le due funzionalità che gli altri dialetti offrono proprio per impedire l'esplosione di backtracking descritta sopra. Non ha ricorsione, quindi i classici pattern per "trovare parentesi bilanciate" sono semplicemente impossibili.

Il lookbehind in JavaScript esiste, da ES2018, ma è arrivato tardi: Safari l'ha introdotto solo nella versione 16.4, a marzo 2023. Se supporti dispositivi iOS più vecchi, un pattern che usa (?<=...) non è che non troverà il match: lancerà un SyntaxError quando la regex viene costruita, portandosi giù lo script. È un fallimento molto più rumoroso di un match mancato, e conviene saperlo prima di metterne uno in produzione.

I flag, in breve

g trova ogni match invece di fermarsi al primo. i ignora maiuscole e minuscole. m cambia il significato di ^ e $: li fa corrispondere ai confini di riga invece che solo all'inizio e alla fine dell'intera stringa, che è quello che di solito si vuole testando su testo multiriga incollato. s fa sì che il punto corrisponda anche agli a capo; senza, il punto non attraversa un'interruzione di riga, il che spiega un numero sorprendente di domande tipo "perché il mio pattern si ferma a fine riga".

u attiva la gestione Unicode corretta. Senza, il motore lavora in unità di codice UTF-16, quindi una singola emoji conta come due caratteri e una classe di caratteri può tagliarla a metà. Se il tuo testo contiene qualcosa oltre il piano multilingue di base, ti serve u.

Quando non usare una regex

Le regex trovano pattern in testo piatto. Non sanno contare, non ricordano annidamenti arbitrari, e non hanno alcun concetto di struttura. HTML, JSON e codice sorgente si annidano tutti, ed è per questo che ogni tentativo di analizzarli con una regex funziona sugli esempi e fallisce sui dati veri. Usa un parser: ogni linguaggio ne ha uno.

Gli indirizzi email meritano un avvertimento a parte. La grammatica della RFC 5322 permette commenti, stringhe quotate e costrutti annidati, e le regex che la implementano fedelmente arrivano a migliaia di caratteri. Nel frattempo un indirizzo valido può comunque rimbalzare, e uno dall'aria "non valida" può essere perfettamente recapitabile. Controlla che ci sia una @ con qualcosa da entrambi i lati, poi manda una mail di conferma: è l'unico test che dimostri davvero qualcosa.

Domande frequenti

Il mio pattern o il mio testo vengono inviati a un server?

No. Questo strumento è marcato "client": il pattern gira in un Web Worker dentro il tuo browser, che è un thread separato sulla tua macchina, non una macchina remota. Non viene trasmesso niente e non viene conservato niente.

Perché mi dice "Questo pattern impiega troppo"?

Il tuo pattern ha sforato il budget di un secondo, il che in pratica significa sempre backtracking catastrofico e non un lavoro davvero grosso. Cerca quantificatori annidati — un gruppo che finisce con + o * e che è a sua volta ripetuto, tipo (a+)+ o (\d*)* — e alternative i cui rami possono corrispondere allo stesso testo. Rendere più specifica la parte interna di solito fa crollare il tempo da minuti a microsecondi.

Il mio pattern funziona qui ma non nel mio codice. Perché?

Due sospetti abituali. Il primo è lastIndex, descritto sopra: probabilmente il tuo codice riusa un solo oggetto regex col flag g dove questo strumento ne costruisce uno nuovo ogni volta. Il secondo è l'escape. In una stringa letterale JavaScript, "\d" è solo "d": ti serve "\\d", o meglio un letterale regex come /\d+/ dove non c'è nessun livello di escape di stringa. Il campo pattern qui prende il pattern grezzo, senza nessuno strato di stringa in mezzo.

Come faccio a trovare un punto letterale, o un carattere speciale?

Mettici davanti un backslash: \. corrisponde a un punto invece che a "qualsiasi carattere". I caratteri che vogliono questo trattamento sono . * + ? ^ $ { } ( ) | [ ] \ — e dentro una classe di caratteri le regole si rilassano, quindi anche [.] funziona ed è spesso più leggibile. Se stai facendo l'escape di una stringa arrivata da input utente, fallo via codice e non a mano.

Perché $ non corrisponde alla fine di ogni riga?

Perché senza il flag m, ^ e $ si ancorano all'intera stringa invece che a ogni riga. Aggiungi m ai flag e corrisponderanno a ogni confine di riga. È un inciampo continuo quando si testa un pattern su input multiriga incollato, perché il pattern è giusto e sono i flag a non esserlo.