Groupe Regex dans Perl: comment capturer des éléments dans un tableau à partir du groupe regex qui correspond au nombre inconnu d'occurrences / multiples / variables à partir d'une chaîne de caractères?

dans Perl, Comment puis-je utiliser un groupement regex pour capturer plus d'un événement qui le correspond, en plusieurs éléments de tableau?

par exemple, pour une chaîne de caractères:

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

à traiter avec code:

$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";
}

je voudrais voir comme sortie:

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

qu'est-ce que j'utiliserais comme regex?

le point commun entre les choses que je veux faire correspondre ici est un motif de chaîne d'assignation, donc quelque chose comme:

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

où le * indique un ou plusieurs événements correspondant au groupe.

(je actualisés à l'aide d'un split() de certains matchs contenir des espaces à l'intérieur d'eux-mêmes (c'est à dire var3...) et ne donnerait donc pas les résultats souhaités.)

avec le regex ci-dessus, je n'obtiens que:

0: var1=100 var2

est-ce possible dans un regex? Ou code d'ajout requis?

a déjà examiné les réponses existantes, lors de la recherche de "perl regex multiple group" mais pas assez d'indices:

43
demandé sur Community 2010-08-11 18:59:35

9 réponses

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 "<> => <>\n";
}

Imprime:

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

explication:

dernière pièce en premier: le drapeau g à la fin signifie que vous pouvez appliquer le regex à la chaîne plusieurs fois. La deuxième fois, il continuera à correspondre là où le dernier match s'est terminé dans la chaîne.

maintenant pour le regex: (?:^|\s+) correspond soit au début de la chaîne, soit à un groupe d'un ou plusieurs espaces. Cela est nécessaire si lorsque le regex est appliqué la prochaine fois, nous sauterons les espaces entre les paires clé/valeur. Le ?: signifie que le contenu entre parenthèses ne sera pas capturé en tant que groupe (Nous n'avons pas besoin des espaces, seulement clé et valeur). \S+ correspond au nom de la variable. Puis nous sauter n'importe quelle quantité d'espaces et d'un signe égal entre les deux. Enfin, ("[^"]*"|\S*)/ correspond soit à deux guillemets avec n'importe quelle quantité de caractères entre, ou n'importe quelle quantité de caractères sans espace pour la valeur. Notez que l'appariement de devis est assez fragile et ne traitera pas correctement les soumissions omises, par exemple "\"quoted\"" aurait pour résultat "\" .

EDIT:

puisque vous voulez vraiment obtenir l'ensemble de la tâche, et pas les clés/valeurs simples, voici un one-liner qui extrait ceux:

my @list = $string =~ /(?:^|\s+)((?:\S+)\s*=\s*(?:"[^"]*"|\S*))/g;
39
répondu jkramer 2010-08-12 10:18:32

avec des expressions régulières, utilisez une technique que j'aime appeler tack-and-stretch: ancre sur les fonctionnalités que vous savez sera là (tack) et puis saisir ce qui est entre (stretch).

dans ce cas, vous savez qu'une seule affectation correspond

\b\w+=.+

et vous avez beaucoup de ces répétés dans $string . Rappelez-vous que \b signifie limite de mots:

un mot limite ( \b ) est un place entre deux caractères qui ont un \w d'un côté et un \W de l'autre côté (dans l'un ou l'autre ordre), en comptant les caractères imaginaires du début et de la fin de la chaîne comme correspondant à un \W .

les valeurs dans les assignations peuvent être un peu difficiles à décrire avec une expression régulière, mais vous savez aussi que chaque valeur se terminera avec des espaces-bien que pas nécessairement le premier espace rencontrés!- suivi soit d'une autre assignation, soit d'une fin de chaîne.

pour éviter de répéter le modèle d'assertion, compilez-le une fois avec qr// et réutilisez-le dans votre modèle avec une assertion de prospective (?=...) pour étirer la correspondance juste assez loin pour capturer la valeur entière tout en l'empêchant de se déverser dans le nom de la variable suivante.

correspondant à votre modèle dans un contexte de liste avec m//g donne le comportement suivant:

le modificateur /g spécifie l'appariement global des motifs-c'est-à-dire l'appariement le plus de fois possible dans la chaîne. Son comportement dépend du contexte. Dans le contexte list, il renvoie une liste des sous-chaînes correspondant à des parenthèses de capture dans l'expression regular. S'il n'y a pas de parenthèses, il renvoie une liste de toutes les chaînes assorties, comme s'il y avait les parenthèses autour de l'ensemble du motif.

Le modèle $assignment utilise non-greedy .+? pour couper la valeur dès que le " look-ahead voit une autre affectation ou de fin de ligne. N'oubliez pas que le match renvoie les sous-couches de all capturing subpatterns, de sorte que l'alternance de l'avenir utilise non-capture (?:...) . Le qr// , en revanche, contient des parenthèses de saisie implicites.

#! /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";
}

