2009-09-25 17 views
5

Tengo una lista creciente de expresiones regulares que estoy usando para analizar a través de archivos de registro en busca de errores "interesantes" y declaraciones de depuración. Actualmente los estoy dividiendo en 5 cubos, y la mayoría caen en 3 cubos grandes. Tengo más de 140 patrones hasta ahora, y la lista continúa creciendo.¿Cómo puedo combinar de manera eficiente muchos patrones de expresiones regulares diferentes en Perl?

La mayoría de las expresiones regulares son simples, pero también son bastante únicas, por lo que mis oportunidades de detectar múltiples coincidencias con un solo patrón son pocas y distantes. Debido a la naturaleza de lo que estoy igualando, los patrones tienden a ser oscuros y por lo tanto rara vez coinciden, así que estoy haciendo un montón de trabajo en cada línea de entrada con el resultado final de que no puede coincidir con nada, o coincide uno de los genéricos al final.

Y debido a la cantidad de entrada (cientos de megabytes de archivos de registro) a veces estoy esperando uno o dos minutos para que el script termine. De ahí mi deseo de una solución más eficiente. Aunque no estoy interesado en sacrificar la claridad por la velocidad.

que actualmente he puesto las expresiones regulares de esta manera:

if (($line =~ m{Failed in routing out}) || 
    ($line =~ m{Agent .+ failed}) || 
    ($line =~ m{Record Not Exist in DB}) || 
     ... 

¿Hay una mejor manera de estructurar este por lo que es más eficiente, y aún así fácil de mantener? ¡Gracias!

Respuesta

5

Es posible que desee echar un vistazo a Regexp::Assemble. Está destinado a manejar exactamente este tipo de problema.

código Impulsado desde sinopsis del módulo:

use Regexp::Assemble; 

my $ra = Regexp::Assemble->new; 
$ra->add('ab+c'); 
$ra->add('ab+-'); 
$ra->add('a\w\d+'); 
$ra->add('a\d+'); 
print $ra->re; # prints a(?:\w?\d+|b+[-c]) 

Incluso puede sorber su colección de expresiones regulares de un archivo separado.

+2

Definitivamente el camino a seguir. Tengo una aplicación actualmente en producción que usa Regexp :: Assemble para comparar cadenas de texto entrantes con una lista de 1.334 términos para ver cuáles (si las hay) de ellas en cada cadena. El código es simple como el demonio y funciona bien y rápido. –

5

Usted puede combinar sus expresiones regulares con el operador de la alternancia |, como en: /pattern1|pattern2|pattern3/

Obviamente, no va a ser muy fácil de mantener si se pone a todos en una sola línea, pero usted ha opciones tiene para mitigar eso.

  • Puede usar el modificador de expresiones regulares /x para espaciarlas, una por línea. Una palabra de advertencia si elige esta dirección: tendrá que especificar explícitamente los caracteres de espacio que espera, de lo contrario se ignorarían debido al /x.
  • Puede generar su expresión regular en tiempo de ejecución, combinando fuentes individuales. Algo como esto (no probado):

    my $regex = join '|', @sources; 
    while (<>) { 
        next unless /$regex/o; 
        say; 
    } 
    
+0

+1 Esto realmente puede ser más eficiente que hacerlos por separado. El motor de expresiones regulares puede ser inteligente y extraer prefijos comunes que pueden acelerar las cosas (esto es cierto para Perl 5.10 al menos, y posiblemente antes). –

+4

/o está obsoleto/en desuso. Use qr //. Y creo que en perls modernos, siempre y cuando $ regex no cambie, no se volverá a compilar de todos modos. Pero no me cite :-) – runrig

+0

@runrig Recuerdo haber leído algo acerca de/o que estaba en desuso, pero no pude encontrar nada al respecto en los documentos. Por lo que puedo recordar, es necesario en 5.8.8 (¿cuán moderno es eso ?, no me cites tampoco). qr // evita el problema, pero está más lejos del siguiente paso lógico en mi razonamiento: leer la lista de expresiones regulares de otras fuentes (por ejemplo, archivo separado) –

1

Una posible solución es dejar que la máquina de estados de expresiones regulares hacer la comprobación de alternativas para usted. Tendrá que comparar para ver si el resultado es notablemente más eficiente, pero sin duda será más fácil de mantener.

Primero, mantendrá un archivo que contenga un patrón de interés por línea.

Failed in routing out 
Agent .+ failed 
Record Not Exist in DB 

Entonces será leído en ese archivo al comienzo de la carrera, y construir una gran expresión regular usando el operador "alternativo", "|"

open(PATTERNS,"<foo.txt") or die $!; 
@patterns = <PATTERNS>; 
close PATTERNS or die $!; 
chomp @patterns; 
$matcher = join('|', @patterns); 

while (<MYLOG>) { 
    print if $_ =~ $matcher; 
} 
1

Tal vez algo como:

my @interesting = (
    qr/Failed in routing out/, 
    qr/Agent .+ failed/, 
    qr/Record Not Exist in DB/, 
); 

... 


for my $re (@interesting) { 
    if ($line =~ /$re/) { 
    print $line; 
    last; 
    } 
} 

Puede intentar unir todos sus patrones con "|" para hacer una expresión regular Eso puede o no ser más rápido.

4

Es posible que desee deshacerse de la gran sentencia if:

my @interesting = (
    qr/Failed in routing out/, 
    qr/Agent .+ failed/, 
    qr/Record Not Exist in DB/, 
); 

return unless $line =~ $_ for @interesting; 

aunque no puedo prometer que esto mejorará nada w/o evaluación comparativa con los datos reales.

Podría ayudar si puede anclar sus patrones al principio para que puedan fallar más rápidamente.

+0

Sinan - +1 pero recuerde que está analizando archivos de registro, por lo que el desnudo las cadenas no serán anclables, lo más probable. Pero con suerte sus registros tienen prefijos uniformes para que pueda anclar la marca de tiempo o el prefijo que se parezca. – DVK

+0

De hecho, generalmente soy muy religioso sobre cómo anclar mis cadenas, pero como dice DVK, las cadenas pueden y estarán por todos lados (especialmente cuando estoy analizando diferentes tipos de archivos de registro). La marca de tiempo RE es la primera que hago y, por supuesto, está anclada (una vez que averiguo qué marca de hora RE usar). –

+0

Las líneas de registro generalmente tienen algunos campos definidos. Estaba pensando en eliminar esos campos primero antes de buscar posibles coincidencias. –

1

Sus expresiones regulares de ejemplo parecen estar basadas principalmente en palabras y frases comunes. Si ese es el caso, es posible que pueda acelerar considerablemente las cosas al filtrar previamente las líneas de entrada usando index, que es mucho más rápido que una expresión regular. Bajo tal estrategia, cada expresión regular tendría una palabra o frase no regex correspondiente para usar en la etapa de prefiltrado. Mejor aún sería omitir la prueba de expresiones regulares por completo, siempre que sea posible: dos de sus pruebas de ejemplo no requieren expresiones regulares y podrían hacerse simplemente con index.

Aquí es una ilustración de la idea básica:

use strict; 
use warnings; 

my @checks = (
    ['Failed', qr/Failed in routing out/ ], 
    ['failed', qr/Agent .+ failed/  ], 
    ['Not Exist', qr/Record Not Exist in DB/ ], 
); 
my @filter_strings = map { $_->[0] } @checks; 
my @regexes  = map { $_->[1] } @checks; 

sub regex { 
    my $line = shift; 
    for my $reg (@regexes){ 
     return 1 if $line =~ /$reg/; 
    } 
    return; 
} 

sub pre { 
    my $line = shift; 
    for my $fs (@filter_strings){ 
     return 1 if index($line, $fs) > -1; 
    } 
    return; 
} 

my @data = (
    qw(foo bar baz biz buz fubb), 
    'Failed in routing out.....', 
    'Agent FOO failed miserably', 
    'McFly!!! Record Not Exist in DB', 
); 

use Benchmark qw(cmpthese); 
cmpthese (-1, { 
    regex => sub { for (@data){ return $_ if(   regex($_)) } }, 
    pre => sub { for (@data){ return $_ if(pre($_) and regex($_)) } }, 
}); 

de salida (resultados con sus datos podrían ser muy diferentes):

   Rate  regex prefilter 
regex  36815/s  --  -54% 
prefilter 79331/s  115%  -- 
+0

Deberías usar '. +?' en regex. –

2

Esto se maneja fácilmente con Perl 5.10

use strict; 
use warnings; 
use 5.10.1; 

my @matches = (
    qr'Failed in routing out', 
    qr'Agent .+ failed', 
    qr'Record Not Exist in DB' 
); 

# ... 

sub parse{ 
    my($filename) = @_; 

    open my $file, '<', $filename; 

    while(my $line = <$file>){ 
    chomp $line; 

    # you could use given/when 
    given($line){ 
     when(@matches){ 
     #... 
     } 
    } 

    # or smartmatch 
    if($line ~~ @matches){ 
     # ... 
    } 
    } 
} 

Puede usar el nuevo Smart-Match operator ~~.

if($line ~~ @matches){ ... } 

O puede usar given/when. Lo cual hace lo mismo que usar el operador Smart-Match.

given($line){ 
    when(@matches){ 
    #... 
    } 
} 
3

De respuesta perlfaq6 's a How do I efficiently match many regular expressions at once?


¿Cómo coinciden de manera eficiente muchas expresiones regulares a la vez?

(aportado por Brian D Foy)

evitar pedir Perl para compilar una expresión regular cada vez que desee para que coincidan. En este ejemplo, perl debe recompilar la expresión regular para cada iteración del ciclo foreach, ya que no tiene forma de saber qué patrón $ será.

@patterns = qw(foo bar baz); 

LINE: while(<DATA>) 
    { 
    foreach $pattern (@patterns) 
     { 
     if(/\b$pattern\b/i) 
      { 
      print; 
      next LINE; 
      } 
     } 
    } 

El operador qr // apareció en Perl 5.005. Compila una expresión regular, pero no la aplica. Cuando utiliza la versión pre compilada de la expresión regular, perl hace menos trabajo. En este ejemplo, inserté un mapa para convertir cada patrón en su forma precompilada. El resto del guión es el mismo, pero más rápido.

@patterns = map { qr/\b$_\b/i } qw(foo bar baz); 

LINE: while(<>) 
    { 
    foreach $pattern (@patterns) 
     { 
     if(/$pattern/) 
      { 
      print; 
      next LINE; 
      } 
     } 
    } 

En algunos casos, es posible que pueda hacer varios patrones en una sola expresión regular. Sin embargo, ten cuidado con las situaciones que requieren retroceso.

$regex = join '|', qw(foo bar baz); 

LINE: while(<>) 
    { 
    print if /\b(?:$regex)\b/i; 
    } 

Para obtener más detalles sobre la eficacia de la expresión regular, consulte Dominar las expresiones regulares de Jeffrey Freidl. Explica cómo funciona el motor de expresiones regulares y por qué algunos patrones son sorprendentemente ineficientes. Una vez que comprenda cómo perl aplica expresiones regulares, puede sintonizarlas para situaciones individuales.

Cuestiones relacionadas