Articolo originale: https://www.freecodecamp.org/news/regular-expressions-for-beginners/

Le espressioni regolari (regex) sono un utile strumento di programmazione. Sono fondamentali per elaborare del testo in modo efficiente. A uno sviluppatore può tornare molto utile sapere come risolvere problemi usando le espressioni regolari, inoltre migliora la produttività.

In questo articolo, imparerai i fondamenti delle espressioni regolari, la loro notazione, come interpretare un semplice pattern regex e come scrivere dei pattern regex tu stesso. Iniziamo!

Cosa sono le espressioni regolari?

Le espressioni regolari sono dei pattern (dei motivi o modelli che corrispondono a specifiche sequenze di testo) che consentono di descrivere, trovare delle corrispondenze o analizzare un testo. Con le espressioni regolari puoi fare cose come trovare e sostituire del testo, verificare che un input di dati corrisponda al formato richiesto e altre cose simili.

Ecco un possibile utilizzo: vuoi verificare che il numero di telefono inserito in un modulo da un utente corrisponda a un formato, diciamo ###-###-#### (dove # rappresenta un numero). Un modo per risolvere questa situazione potrebbe essere:

function isPattern(userInput) {
  if (typeof userInput !== 'string' || userInput.length !== 12) {
    return false;
  }
  for (let i = 0; i < userInput.length; i++) {
    let c = userInput[i];
    switch (i) {
      case 0:
      case 1:
      case 2:
      case 4:
      case 5:
      case 6:
      case 8:
      case 9:
      case 10:
      case 11:
        if (c < 0 || c > 9) return false;
        break;
      case 3:
      case 7:
        if (c !== '-') return false;
        break;
    }
  }
  return true;
}

Alternativamente, possiamo usare un'espressione regolare come questa:

function isPattern(userInput) {
  return /^\d{3}-\d{3}-\d{4}$/.test(userInput);
}

Abbiamo eseguito il refactoring del codice usando regex. Fantastico, vero? Tutto grazie al potere delle espressioni regolari.

Come creare un'espressione regolare

In JavaScript, puoi creare un'espressione regolare in due modi:

  • Metodo #1: usando un'espressione regolare letterale, che consiste di un pattern racchiuso tra barre oblique. Puoi scriverla con o senza flag (vedremo a breve cosa vuol dire flag). La sintassi è la seguente:
const regExpLiteral = /pattern/;          // Senza flag

const regExpLiteralWithFlags = /pattern/flag; // Con flag

Le barre oblique /…/ indicano che stiamo creando il pattern di un'espressione regolare, allo stesso modo in cui usiamo le virgolette “ ” per creare una stringa.

  • Metodo #2: usando la funzione constructor RegExp. La sintassi è la seguente:
new RegExp(pattern [, flag])

In questo caso il pattern è racchiuso tra virgolette, così come il parametro flag, che è opzionale.

Dunque quando utilizziamo questi due metodi?

Dovresti utilizzare un'espressione regolare letterale quando conosci il pattern al momento della scrittura del codice

D'altro canto, dovresti utilizzare il costruttore Regex se il pattern deve essere creato dinamicamente. Il costruttore regex ti permette di scrivere un pattern usando un template literal, cosa che non è possibile con la sintassi regex letterale.

Cosa sono i flag nelle espressioni regolari?

I flag o modificatori sono caratteri che abilitano funzionalità di ricerca avanzata come la ricerca globale o case-insensitive (che non tiene conto di maiuscole e minuscole). Puoi usarli da soli o congiuntamente. Alcuni di quelli usati comunemente sono:

  • g: usato per la ricerca globale – la ricerca non si fermerà dopo la prima corrispondenza.
  • i: usato per la ricerca case-insensitive – la corrispondenza può avvenire indipendentemente da maiuscole e minuscole.
  • m: usato per la ricerca multi-riga.
  • u: usato per la ricerca Unicode.

Diamo un'occhiata ad alcuni pattern di espressioni regolari usando entrambe le sintassi.

Come usare le espressioni regolari letterali:

// Sintassi: /pattern/flag

const regExpStr = 'Hello world! hello there';

const regExpLiteral = /Hello/gi;

