Scrivere espressioni regolari con prestazioni efficaci

Qui discutiamo alcuni degli aspetti importanti che devi considerare durante la creazione di espressioni regolari performanti.

Per i costrutti di espressione regolare che possono essere utilizzati con parser, etichette e filtri dati, vedere Documentazione su Java Platform Standard Ed. 8.

In caso di ricerche e regex in fase di query, fare riferimento alla sintassi RE2J all'indirizzo Implementazione Java di RE2.

Classi di caratteri

Le classi di caratteri specificano i caratteri che si sta cercando o non si sta cercando di trovare una corrispondenza. Assicurarsi di sostituire . nel file .*s con un carattere più specifico. .* passerà invariabilmente alla fine dell'input e quindi tornerà indietro, ovvero tornerà a uno stato salvato in precedenza per continuare la ricerca di una corrispondenza. Quando si utilizza una classe di caratteri specifica, si ha il controllo sul numero di caratteri che * farà consumare il motore regex, dando la potenza per arrestare il backtracking dilagante.

Si consideri l'espressione regolare di esempio riportata di seguito.

(\d{4})(\d{2})(\d{2}),(\S+),([\S\s]*),([\S\s]*),([\S\s]*),([\S\s]*),([\S\s]*),([\S\s]*),([\S\s]*),([\S\s]*),([\S\s]*),([\S\s]*),([0-9.]+),([0-9.]+),([0-9.]+),([0-9.]+),([0-9.]+),([0-9.]+)

Per il seguente input:

20150220,201502,16798186260,tvN,Divertimento/Musica,Femmina,67,2,Individuale,ollehtv Economico,Alloggio Commerciale,5587,0,2,0,1,1

Come risultato dell'espressione regolare specificata, la corrispondenza può essere eseguita nel backtracking. Questa situazione viene rilevata da Oracle Log Analytics e l'operazione di corrispondenza viene interrotta.

Modificando l'espressione regolare nell'esempio riportato di seguito, è possibile assicurarsi che la corrispondenza venga completata più rapidamente. Si noti che [\S\s]* viene modificato in [^,], evitando il backtracking non necessario.

(\d{4})(\d{2})(\d{2}),(\S+),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([0-9.]+),([0-9.]+),([0-9.]+),([0-9.]+),([0-9.]+),([0-9.]+)

Quantificatori lazy

In molte espressioni regolare, i quantificatori greedy (.*s) possono essere sostituiti in tutta sicurezza con i quantificatori lazy (.*?s), assegnando all'espressione regolare un incremento delle prestazioni senza modificare il risultato.

Prendere in considerazione l'input:

Trace file /u01/app/oracle/diag/rdbms/navisdb/NAVISDB/trace/NAVISDB_arc0_3941.trc
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options
ORACLE_HOME = /u01/app/oracle/product/11.2.0/db_1
System name: Linux
Node name: NAVISDB
Release: 2.6.18-308.el5
Version: #1 SMP Fri Jan 27 17:17:51 EST 2012
Machine: x86_64
Instance name: NAVISDB
Redo thread mounted by this instance: 1
Oracle process number: 21
Unix process pid: 3941, image: oracle@NAVISDB (ARC0)

Considerare la seguente espressione regolare avida per l'input dato:

Trace\sfile\s(\S*).*ORACLE_HOME\s*[:=]\s*(\S*).*System\sname:\s*(\S*).*Node\sname:\s*(\S*).*Release:\s*(\S*).*Machine:\s*(\S*).*Instance\sname:\s*(\S*).*Redo\sthread\smounted\sby\sthis\sinstance:\s(\d*).*Oracle\sprocess\snumber:\s*(\d*).*Unix\sprocess\spid:\s(\d*).*image:\s+([^\n\r]*)

Il motore regex spara alla fine dell'input ogni volta che incontra .*.. La prima volta che viene visualizzato .*, consuma tutto l'input e quindi esegue il backtracks fino a raggiungere ORACLE_HOME. Questo è un modo inefficiente di abbinare. Il regex pigro alternativo è come mostrato di seguito:

Trace\sfileRelease:\s*(\S*).*?Machine:\s*(\S*).*?Instance\sname:\s*(\S*).*?Redo\sthread\smounted\sby\sthis\sinstance:\s(\d*).*?Oracle\sprocess\snumber:\s*(\d*).*?Unix\sprocess\spid:\s(\d*).*?image:\s+([^\n\r]*)

L'espressione regolare sopra riportata viene utilizzata a partire dall'inizio della stringa finché non raggiunge ORACLE_HOME, il punto in cui potrebbe continuare a cercare corrispondenze per il resto della stringa.

Nota: se il campo ORACLE_HOME viene visualizzato all'inizio dell'input, si consiglia di usare il quantificatore lazy. Se il campo ORACLE_HOME viene visualizzato verso la fine, potrebbe essere opportuno utilizzare il quantificatore greedy.

