Ultimo aggiornamento: 1-Lug-2023

Regular expressions

Cosa sono?

Le “regular expressions” (o regex in breve) sono un linguaggio usato per analizzare e manipolare le stringhe di testo in maniera complessa. Una regex è una “stringa che identifica un insieme di stringhe”. Le regular expression sono incluse in molti linguaggi di programmazione (a partire da Unix negli anni settanta, per poi diffondersi maggiormente con Perl) tra cui anche SQL di DB2 for i a partire dalla versione IBM i 7.1.

Una regular expression è una stringa che contiene una combinazione di caratteri e metacaratteri. Il pattern matching operato dal regular expression engine cerca una porzione di testo che è descritta dalla regular expression.

Prerequisiti

  • IBM i 7.1 TR9, IBM i 7.2 TR1, IBM i 7.3 e successivi
  • 5770SS1 opz. 39 Internation Component for Unicode (free of charge)
    • Se questa opzione non è installata si riceverà l’errore SQL0204 / SQLState = 42704 “QQQSVREG in QSYS type *SRVPGM was not found”

Funzioni ed operatori in DB2 for i

Le regular expressions sono valutate usando l’International Components for Unicode (ICU) regular expression API (cfr. http://site.icu-project.org/home e https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_73/apis/icu.htm).

  • Funzioni:
    • regexp_count: restituisce il numero di occorrenze trovato in una stringa della regular expression
    • regexp_instr: restituisce la posizione iniziale o finale dell’occorrenza trovata in una stringa della regular expression
    • regexp_replace: sostituisce la regular expression trovata in una stringa con un’altra stringa
    • regexp_substr: restituisce la sottostringa dell’occorrenza trovata in una stringa della regular expression
  • Operatore:
    • regexp_like: questo predicato usato in una clausola where cerca la pattern-expression nella stringa source-string. Se la trova restituisce true altrimenti false. La dimensione massima di source-string è 1 Gb. La dimensione massima di pattern-expression è 32 Kb.
      • regexp_like(source-string, pattern-expression, start, flags)

Flag values

Il parametro flags nelle funzioni e nel predicato delle regular expressions condiziona la modalità di analisi della stringa. I flag possono anche essere combinati.

  • c: ricerca case sensitive. E’ il valore di default.
  • i: ricerca case insensitive
  • m: specifica che la stringa di origine può contenere una o più righe
  • n o s: specifica che il carattere ‘.’ nella regular expression identifica la ricerca del fine riga
  • x: specifica che gli spazi nella regular expression sono ignorati

Esempi

Di seguito propongo alcuni esempi pratici di utilizzo delle regular expressions per effettuare dei controlli di validità formali di stringhe che debbano rispettare alcune regole di composizione: p.es. indirizzi email, indirizzi IP, codice fiscale, partita IVA…

Per una trattazione completa della sintassi delle regular expressions si può consultare il manuale IBM “SQL Reference” (cap. 2 Language elements > Predicates > REGEXP_LIKE predicate).

Un sito molto utile per imparare e testare la sintassi delle regular expressions è https://regexr.com/ “RegExr is an online tool to learn, build, & test Regular Expressions”.

Gli esempi sono costruiti utilizzando l’operatore regexp_like, ma la sintassi è del tutto simile anche per le altre funzioni. Per brevità dopo il primo esempio ho indicato solo la porzione di codice dell’operatore regexp_like

  • controllo indirizzo IPv4
select count(*)
  from SYSIBM/SYSDUMMY1
  where regexp_like('192.168.10.14',
    '(\b(?:(?:2(?:[0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9])\.){3}(?:(?:2([0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9]))\b)$');

-- oppure
regexp_like('192.168.10.14',
    '(\b((([0-2]\d[0-5])|(\d{2})|(\d))\.){3}(([0-2]\d[0-5])|(\d{2})|(\d))\b)$')
  • controllo indirizzo IPv6
regexp_like('fe80:0000:0000:0000:02a0:24ff:fe77:4997', '(?:(?:(?:[A-F0-9]{1,4}:){6}|(?=(?:[A-F0-9]{0,4}:){0,6} (?:[0-9]{1,3}\.){3}[0-9]{1,3}$) (([0-9A-F]{1,4}:){0,5}|:)((:[0-9A-F]{1,4}){1,5}:|:))(?:(?:25[0-5]|2[0-4][0-9]| [01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:[A-F0-9]{1,4}:){7}[A-F0-9]{1,4}|(?=(?:[A-F0-9]{0,4}:){0,7}[A-F0-9]{0,4}$)(([0-9A-F]{1,4}:){1,7}|:)((:[0-9A-F]{1,4}){1,7}|:))', 'i')
  • controllo MAC address
regexp_like('01:23:45:67:89:ab', '([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$')
  • controllo data in formato dd-mm-yyyy:  separatori consentiti / o . o -. Controlla anche la correttezza della data 29 febbraio per gli anni bisestili.
regexp_like('29.02.2016',
    '^(((0[1-9]|[12][0-9]|3[01])[- /.](0[13578]|1[02])|(0[1-9]|[12][0-9]|30)[- /.](0[469]|11)|(0[1-9]|1\d|2[0-8])[- /.]02)[- /.]\d{4}|29[- /.]02[- /.](\d{2}(0[48]|[2468][048]|[13579][26])|([02468][048]|[13579][26])00))$')
  • controllo ora in formato 24H
regexp_like('01:00', '\b([01]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?')
  • controllo numero carta di credito Visa
regexp_like('4012345678901234', '^4[0-9]{12}(?:[0-9]{3})?$')
  • controllo numero carta di credito MasterCard
regexp_like('5112345678901234',
'^(?:5[1-5][0-9]{2}|222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}$')
  • controllo numero carta di credito American Express
regexp_like('341234567890123', '^3[47][0-9]{13}$')
  • controllo codice fiscale
regexp_like('RSSMRA70H25A940U', '^[a-z]{6}[0-9]{2}[a-z][0-9]{2}[a-z][0-9]{3}[a-z]$', 'i')
  • controllo partita IVA
regexp_like('01234567890', '^[0-9]{11}$')

Questa regular expression esegue un controllo minimale sulla correttezza della partita IVA.

Un controllo più preciso richiede un’espressione più complessa in quanto:

    • la prima cifra deve essere compresa tra 1 e 7 (i codici che cominciano con 8 o 9 sono codici fiscali di istituzioni statali, militari, giudiziarie, le persone giuridiche)
    • le successive 6 cifre possono essere qualsiasi tra 0 e 9
    • le cifre dalla ottava alla decima sono il codice ufficio provinciale (che vanno da 001 a 100, 120, 121 e poi i codici speciali 888 e 999)
    • l’ultima cifra che è il codice di controllo può essere da 0 a 9
regexp_like('12345670991', '^[0-7]{1}\d{6}(0{2}[1-9]{1}|0{1}[1-9]{1}\d{1}|100|120|121|888|999)\d{1}$')
  • controllo estensione file (nell’esempio viene controllato che il file abbia estensione jpeg o jpg o png o gif o bmp)
regexp_like('pippo.jpeg', '^.+\.(jpeg|jpg|png|gif|bmp)$')
  • estrazione nome file da un percorso completo: se si ha una variabile che contiene il percorso completo di un file e si desidera estrarre solo il nome del file viene molto comodo usare la funzione regexp_replace
-- estrarre da un percorso solo il nome file (stile Unix)
select regexp_replace('/home/myfolder/myfile.txt', '^.*/', ' ')
  from SYSIBM/SYSDUMMY1;
  
-- estrarre da un percorso solo il nome file (stile Windows)
select regexp_replace('\home\myfolder\myfile.txt', '^.*\\', ' ')
  from SYSIBM/SYSDUMMY1;

 -- risultato: myfile.txt
  • controllo numero di cellulare (nell’esempio viene controllato che il prefisso sia di 3 cifre, il numero di 6 o 7 cifre e i separatori siano / o – o .)
regexp_like('+39/329/1234567', '(00|\+)[0-9]{2}[\/|\-|\.]?[0-9]{3}[\/|\-|\.]?[0-9]{6,7}+$', 'i')
  • controllo indirizzo e-mail
regexp_like('foo@foo.com', '[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z_+])*@([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,9}$')

-- oppure
regexp_like('foo@foo.com', '[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$', 'i')

-- oppure
regexp_like('foo@foo.com', '[a-z0-9!#$%&''*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&''*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?')
  • controllo URL
regexp_like('http://www.google.it', '[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)', 'i')
  • controllo presenza caratteri estranei al set di caratteri latino: nell’esempio viene controllata la presenza di caratteri estranei al set di caratteri Basic Latin nel campo FIELD1. Se il campo FIELD1 è a lunghezza fissa bisogna usare la funzione trim per sopprimere i blanks finali.
regexp_like(trim(FIELD1), '\P{InBasicLatin}')

Dopo aver individuato i caratteri anomali con la funzione regexp_replace si possono sostituire con altri caratteri validi, p.es. uno spazio.

update MYTAB
  set FIELD1 = regexp_replace(trim(FIELD1), '\P{InBasicLatin}', ' ')
  where regexp_like(trim(FIELD1), '\P{InBasicLatin}');
  • controllo esatta composizione del codice di un prodotto: ipotizziamo che si desideri definire una regola per assegnare i codici che identificano i prodotti nel database dell’anagrafica articoli. La regola impone che il codice debba essere costruito così: 2 lettere maiuscole o minuscole + 2 lettere minuscole + 1 lettera (solo X o Y o Z) + ‘-‘ + 1 lettera maiuscola o 1 cifra (tranne le lettere X, Y, Z).
regexp_like('eGabY-3', '^[A-Za-z]{2}[a-z]{2}[XYZ]{1}-[^XYZ]')

Esempio in embedded SQL in RPG

Di seguito un esempio di utilizzo delle regular expressions per eseguire il controllo formale di validità del contenuto di una variabile in un programma RPG.

Viene esposto sia l’utilizzo dell’operatore regexp_like oppure della funzione regexp_count

dcl-s fValido uns(3);
dcl-s Valore varchar(100);
dcl-s Regola varchar(100);
dcl-s Msg char(52);
Valore = '4012345678901234';
Regola = '^4[0-9]{12}(?:[0-9]{3})?$';
exec sql
  select count(*) into :fValido
    from SYSIBM/SYSDUMMY1
    where regexp_like(:Valore, :Regola);
if fValido > *zeros;
  Msg = 'Controllo di validità corretto';
else;
  Msg = 'Controllo di validità errato';
endif;
dsply msg '*EXT';
exec sql
  set :fValido = regexp_count(:Valore, :Regola);
if fValido > *zeros;
  Msg = 'Controllo di validità corretto';
else;
  Msg = 'Controllo di validità errato';
endif;
dsply msg '*EXT';
*inlr = *on;

Esempio funzione scalare SQL

Di seguito un esempio di utilizzo delle regular expressions incapsulata in una funzione scalare SQL.

-- funzione di controllo carta credito Visa. Restituisce 1 se valido, 0 se non valido
create or replace function checkVisa(NumCarta varchar(16))
  returns integer
  language sql
  deterministic
  begin
    declare Regola char(50);
    declare fValido integer;
    set Regola = '^4[0-9]{12}(?:[0-9]{3})?$';
    set fValido = regexp_count(NumCarta, trim(Regola));
    if fValido > 1 then set fValido = 1;
    end if;
    return fValido;
    
  end;

Esempio di utilizzo della funzione

select checkVisa('4012345678901234')
  from SYSIBM/SYSDUMMY1;

Nozioni base sulla sintassi delle regex

In questo capitolo riepilogo gli elementi base della sintassi delle regular expression implementate in DB2 for i. Per una trattazione completa consultare IBM KnowledgeCenter: https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_73/db2/rbafzregexp_like.htm e http://userguide.icu-project.org/strings/regexp.

Definizioni

  • boundaries: nella source-string rappresentano le posizioni di confine tra word (\w) e non-word (\W)
  • white space: termine generico per indicare uno dei seguenti caratteri “bianchi”: tabulazione (\t), line feed (\n), form feed (\f), carriage return (\r), \p{Z}.
  • carattere word: sono [\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\u200c\u200d].

Caratteri di controllo

\A trova corrispondenza all’inizio della stringa di input
^ trova corrispondenza all’inizio di una riga
\b trova corrispondenza se la posizione corrente è un word boundary
\z trova corrispondenza alla fine della stringa di input
$ trova corrispodenza alla fine di una riga
\B negazione di \b
\d trova corrispondenza per ogni carattere numerico
\D negazione di \d
\G trova corrispondenza se la posizione corrente è alla fine della precedente corrispondenza
\n trova corrispondenza per il line feed (LF ovvero \u000A)
\r trova corrispondenza per il carriage return (CR ovvero \u000D)
\s trova corrispondenza per un carattere white space. Equivalente a [\t\n\f\r\p{Z}]
\S negazione di \s
\t trova corrispondenza per una tabulazione (\u0009)
\w trova corrispodenza per un carattere word
\W negazione di \w
\xhh trova corrispondenza per il carattere con codice esadecimale hh
\z trova corrispondenza se la posizione corrente è alla fine
[pattern] trova corrispondenza per ogni carattere specificato all’interno delle parentesi quadre
. trova corrispondenza per qualsiasi carattere
\ carattere escape per identificare come letterali i caratteri speciali: * ? + [ ( ) { } ^ $ | \

Operatori

| or
* trova corrispondenza 0 o più volte
+ trova corrispondenza 1 o più volte
? trova corrispondenza 0 o 1 volta
{n} trova corrispondenza esattamente n volte
{n,} trova corrispondenza almeno n volte
{n, m} trova corrispodenza tra n e m volte
(...) parentesi per definire un gruppo catturandolo
(?:...) parentesi per definire un gruppo senza catturarlo

Classi di caratteri

[abc] trova a o b o c
[^abc] trova tutti i caratteri tranne a, b, e c
[a-c] trova tutti i caratteri nel range da a a c
[\p{L}] tutti i caratteri Unicode della categoria Letter
[\p{L}&&\p{script=cyrillic}] && (= intersezione di insiemi) trova tutte le lettere cirillliche
[\p{L}--\p{script=cyrillic}] — (=sottrazione di insiemi) trova tutte le lettere non cirilliche
[[a-z][A-Z][0-9]] or implicito ovvero unione di range di caratteri
[[a-zA-Z0-9]] equivalente al precedente

Bibliografia