sortie:

0: var1=100
1: var2=90
2: var5=hello
3: var3="a, b, c"
4: var7=test
5: var3=hello
8
répondu Greg Bacon 2010-08-13 09:59:39

Je ne dis pas que c'est ce que vous devriez faire, mais ce que vous essayez de faire est d'écrire une grammaire . Maintenant, votre exemple est très simple pour une Grammaire, mais Damian Conway module Regexp::Grammaires est vraiment grande à ce. Si tu dois faire pousser ça, tu verras que ça te rendra la vie plus facile. Je l'utilise tout à fait un peu ici, c'est un peu 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 {%/};

sortie:

$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
répondu Evan Carroll 2015-03-05 03:45:11

un peu exagéré peut-être, mais une excuse pour que je regarde dans http://p3rl.org/Parse::RecDescent . Comment au sujet de faire un parser?

#!/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);

donne:

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

PS. Notez le double var3, si vous voulez que cette dernière tâche écrase la première, vous pouvez utiliser un hachage pour stocker les valeurs, puis les utiliser plus tard.

PPS. Ma première pensée a été de se séparer sur ' = ' mais cela échouerait si une chaîne contenait ' = ' Et puisque regexps est presque toujours mauvais pour l'analyse, j'ai fini par l'essayer et ça marche.

Edit: ajout d'un support pour les guillemets échappés dans les chaînes de caractères.

4
répondu nicomen 2010-08-12 11:03:56

j'ai récemment dû analyser les lignes" Subject " des certificats x509. Ils avaient une forme semblable à celle que vous avez fournie:

echo 'Subject: C=HU, L=Budapest, O=Microsec Ltd., CN=Microsec e-Szigno Root CA 2009/emailAddress=info@e-szigno.hu' | \
  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/emailAddress=info@e-szigno.hu

Brève description du regex:

(\w+\=.+?) - mots de saisie suivis de " = "et de tout symbole subséquent en mode non cupide

(?=(?:, \w+\=|$)) - qui sont suivis soit d'un autre , KEY=val ou d'une fin de ligne.

la partie intéressante du regex utilisé sont:

  • .+? - mode Non gourmand
  • (?:pattern) - Non mode de capture
  • (?=pattern) de largeur zéro positif "look-ahead" assertion
3
répondu Delian Krustev 2012-02-23 10:14:41

celui-ci vous fournira aussi l'échappatoire commune dans les guillemets comme par exemple var3="a, \"b, c".

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

en action:

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
répondu Hynek -Pichi- Vychodil 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
répondu Sinan Ünür 2010-08-12 01:59:49

vous avez demandé une solution RegEx ou un autre code. Voici une solution (la plupart du temps) non regex utilisant uniquement des modules de base. La seule regex est \s+ pour déterminer le délimiteur; dans ce cas, un ou plusieurs espaces.

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";
}

ou vous pouvez exécuter le code ici

la sortie est:

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

si vous voulez vraiment une solution regex, le de Alan Moore "commentaire lien vers son code sur IDEone est le gaz!

1
répondu dawg 2010-08-12 01:34:44

il est possible de faire cela avec regexes, cependant il est fragile.

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
répondu szbalint 2010-08-12 11:11:00