Ancoraggi

Gli ancoraggi indicano al motore regex che si intende che il cursore si trovi in una posizione specifica nell'input. Gli ancoraggi più comuni sono ^ e $, che indicano l'inizio e la fine dell'input.

Prendere in considerazione le espressioni regolari seguenti per trovare un indirizzo IPv4:

\d{1,3}\.d{1,3}.\d{1,3}.\d{1,3}
^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}

Notare che la seconda espressione regolare inizia con ^ ed è pertanto specifica dell'indirizzo IP visualizzato all'inizio dell'input.

Si sta cercando l'espressione regolare nell'input simile all'esempio seguente:

107.21.20.1 - - [07/Dec/2012:18:55:53 -0500] "GET
      /extension/bsupport/design/cl/images/btn_letschat.png HTTP/1.1" 200
    2144

Un input non corrispondente sarebbe simile al seguente esempio:

[07/Dec/2012:23:57:13 +0000] 1354924633 GET "/favicon.ico" "" HTTP/1.1 200 82726 "-"
      "ELB-HealthChecker/1.0"

La seconda espressione regolare (che inizia con ^) viene eseguita più velocemente sull'input non corrispondente perché elimina l'input non corrispondente immediatamente.

L'importanza dell'alternanza

L'ordine di alternanza conta, quindi posiziona le opzioni più comuni nella parte anteriore in modo che possano essere abbinate più velocemente. Se le opzioni più rare vengono posizionate per prime, il motore delle espressioni regolari perderà tempo nel controllare quelle prima di controllare le opzioni più comuni che sono più probabili avere successo. Inoltre, cercare di estrarre i modelli comuni. Ad esempio, al posto di (abcd|abef) utilizzare ab(cd|ef).

Osservare le espressioni regolari seguenti:

{TIMEDATE}\s{0,1}:\s*(?:|\[)(\w+)(?:\:|\])(?:\[|)(\d+)(?:\:|\])(.{1,1000}).*
{TIMEDATE}\s{0,1}:\s*(?:\[|)(\w+)(?:\:|\])(?:\[|)(\d+)(?:\:|\])(.{1,1000}).*

Nell'input seguente:

2014-06-16 12:13:46.743: [UiServer][1166092608] {0:7:2} Done for
ctx=0x2aaab45d8330

