Grupo de expresiones regulares en Perl: ¿cómo capturar elementos en una matriz desde un grupo de expresiones regulares que coincide con un número desconocido de ocurrencias/múltiples/variables de una cadena?


En Perl, ¿cómo puedo usar una agrupación de expresiones regulares para capturar más de una ocurrencia que coincide con ella, en varios elementos de matriz?

Por ejemplo, para una cadena:

var1=100 var2=90 var5=hello var3="a, b, c" var7=test var3=hello

Para procesar esto con código:

$string = "var1=100 var2=90 var5=hello var3=\"a, b, c\" var7=test var3=hello";

my @array = $string =~ <regular expression here>

for ( my $i = 0; $i < scalar( @array ); $i++ )
{
  print $i.": ".$array[$i]."\n";
}

Me gustaría ver como salida:

0: var1=100
1: var2=90
2: var5=hello
3: var3="a, b, c"
4: var7=test
5: var3=hello

¿Qué usaría como expresión regular?

La similitud entre las cosas que quiero que coincidan aquí es un patrón de cadena de asignación, así que algo como:

my @array = $string =~ m/(\w+=[\w\"\,\s]+)*/;

Donde el * indica una o más ocurrencias coincidiendo con el grupo.

(Descarté el uso de split() ya que algunas coincidencias contienen espacios dentro de sí mismas (es decir, var3...) y, por lo tanto, no daría los resultados deseados.)

Con la expresión regular anterior, solo obtengo:

0: var1=100 var2

¿Es posible en una expresión regular? O código de adición requerido?

Miró las respuestas existentes ya, al buscar "perl regex multiple group"pero no suficientes pistas:

Author: Community, 2010-08-11

9 answers

my $string = "var1=100 var2=90 var5=hello var3=\"a, b, c\" var7=test var3=hello";

while($string =~ /(?:^|\s+)(\S+)\s*=\s*("[^"]*"|\S*)/g) {
        print "<$1> => <$2>\n";
}

Impresiones:

<var1> => <100>
<var2> => <90>
<var5> => <hello>
<var3> => <"a, b, c">
<var7> => <test>
<var3> => <hello>

Explicación:

La última pieza primero: la bandera g al final significa que puede aplicar la expresión regular a la cadena varias veces. La segunda vez continuará coincidiendo donde terminó la última coincidencia en la cadena.

Ahora para la expresión regular: (?:^|\s+) coincide con el principio de la cadena o con un grupo de uno o más espacios. Esto es necesario para que cuando la expresión regular se aplique la próxima vez, saltaremos los espacios entre los pares clave/valor. El ?: significa que el contenido de los paréntesis no se capturará como grupo (no necesitamos los espacios, solo clave y valor). \S+ coincide con el nombre de la variable. Luego saltamos cualquier cantidad de espacios y un signo igual en el medio. Finalmente, ("[^"]*"|\S*)/ hace coincidir dos comillas con cualquier cantidad de caracteres en el medio, o cualquier cantidad de caracteres que no sean espacios para el valor. Tenga en cuenta que la coincidencia de citas es bastante frágil y no manejará las citas copiadas correctamente, por ejemplo, "\"quoted\"" daría lugar a "\".

EDITAR:

Dado que realmente desea obtener toda la asignación, y no las claves/valores individuales, aquí hay una línea que los extrae:

my @list = $string =~ /(?:^|\s+)((?:\S+)\s*=\s*(?:"[^"]*"|\S*))/g;
 39
Author: jkramer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-12 10:18:32

Con las expresiones regulares, use una técnica que me gusta llamar tack-and-stretch: anclar en las características que sabe que estarán allí (tack) y luego agarrar lo que hay entre (stretch).

En este caso, usted sabe que una sola asignación coincide con

\b\w+=.+

Y tienes muchos de estos repetidos en $string. Recuerde que \b significa límite de palabra:

Un límite de palabra (\b) es un punto entre dos caracteres que tiene un \w en un lado y un \W en el otro lado de ella (en cualquier orden), contando los caracteres imaginarios del principio y el final de la cadena como coincidentes con un \W.

Los valores en las asignaciones pueden ser un poco difíciles de describir con una expresión regular, pero también sabe que cada valor terminará con espacios en blanco, ¡aunque no necesariamente el primer espacio en blanco encontrado!- seguido de otra asignación o fin de cadena.

Para evitar repetir el patrón de aserción, compílelo una vez con qr// y reutilizarlo en su patrón junto con una aserción de look-ahead (?=...) para estirar la coincidencia lo suficiente para capturar todo el valor y evitar que se derrame en el siguiente nombre de variable.

Hacer coincidir su patrón en el contexto de lista con m//g da el siguiente comportamiento:

El modificador /g especifica la coincidencia global de patrones, es decir, la coincidencia tantas veces como sea posible dentro de la cadena. Cómo se comporta depende de contexto. En el contexto de lista, devuelve una lista de las subcadenas coincidentes con cualquier paréntesis de captura en la expresión regular. Si no hay paréntesis, devuelve una lista de todas las cadenas coincidentes, como si hubiera paréntesis alrededor de todo el patrón.

El patrón $assignment utiliza .+? no codicioso para cortar el valor tan pronto como el look-ahead ve otra asignación o fin de línea. Recuerde que la coincidencia devuelve las subcadenas de all capturing subpatrones, por lo que la alternancia del look-ahead usa (?:...) no captura. El qr//, por el contrario, contiene paréntesis de captura implícitos.

#! /usr/bin/perl

use warnings;
use strict;

my $string = <<'EOF';
var1=100 var2=90 var5=hello var3="a, b, c" var7=test var3=hello
EOF

my $assignment = qr/\b\w+ = .+?/x;
my @array = $string =~ /$assignment (?= \s+ (?: $ | $assignment))/gx;

for ( my $i = 0; $i < scalar( @array ); $i++ )
{
  print $i.": ".$array[$i]."\n";
}

Salida:

0: var1=100
1: var2=90
2: var5=hello
3: var3="a, b, c"
4: var7=test
5: var3=hello
 8
Author: Greg Bacon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-13 09:59:39

No estoy diciendo que esto sea lo que deberías hacer, pero lo que estás tratando de hacer es escribir una Gramática. Ahora tu ejemplo es muy simple para una gramática, pero El módulo de Damian Conway Regexp::Gramáticas es realmente genial en esto. Si tienes que cultivar esto, descubrirás que te hará la vida mucho más fácil. Yo lo uso bastante aquí-es una especie de perl6-ish.

use Regexp::Grammars;
use Data::Dumper;
use strict;
use warnings;

my $parser = qr{
    <[pair]>+
    <rule: pair>     <key>=(?:"<list>"|<value=literal>)
    <token: key>     var\d+
    <rule: list>     <[MATCH=literal]> ** (,)
    <token: literal> \S+

}xms;

q[var1=100 var2=90 var5=hello var3="a, b, c" var7=test var3=hello] =~ $parser;
die Dumper {%/};

Salida:

$VAR1 = {
          '' => 'var1=100 var2=90 var5=hello var3="a, b, c" var7=test var3=hello',
          'pair' => [
                      {
                        '' => 'var1=100',
                        'value' => '100',
                        'key' => 'var1'
                      },
                      {
                        '' => 'var2=90',
                        'value' => '90',
                        'key' => 'var2'
                      },
                      {
                        '' => 'var5=hello',
                        'value' => 'hello',
                        'key' => 'var5'
                      },
                      {
                        '' => 'var3="a, b, c"',
                        'key' => 'var3',
                        'list' => [
                                    'a',
                                    'b',
                                    'c'
                                  ]
                      },
                      {
                        '' => 'var7=test',
                        'value' => 'test',
                        'key' => 'var7'
                      },
                      {
                        '' => 'var3=hello',
                        'value' => 'hello',
                        'key' => 'var3'
                      }
                    ]
 7
Author: Evan Carroll,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-05 03:45:11

Un poco exagerado tal vez, pero una excusa para investigar http://p3rl.org/Parse::RecDescent . ¿Qué tal hacer un analizador?

#!/usr/bin/perl

use strict;
use warnings;

use Parse::RecDescent;

use Regexp::Common;

my $grammar = <<'_EOGRAMMAR_'
INTEGER: /[-+]?\d+/
STRING: /\S+/
QSTRING: /$Regexp::Common::RE{quoted}/

VARIABLE: /var\d+/
VALUE: ( QSTRING | STRING | INTEGER )

assignment: VARIABLE "=" VALUE /[\s]*/ { print "$item{VARIABLE} => $item{VALUE}\n"; }

startrule: assignment(s)
_EOGRAMMAR_
;

$Parse::RecDescent::skip = '';
my $parser = Parse::RecDescent->new($grammar);

my $code = q{var1=100 var2=90 var5=hello var3="a, b, c" var7=test var8=" haha \" heh " var3=hello};
$parser->startrule($code);

Rinde:

var1 => 100
var2 => 90
var5 => hello
var3 => "a, b, c"
var7 => test
var8 => " haha \" heh "
var3 => hello

PS. Tenga en cuenta la doble var3, si desea que esta última asignación sobrescriba la primera, puede usar un hash para almacenar los valores y luego usarlos más tarde.

PPS. Mi primer pensamiento fue dividir en ' = 'pero eso fallaría si una cadena contuviera' = ' y ya que las expresiones regulares son casi siempre malas para el análisis, bueno, terminó probándolo y funciona.

Edit: Se agregó soporte para comillas escapadas dentro de cadenas entrecomilladas.

 4
Author: nicomen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-12 11:03:56

Recientemente he tenido que analizar las líneas "Asunto" de los certificados x509. Tenían una forma similar a la que usted ha proporcionado:

echo 'Subject: C=HU, L=Budapest, O=Microsec Ltd., CN=Microsec e-Szigno Root CA 2009/[email protected]' | \
  perl -wne 'my @a = m/(\w+\=.+?)(?=(?:, \w+\=|$))/g; print "$_\n" foreach @a;'

C=HU
L=Budapest
O=Microsec Ltd.
CN=Microsec e-Szigno Root CA 2009/[email protected]

Breve descripción de la expresión regular:

(\w+\=.+?) - captura palabras seguidas de ' = ' y cualquier símbolo posterior en modo no codicioso
(?=(?:, \w+\=|$)) - que son seguidos por otro , KEY=val o final de línea.

La parte interesante de la expresión regular utilizada son:

  • .+? - Modo no codicioso
  • (?:pattern) - Modo de no captura
  • (?=pattern) aserción positiva de ancho cero
 3
Author: Delian Krustev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-02-23 10:14:41

Este también le proporcionará escapes comunes entre comillas dobles como por ejemplo var3="a, \"b, c".

@a = /(\w+=(?:\w+|"(?:[^\\"]*(?:\\.[^\\"]*)*)*"))/g;

En acción:

echo 'var1=100 var2=90 var42="foo\"bar\\" var5=hello var3="a, b, c" var7=test var3=hello' |
perl -nle '@a = /(\w+=(?:\w+|"(?:[^\\"]*(?:\\.[^\\"]*)*)*"))/g; $,=","; print @a'
var1=100,var2=90,var42="foo\"bar\\",var5=hello,var3="a, b, c",var7=test,var3=hello
 2
Author: Hynek -Pichi- Vychodil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-11 17:18:43
#!/usr/bin/perl

use strict; use warnings;

use Text::ParseWords;
use YAML;

my $string =
    "var1=100 var2=90 var5=hello var3=\"a, b, c\" var7=test var3=hello";

my @parts = shellwords $string;
print Dump \@parts;

@parts = map { { split /=/ } } @parts;

print Dump \@parts;
 2
Author: Sinan Ünür,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-12 01:59:49

Usted pidió una solución RegEx u otro código. Aquí hay una solución (en su mayoría) no regex que utiliza solo módulos principales. La única expresión regular es \s+ para determinar el delimitador; en este caso uno o más espacios.

use strict; use warnings;
use Text::ParseWords;
my $string="var1=100 var2=90 var5=hello var3=\"a, b, c\" var7=test var3=hello";  

my @array = quotewords('\s+', 0, $string);

for ( my $i = 0; $i < scalar( @array ); $i++ )
{
    print $i.": ".$array[$i]."\n";
}

O puedes ejecutar el código AQUÍ

La salida es:

0: var1=100
1: var2=90
2: var5=hello
3: var3=a, b, c
4: var7=test
5: var3=hello

Si realmente quieres una solución regex, el comentario de Alan Moore ¡enlazar a su código en IDEone es el gas!

 1
Author: dawg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-12 01:34:44

Es posible hacer esto con expresiones regulares, sin embargo es frágil.

my $string = "var1=100 var2=90 var5=hello var3=\"a, b, c\" var7=test var3=hello";

my $regexp = qr/( (?:\w+=[\w\,]+) | (?:\w+=\"[^\"]*\") )/x;
my @matches = $string =~ /$regexp/g;
 0
Author: szbalint,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-12 11:11:00