console.log(regExpStr.match(regExpLiteral));

// Output: ['Hello', 'hello']

Senza utilizzare il flag i, sarebbe stato restituito solo Hello.

/Hello/ è un esempio di pattern semplice, che consiste di caratteri che devono apparire letteramente nel testo bersaglio. Affinché ci sia corrispondenza, il testo bersaglio deve contenere la stessa sequenza del pattern.

Ad esempio, se riscrivi il testo dell'esempio precedente e provi a effettuare un riscontro:

const regExpLiteral = /Hello/gi;

const regExpStr = 'oHell world, ohell there!';

console.log(regExpStr.match(regExpLiteral));

// Output: null

Ottieni null perché i caratteri nella stringa non appaiono come specificato nel pattern. Quindi un pattern letterale come /hello/ vuol dire esattamente h, seguita da e, seguita da l, seguita da l, seguita da o.

Come usare il costruttore RegExp:

// Sintassi: RegExp(pattern [, flag])

const regExpConstructor = new RegExp('xyz', 'g'); // Con flag -g

const str = 'xyz xyz';

console.log(str.match(regExpConstructor));

// Output: ['xyz', 'xyz']

Qui il pattern xyz viene passato come una stringa, così come il flag. La presenza di xyz viene riscontrata due volte per via del flag -g. Senza di esso avremmo ottenuto solo il primo riscontro.

Possiamo anche passare dei pattern creati dinamicamente come template literal, usando la funzione costruttore. Ad esempio:

const pattern = prompt('Inserisci un pattern');
// Ipotizziamo di aver inserito 'xyz'

const regExpConst = new RegExp(`${pattern}`, 'gi');

const str = 'xyz XYZ';

console.log(str.match(regExpConst)); // Output: ['xyz', 'XYZ']

Come usare i caratteri speciali delle espressioni regolari

Un carattere speciale in un'espressione regolare è un carattere con un significato riservato. Usando i caratteri speciali, puoi fare ben altro che trovare semplicemente una corrispondenza diretta.

Ad esempio, se vuoi trovare un carattere che può o non può apparire una o più volte in una stringa, puoi farlo con dei caratteri speciali. Questi caratteri rientrano in diversi sottogruppi che svolgono funzioni simili.

Diamo un'occhiata a ogni sottogruppo e ai caratteri al loro interno.

Ancoraggi e confini:

Gli ancoraggi (anchor) sono metacaratteri che indicano l'inizio e la fine di una riga del testo in esame. Si utilizzano per imporre la posizione di un confine.

I due caratteri usati sono ^ e $.

  • ^ trova la corrispondenza all'inizio di una riga e ancora il pattern letterale al suo inizio. Ad esempio:
const regexPattern1 = /^cat/;

console.log(regexPattern1.test('cat and mouse')); // Output: true

console.log(regexPattern1.test('The cat and mouse')); // Output: false
//perché la riga non inizia con cat

// Senza ^ nel pattern, l'output restituirà true
// perché non abbiamo imposto un confine.

const regexPattern2 = /cat/;

console.log(regexPattern2.test('The cat and mouse')); // Output: true
  • $ trova la corrispondenza alla fine di una riga e ancora il pattern letterale alla sua fine. Ad esempio:
const regexPattern = /cat$/;

console.log(regexPattern.test('The mouse and the cat')); // Output: true

console.log(regexPattern.test('The cat and mouse')); // Output: false

I caratteri di ancoraggio ^ e $ trovano la corrispondenza in base alla posizione dei caratteri del pattern e non all'effettiva presenza dei caratteri stessi.

I confini di parole (word boundary) sono metacaratteri che trovano la corrispondenza all'inizio e alla fine di una parola – una sequenza di caratteri alfanumerici. Puoi immaginarli come una versione basata sulle parole di ^ e $.  Puoi usare i metacaratteri b e B per imporre un confine di parola.

  • \b trova la corrispondenza all'inizio o alla fine di una parola, a seconda della posizione del metacarattere. Ecco un esempio:
// Sintassi 1: /\b.../ dove ... rappresenta una parola.

// Indica una parola che inizia con il pattern ward
const regexPattern1 = /\bward/gi;

const text1 = 'backward Wardrobe Ward';

