2010-08-24 11 views
7

¿Cómo puedo eliminar la captura de subgrupos anidados arbitrariamente en una cadena de expresiones regulares Perl? Me gustaría anidar cualquier expresión regular en una expresión envolvente que capture la subregex como una entidad completa, así como los grupos posteriores conocidos estáticamente. ¿Debo transformar manualmente la cadena de expresiones regulares en todos los grupos de (?:) que no se están capturando (y espero que no me equivoque), o existe un mecanismo de biblioteca o regex de Perl que proporcione esto?¿Cómo puedo hacer que una Perl regex arbitraria no sea capturada por completo? (Respuesta: No se puede)

# How do I 'flatten' $regex to protect $2 and $3? 
# Searching 'ABCfooDE' for 'foo' OK, but '((B|(C))fo(o)?(?:D|d)?)', etc., breaks. 
# I.E., how would I turn it effectively into '(?:(?:B|(?:C))fo(?:o)?(?:D|d)?)'? 
sub check { 
    my($line, $regex) = @_; 
    if ($line =~ /(^.*)($regex)(.*$)/) { 
    print "<", $1, "><", $2, "><", $3, ">\n"; 
    } 
} 

Adición: soy vagamente consciente de $&, $`, y $' y han aconsejado a evitarlos si es posible, y no tienen acceso a ${^PREMATCH}, ${^MATCH} y ${^POSTMATCH} en mi entorno Perl 5.8 . El ejemplo anterior se puede dividir en 2/3 fragmentos utilizando métodos como estos, y casos reales más complejos podrían iterar esto manualmente, pero creo que me gustaría una solución general, si es posible.

respuesta aceptado: lo que deseo existía y sorprendente (al menos para mí) no lo hace, es un grupo de encapsulación que hace que su contenido opaco, de tal manera que backreferences posicionales subsiguientes ver el contenido como una sola entidad y nombres de referencias son sin alcance gbacon tiene una solución potencialmente útil para Perl 5.10+, y FM muestra un mecanismo iterativo manual para cualquier versión que puede lograr el mismo efecto en casos específicos, pero j_random_hacker lo llama que no hay un mecanismo de lenguaje real para encapsular subexpresiones.

+1

+1 Buen trabajo con las ediciones de seguimiento. – FMc

Respuesta

8

En general, no se puede.

Incluso si se pudiera transformar todos (...) s en (?:...) s, esto no funcionaría en el caso general, ya el patrón puede requerir referencias hacia atrás: por ejemplo, /(.)X\1/, que coincide con cualquier carácter, seguido de un X, seguido del carácter originalmente coincidente.

Por lo tanto, a falta de un mecanismo Perl para descartar los resultados capturados "después del hecho", no hay forma de resolver su problema para todas las expresiones regulares. Lo mejor que puede hacer (o podría hacer si tuviera Perl 5.10) es usar gbacon's suggestion y esperar generar un nombre único para el búfer de captura.

0

Esto no deshabilita la captura, pero podría lograr lo que desea:

$ perl -wle 'my $_ = "123abc"; /(\d+)/ && print "num: $1"; { /([a-z]+)/ && print "letter: $1"; } print "num: $1";' 
num: 123 
letter: abc 
num: 123 

se crea un nuevo alcance y $ 1 fuera de él no se verá afectada.

7

Una forma de proteger los sub-patrones que le interesan es utilizar named capture buffers:

Adicionalmente, al Perl 5.10.0 puede usar búferes de captura con nombre y referencias hacia atrás con nombre. La notación es (?<name>...) para declarar y \k<name> a la referencia. También puede usar apóstrofes en lugar de corchetes angulares para delimitar el nombre; y puede usar la sintaxis de retroreferencia \g{name} entre corchetes. También es posible hacer referencia a un buffer de captura nombrado por número absoluto y relativo. Fuera del patrón, un buffer de captura nombrado está disponible a través del hash %+. Cuando diferentes almacenamientos intermedios dentro del mismo patrón tienen el mismo nombre, $+{name} y \k<name> se refieren al grupo definido más a la izquierda.

En el contexto de su pregunta, check convierte

sub check { 
    use 5.10.0; 
    my($line, $regex) = @_; 
    if ($line =~ /(^.*)($regex)(.*$)/) { 
    print "<", $+{one}, "><", $+{two}, "><", $+{three}, ">\n"; 
    } 
} 

Entonces llamando con

my $pat = qr/(?<one>(?<two>B|(?<three>C))fo(o)?(?:D|d)?)/; 
check "ABCfooDE", $pat; 

salidas

<CfooD><C><C>
+0

Esta es una técnica clara de la que no tenía conocimiento, pero desafortunadamente, estoy atrapado en un entorno RHEL 4 (Perl v5.8.5), por lo que no puedo usarlo por el momento. – Jeff

5

Esto no aborda el caso general, pero su ejemplo específico puede ser manejado con la opción /g en contexto escalar, lo que permitiría que permite dividir el problema en dos partidos, el segundo recogiendo donde la primera a la izquierda:

sub check { 
    my($line, $regex) = @_; 
    my ($left_side, $regex_match) = ($1, $2) if $line =~ /(^.*)($regex)/g; 
    my $right_side = $1 if $line =~ /(.*$)/g; 
    print "<$left_side> <$regex_match> <$right_side>\n"; # <AB> <CfooD> <E123> 
} 

check('ABCfooDE123', qr/((B|(C))fo(o)?(?:D|d)?)/); 
+0

Gracias, esta técnica es probablemente lo suficientemente buena para usarla en mis casos de uso actuales por el momento. Creo que finalmente necesitaré una solución más general, así que voy a mantener la pregunta abierta. – Jeff

2

Si todo lo que necesita es la parte de la cadena antes y después del partido, se puede utilizar el @- y @+ matrices para obtener los desplazamientos en la cadena coincidente:

sub check { 
    my ($line, $regex) = @_; 
    if ($line =~ /$regex/) { 
     my $pre = substr $line, 0, $-[0]; 
     my $match = substr $line, $-[0], $+[0] - $-[0]; 
     my $post = substr $line, $+[0]; 
     print "<$pre><$match><$post>\n"; 
    } 
} 
1

Perl de la versión> 5,22 es se informa que tiene un modificador '/ n' que desactiva toda la captura.

Cuestiones relacionadas