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 a espressione regolare che possono essere utilizzati con parser, etichette e filtri dati, vedere Documentazione su Java Platform Standard Ed. 8.
Nel caso di ricerche e espressioni regolari query-time, fare riferimento alla sintassi RE2J in Implementazione Java di RE2.
Classi di caratteri
Le classi di caratteri specificano i caratteri che si sta tentando o non si sta tentando di trovare una corrispondenza. Assicurarsi di sostituire .
nel file .*s
con un carattere più specifico. Il valore .*
verrà spostato invariabilmente alla fine dell'input e verrà quindi eseguito il backtrack, ovvero verrà ripristinato lo stato salvato in precedenza per continuare la ricerca di una corrispondenza. Quando si utilizza una specifica classe di caratteri, si ha il controllo su quanti caratteri il *
causerà il consumo del motore regex, dando la possibilità di 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 l'input seguente:
20150220,201502,16798186260,tvN,Intrattenimento/Musica,Donna,67,2,Individuale,ollehtv Economico,Alloggio commerciale,5587,0,2,0,1,1Come risultato dell'espressione regolare specificata, la corrispondenza può essere eseguita nel backtracking. Questa situazione viene rilevata da Oracle Logging 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 [^,]
per evitare 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 regolari, i quantificatori greedy (.*s)
possono essere sostituiti in tutta sicurezza con i quantificatori lazy (.*?s)
, ottenendo così un incremento delle prestazioni dell'espressione regolare 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 greedy per l'input specificato:
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 .*
, viene utilizzato tutto l'input, quindi viene eseguito il backtrack fino a raggiungere ORACLE_HOME
. Questo è un modo inefficiente di abbinare. L'espressione regolare pigra alternativa è la seguente:
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'elemento regex lazy sopra riportato viene utilizzato a partire dall'inizio della stringa fino a quando non raggiunge ORACLE_HOME
, punto in cui può continuare a corrispondere al resto della stringa.
Nota: se il campo ORACLE_HOME
viene visualizzato verso l'inizio dell'input, è necessario utilizzare il quantificatore lazy. Se il campo ORACLE_HOME
viene visualizzato verso la fine, potrebbe essere opportuno utilizzare il quantificatore greedy.
Ancore
Le ancore indicano al motore regex che si intende che il cursore si trovi in una determinata posizione nell'input. Gli ancoraggi più comuni sono ^
e $
, che indicano l'inizio e la fine dell'input.
Per trovare un indirizzo IPv4, considerare le espressioni regolari riportate di seguito.
\d{1,3}\.d{1,3}.\d{1,3}.\d{1,3}
^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}
Si noti che la seconda espressione regolare inizia con ^
ed è specifica dell'indirizzo IP visualizzato all'inizio dell'input.
È in corso la ricerca dell'espressione regolare nell'input simile alla 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 avrebbe un aspetto 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 immediatamente l'input non corrispondente.
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 regex perderà tempo nel controllarle prima di controllare le opzioni più comuni che hanno maggiori probabilità di successo. Inoltre, cerca di estrarre modelli comuni. Ad esempio, anziché (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 corrisponde più velocemente in quanto l'alternanza cerca prima il carattere [
, seguito da un valore nullo. Poiché l'input contiene [
, la corrispondenza viene eseguita più velocemente.
Espressioni di analisi di esempio
È possibile fare riferimento alle seguenti espressioni di analisi di esempio per creare un'espressione di analisi adatta per l'estrazione di 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 dei campi per l'analisi di un file di log di un formato particolare. 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. Un'espressione di analisi viene scritta sotto forma di espressione regolare che definisce un pattern di ricerca. In un'espressione di analisi è possibile racchiudere i pattern di ricerca tra parentesi (), per ogni campo corrispondente che si desidera estrarre da una voce di log. I valori che corrispondono a un pattern di ricerca esterno alle parentesi non vengono estratti.
Esempio 1
Per analizzare le seguenti voci del 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 riportata 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 senza spazi vuoti per il mese -
(\d+)
: più caratteri senza spazi vuoti per il giorno -
(?:([^:\[]+)
: (Facoltativo) Tutti i caratteri tranne ^, :, \, []; questo è per il nome del servizio -
(.+)
: (Facoltativo) Contenuto messaggio principale
Esempio 2
Per analizzare le seguenti voci del 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 riportata l'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 di 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})
: secondi a 2 cifre -
(\p{Upper}{2})
: AM/PM a 2 lettere in maiuscolo -
(?:\s+(\w+))
: (facoltativo: alcune voci potrebbero non restituire alcun valore per questo) Caratteri alfanumerici multipli per il fuso orario -
(.*?)
: (Facoltativo, alcune voci potrebbero non restituire alcun valore per questo) Uno o più caratteri per il livello di severità. In questo caso,<INFO>
-
(.*)
: eventuali dettagli aggiuntivi insieme al messaggio
Pattern di ricerca
Alcuni dei modelli comunemente usati sono spiegati nella seguente tabella:
Pattern | descrizione; | Esempio |
---|---|---|
. | Qualsiasi carattere tranne interruzione di riga | d.f corrisponde a def, daf, dbf e così via
|
* | Zero o più volte | D*E*F* corrisponde a DDEEFF, DEF, DDFF, EEFF e così via
|
? | Una o nessuna; opzionale | colou?r corrisponde sia al colore che al colore
|
+ | Uno o più | Stage \w-\w+ corrisponde alla fase A-b1_1, alla 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 ad ACT, ACTION, BAT e così via
|
[^x] | Un carattere diverso da x | [^/d]{2} corrisponde ad AA, BB, AC e così via
|
[^x-y] | Uno dei caratteri non 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 non una cifra | [\d\D]+ corrisponde a qualsiasi carattere, incluse le nuove righe, che il punto normale non corrisponde
|
\s | Uno spazio vuoto | (\S+)\s+(\d+) corrisponde ad AA 123, a_ 221 e così via
|
\S | Un carattere che non è uno spazio vuoto | (\S+) corrisponde ad abcd, ABC, A1B2C3 e così via
|
\n | Una nuova linea | (\d)\n(\w) corrisponde a:
1 A |
\a | Un carattere alfanumerico | [\w-\w\w\w] corrisponde a a-123, 1-aaa e così via
|
\p{Minore} | Lettere minuscole | \p{Lower}{2} corrisponde aa, ab, ac, bb e così via
|
\p{Upper} | Lettere maiuscole | \p{Upper} corrisponde ad A, B, C e così via
|
\ seguito da ?, [], *, . | Carattere di escape; utilizzare i caratteri dopo \ come valori letterali | \? restituisce ?
|