console.log(text1.match(regexPattern1)); // Output: ['Ward', 'Ward']

// Sintassi 2: /...\b/

// Indica una parola che finisce con il pattern ward
const regexPattern2 = /ward\b/gi;

const text2 = 'backward Wardrobe Ward';

console.log(text2.match(regexPattern2)); // Output: ['ward', 'Ward']

// Sintassi 3: /\b....\b/

// Indica una parola a sé stante costituita dal pattern ward
const regexPattern3 = /\bward\b/gi;

const text3 = 'backward Wardrobe Ward';

console.log(text3.match(regexPattern3)); // Output: ['Ward']
  • \B è l'opposto di \b . Trova corrispondenza in ogni posizione in cui \b non la trova.

Codici per altri metacaratteri:

In aggiunta ai metacaratteri che abbiamo visto, ecco alcuni di quelli usati più di frequente:

  • \d – corrisponde a ogni cifra decimale ed è una scorciatoia per [0-9].
  • \w – corrisponde a ogni carattere alfanumerico che può essere una lettera, una cifra o un trattino basso. \w è una scorciatoia per [A-Za-z0-9_].
  • \s – corrisponde allo spazio vuoto.
  • \D – corrisponde a qualsiasi "non numero" ed è equivalente a [^0-9.]
  • \W – corrisponde a qualsiasi carattere non alfanumerico è una una scorciatoia per [^A-Za-z0-9_].
  • \S – corrisponde a ogni carattere che non è spazio vuoto.
  • . – corrisponde a ogni carattere.

Cos'è la classe di caratteri?

Una classe di caratteri viene usata per trovare corrispondenza con un insieme di caratteri in una particolare posizione. Per indicare una classe di caratteri si utilizzano le parentesi quadre [] contenenti la lista dei caratteri desiderati.

Guardiamo l'esempio:

// Indica una parola con due alternative di spelling

const regexPattern = /ambi[ea]nce/;

console.log(regexPattern.test('ambience')); // Output: true

console.log(regexPattern.test('ambiance')); // Output: true

// Il pattern regex si interpreta come:  indica a seguita da m, poi b,
// poi i, poi e o a, poi n, poi c e poi e.

Cos'è una classe di caratteri negati?

Se aggiungi un accento circonflesso all'interno di una classe di caratteri, [^...], la corrispondenza avverrà con ogni carattere che non è elencato nelle parentesi quadre. Ad esempio:

const regexPattern = /[^bc]at/;

console.log(regexPattern.test('bat')); // Output: false

console.log(regexPattern.test('cat')); // Output: false

console.log(regexPattern.test('mat')); // Output: true

Cos'è un intervallo?

Un trattino - indica un intervallo, se usato all'interno di una classe di caratteri. Supponi di voler trovare una serie di numeri, diciamo [0123456789], o un insieme di caratteri come [abcdefg]. Puoi scriverli in forma di intervallo, rispettivamente come [0-9] e [a-g].

Cos'è l'alternanza?

L'alternanza è un altro modo per specificare un insieme di opzioni. Occorre usare una barra verticale | per trovare corrispondenza con una qualsiasi sotto-espressione. Ciascuna delle sotto-espressioni è detta alternativa.

La barra verticale significa ‘o’, quindi trova corrispondenza tra una serie di opzioni. Consente di combinare sotto-espressioni come alternative.

Ad esempio, (x|y|z)a troverà xa o ya, oppure za.  Per limitare la portata dell'alternanza puoi usare le parentesi e raggruppare insieme le alternative.

Senza parentesi, x|y|za vorrebbe dire x o y o za. Ad esempio:

const regexPattern = /(Bob|George)\sClan/;

console.log(regexPattern.test('Bob Clan')); // Output: true

console.log(regexPattern.test('George Clan')); // Output: true

Cosa sono i quantificatori e l'avidità?

I quantificatori indicano quante volte un carattere, una classe di caratteri o un gruppo dovrebbe apparire nel testo bersaglio affinché ci sia una corrispondenza. Eccone alcuni particolari:

  • + trova ogni carattere a cui viene apposto se questo è presente almeno una volta. Ad esempio:
const regexPattern = /hel+o/;