La seconda espressione regolare trova più velocemente la corrispondenza in quanto l'alternanza cerca prima un carattere [, seguito da uno nullo. Poiché l'input contiene [, la corrispondenza viene eseguita più velocemente.

Espressioni di analisi di esempio

È possibile fare riferimento alle espressioni di analisi di esempio riportate di seguito per creare un'espressione di analisi adatta per l'estrazione dei valori dal file di log.

Un file di log comprende le voci generate concatenando più valori di campo. Potrebbe non essere necessario visualizzare tutti i valori di campo per l'analisi di un file di log di un formato specifico. Utilizzando un parser è possibile estrarre i valori solo dai campi che si desidera visualizzare.

Un parser estrae i campi da un file di log in base all'espressione di analisi definita dall'utente. Un'espressione di analisi viene scritta sotto forma di espressione regolare che definisce un pattern di ricerca. In un'espressione di analisi, i pattern di ricerca vengono racchiusi tra parentesi (), per ogni campo corrispondente che si desidera estrarre da una voce di log. Non viene estratto alcun valore corrispondente a un pattern di ricerca esterno alle parentesi.

Esempio 1

Se si desidera analizzare le seguenti voci di log di esempio:

Jun 20 15:19:29 hostabc rpc.gssd[2239]: ERROR: can't open clnt5aa9: No such file or directory
Jul 29 11:26:28 hostabc kernel: FS-Cache: Loaded
Jul 29 11:26:28 hostxyz kernel: FS-Cache: Netfs 'nfs' registered for caching

Di seguito dovrebbe essere l'espressione di analisi:

(\S+)\s+(\d+)\s(\d+):(\d+):(\d+)\s(\S+)\s(?:([^:\[]+)(?:\[(\d+)\])?:\s+)?(.+)

Nell'esempio precedente, alcuni dei valori acquisiti dall'espressione di analisi sono:

  • (\S+): più caratteri non di spazio per il mese

  • (\d+): più caratteri non di spazio per il giorno

  • (?:([^:\[]+): (facoltativo) Tutti i caratteri tranne ^, :, \, []; questo è per il nome del servizio

  • (.+): (facoltativo) contenuto del messaggio primario

Esempio 2

Se si desidera analizzare le seguenti voci di log di esempio:

####<Apr 27, 2014 4:01:42 AM PDT> <Info> <EJB> <host> <AdminServer> <[ACTIVE] ExecuteThread: '0' for queue: 'weblogic.kernel.Default (self-tuning)'> <OracleSystemUser> <BEA1-13E2AD6CAC583057A4BD> <b3c34d62475d5b0b:6e1e6d7b:143df86ae85:-8000-000000000000cac6> <1398596502577> <BEA-010227> <EJB Exception occurred during invocation from home or business: weblogic.ejb.container.internal.StatelessEJBHomeImpl@2f9ea244 threw exception: javax.ejb.EJBException: what do i do: seems an odd quirk of the EJB spec. The exception is:java.lang.StackOverflowError>
####<Jul 30, 2014 8:43:48 AM PDT> <Info> <RJVM> <example.com> <> <Thread-9> <> <> <> <1406735028770> <BEA-000570> <Network Configuration for Channel "AdminServer" Listen Address example.com:7002 (SSL) Public Address N/A Http Enabled true Tunneling Enabled false Outbound Enabled false Admin Traffic Enabled true ResolveDNSName Enabled false> 

Di seguito dovrebbe essere la tua espressione di analisi::

####<(\p{Upper}\p{Lower}{2})\s+([\d]{1,2}),\s+([\d]{4})\s+([\d]{1,2}):([\d]{2}):([\d]{2})\s+(\p{Upper}{2})(?:\s+(\w+))?>\s+<(.*?)>\s+<(.*?)>\s+<(.*?)>\s+<(.*?)>\s+<(.*?)>\s+<(.*?)>\s+<(.*?)>\s+<(.*?)>\s+<\d{10}\d{3}>\s+<(.*?)>\s+<(.*?)(?:\n(.*))?>\s*

Nell'esempio precedente, alcuni dei valori acquisiti dall'espressione di analisi sono:

  • (\p{Upper}\p{Lower}{2}): nome breve di 3 lettere per il mese; con la prima lettera in maiuscolo seguita da due lettere minuscole

  • ([\d]{1,2}): giorno a 1 o 2 cifre

  • ([\d]{4}): anno a 4 cifre

  • ([\d]{1,2}): ora di 1 o 2 cifre

  • ([\d]{2}): minuti a 2 cifre

  • ([\d]{2}): secondo a 2 cifre

  • (\p{Upper}{2}): AM/PM di 2 lettere in maiuscolo

  • (?:\s+(\w+)): (facoltativo: alcune voci potrebbero non restituire alcun valore) Più caratteri alfanumerici per il fuso orario

  • (.*?): (facoltativo, alcune voci non possono restituire alcun valore per questo) Uno o più caratteri per il livello di severità; in questo caso <INFO>

  • (.*): ulteriori dettagli insieme al messaggio

Pattern di ricerca

Alcuni dei modelli comunemente utilizzati sono spiegati nella seguente tabella:

Pattern Descrizione Esempio
. Qualsiasi carattere tranne l'interruzione di riga d.f corrisponde a def, daf, dbf e così via
* Zero o maggiori volte D*E*F* corrisponde a DDEEFF, DEF, DDFF, EEFF e così via
? Una volta o nessuna; facoltativo colou?r corrisponde sia al colore che al colore
+ Uno o più Stage \w-\w+ corrisponde a Fase A-b1_1, Fase B-a2 e così via
{2} esattamente due volte [\d]{2} corrisponde a 01, 11, 21 e così via
{12} Da due a quattro volte [\d]{1,2} corrisponde a 1, 12 e così via
{3} Tre o più volte [\w]{3,} corrisponde a dieci, ciao, h2134 e così via
[ … ] Uno dei caratteri tra parentesi [AEIOU] corrisponde a una vocale maiuscola
[x-y] Uno dei caratteri compresi nell'intervallo da x a y [A-Z]+ corrisponde a ACT, ACTION, BAT e così via
[^x] Un carattere che non è x [^/d]{2} corrisponde a AA, BB, AC e così via
[^x-y] Uno dei caratteri non compresi nell'intervallo da x a y [^a-z]{2} corrisponde a A1, BB, B2 e così via
[\d\D] Un carattere che è una cifra o un carattere non numerico [\d\D]+ corrisponde a qualsiasi carattere, incluse le nuove righe, che il punto normale non corrisponde
\s Uno spazio vuoto (\S+)\s+(\d+) corrisponde a AA 123, a_ 221 e così via
\S Un carattere che non è uno spazio vuoto (\S+) corrisponde a abcd, ABC, A1B2C3 e così via
\n Una nuova linea (\d)\n(\w) corrisponde:

1

A

\w Un carattere alfanumerico [\w-\w\w\w] corrisponde a a a-123, 1–aaa e così via
\p{Inferiore} Lettere minuscole \p{Lower}{2} corrisponde a a aa, ab, ac, bb e così via
\p{Superiore} Lettere maiuscole \p{Upper} corrisponde a A, B, C e così via
\ seguito da ?, [], *, . Carattere di escape; utilizzare i caratteri dopo \ come valori letterali \? restituisce?