console.log(regexPattern.test('helo'));          // Output: true

console.log(regexPattern.test('hellllllllllo')); // Output: true

console.log(regexPattern.test('heo'));           // Output: false
  • * è simile al precedente ma con una leggera differenza. Trova ogni carattere a cui viene apposto se questo è presente zero o più volte. Ad esempio:
const regexPattern = /hel*o/;

console.log(regexPattern.test('helo'));    // Output: true

console.log(regexPattern.test('hellllo')); // Output: true

console.log(regexPattern.test('heo'));     // Output: true

// * trova 0 o qualsiasi numero di 'l'
  • ? significa "opzionale". Quando apposto a un carattere, vuol dire che questo può o non può essere presente. Ad esempio:
const regexPattern = /colou?r/;

console.log(regexPattern.test('color'));  // Output: true

console.log(regexPattern.test('colour')); // Output: true

// Il ? dopo il carattere u lo rende opzionale
  • {N}, quando apposto a un carattere o a una classe di caratteri, specifica il numero di caratteri desiderato. Ad esempio /\d{3}/ corrisponde a tre cifre consecutive.
  • {N,M} è detto quantificatore di intervallo ed è usato per specificare un intervallo tra possibile corrispondenza minima e massima. Ad esempio /\d{3, 6}/ trova un minimo di 3 e un massimo di 6 cifre consecutive.
  • {N, } indica un intervallo aperto. Ad esempio /\d{3, }/ trova tre o più cifre consecutive.

Cos'è l'avidità in regex?

Tutti i quantificatori sono greedy (avidi) di default. Ciò vuol dire che provano a trovare una corrispondenza di tutti i caratteri possibili.

Per rimuovere questo stato predefinito e renderli non-greedy, aggiungi un ? agli operatori, come +?, *?, {N}?, {N,M}? e così via.

Cosa sono raggruppamenti e backreference?

Precedentemente abbiamo visto come limitare la portata dell'alternanza usando le parentesi.

E se volessi usato un quantificatore come + o * su più di un carattere alla volta – diciamo una classe di caratteri o un gruppo? Puoi raggrupparli insieme usando le parentesi prima di aggiungere il quantificatore, proprio come in questo esempio:

const regExp = /abc+(xyz+)+/i;

console.log(regExp.test('abcxyzzzzXYZ')); // Output: true

Ecco cosa vuol dire il pattern: il primo + si riferisce alla c in abc, il secondo + alla z in xyz, e il terzo + alla sotto-espressione xyz (ci sarà corrispondenza anche nel caso la sequenza si ripeta).

Il backreference consente di trovare un nuovo pattern uguale a uno trovato precedentemente in un'espressione regolare. Anche in questo caso si usano le parentesi, per racchiudere una sotto-espressione trovata precedentemente (ovvero un gruppo di acquisizione).

Tuttavia, è possibile avere più di un gruppo di acquisizione in un'espressione regolare. Quindi per fare riferimento a un gruppo di acquisizione si utilizza un numero che identifica le parentesi.

Supponi di avere 3 gruppi di acquisizione in un'espressione regolare e di volervi fare riferimento. Puoi usare \1, \2 o \3, per riferirti alla prima, seconda o terza parentesi. Per numerare le parentesi si inizia a contare le parentesi aperte da sinistra.

Vediamo alcuni esempi:

(x) trova x e memorizza la corrispondenza.

const regExp = /(abc)bar\1/i;

// abc sfrutta il backreference ed è ancorata alla posizione \1
console.log(regExp.test('abcbarAbc')); // Output: true

console.log(regExp.test('abcbar')); // Output: false

(?:x) trova x ma non memorizza la corrispondenza. In questo caso, \n (dove n è un numero) non fa riferimento a un gruppo di acquisizione precedente e la corrispondenza avverrà se letterale. Usando un esempio:

const regExp = /(?:abc)bar\1/i;

console.log(regExp.test('abcbarabc')); // Output: false

console.log(regExp.test('abcbar\1')); // Output: true

La regola di escape

Per apparire letteralmente in un'espressione regolare, un metacarattere necessita di escape tramite barra rovesciata. In questo modo, il metacarattere perde il suo significato speciale all'interno della regex.

Metodi di espressioni regolari

Il metodo test()

Abbiamo usato questo metodo parecchie volte in questo articolo. Il metodo test() confronta il testo bersaglio con l'espressione regolare e restituisce un valore booleano. Se c'è corrispondenza restituisce true (vero), altrimenti false (falso).

const regExp = /abc/i;

console.log(regExp.test('abcdef')); // Output: true

console.log(regExp.test('bcadef')); // Output: false

Il metodo exec()

Il metodo exec() confronta il testo bersaglio con il pattern regex. Se c'è corrispondenza, restituisce un array con gli elementi trovati – altrimenti restituisce null. Ad esempio:

const regExp = /abc/i;

console.log(regExp.exec('abcdef'));
// Output: ['abc', index: 0, input: 'abcdef', groups: undefined]

console.log(regExp.exec('bcadef'));
// Output: null

Esistono anche metodi di stringhe che accettano espressioni regolari come parametro, come match(), replace(), replaceAll(), matchAll(), search() e split().

Esempi di regex

Ecco alcuni esempi per consolidare i concetti che hai imparato in questo articolo.

Primo esempio: come usare un pattern regex per trovare un indirizzo email:

const regexPattern = /^[(\w\d\W)+]+@[\w+]+\.[\w+]+$/i;

console.log(regexPattern.test('abcdef123@gmailcom'));
// Output: false
//Punto mancante

console.log(regexPattern.test('abcdef123gmail.'));
// Output: false
//'com' mancante

console.log(regexPattern.test('abcdef123@gmail.com'));
// Output: true
//L'input corrisponde al pattern

Interpretiamo il pattern. Ecco cosa sta accadendo:

  • / rappresenta l'inizio del pattern dell'espressione regolare.
  • ^ identifica l'inizio della riga con i caratteri nella classe di caratteri.
  • [(\w\d\W)+ ]+ corrisponde a qualsiasi parola, cifra o carattere non alfanumerico nella classe di caratteri presente almeno una volta. Nota come le parentesi sono state usate per raggruppare i caratteri prima di aggiungere il quantificatore. È equivalente a [\w+\d+\W+]+ .
  • @ identifica la @ (letterale) all'interno dell'email.
  • [\w+]+ corrisponde a qualsiasi lettera nella classe di caratteri presente almeno una volta.
  • \. corrisponde al punto, di cui è stato eseguito l'escape per farlo apparire come carattere letterale.
  • [\w+]+$ corrisponde a qualsiasi lettera nella classe. Inoltre, la classe di caratteri è ancorata alla fine della riga.
  • / - fine del pattern

Prossimo esempio: come trovare un URL con formato http://example.com o https://www.example.com:

const pattern = /^[https?]+:\/\/((w{3}\.)?[\w+]+)\.[\w+]+$/i;

console.log(pattern.test('https://www.example.com'));
// Output: true

console.log(pattern.test('http://example.com'));
// Output: true

console.log(pattern.test('https://example'));
// Output: false

Interpretiamo il pattern. Ecco cosa sta accadendo:

  • /...../ rappresenta l'inizio e la fine dell'espressione regolare
  • ^ identifica l'inizio della riga
  • [https?]+ corrisponde ai caratteri elencati almeno uno volta, tuttavia ? rende la 's' opzionale.
  • : corrisponde ai due punti.
  • \/\/ esegue l'escape delle due barre oblique.
  • (w{3}\.)? corrisponde al carattere 'w' 3 volte e al punto che segue immediatamente. Il gruppo è opzionale.
  • [\w+]+ corrisponde ai caratteri nella classe, almeno una volta.
  • \. esegue l'escape del punto
  • [\w+]+$ corrisponde a qualsiasi carattere nella classe. La classe di caratteri è ancorata alla fine della riga.

Conclusione

In questo articolo, abbiamo esaminato i fondamenti delle espressioni regolari. Abbiamo anche spiegato alcuni pattern di espressioni regolari e fatto pratica con degli esempi.

C'è molto altro sulle espressioni regolari oltre questo articolo. Per aiutarti a imparare, ecco alcune risorse (in inglese) che puoi consultare:

È tutto per questo tutorial. Buona programmazione :)