web-dev-qa-db-fra.com

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

En Perl, comment puis-je utiliser un regroupement d'expressions régulières pour capturer plus d'une occurrence qui lui correspond, en plusieurs éléments de tableau?

Par exemple, pour une chaîne:

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

pour traiter cela avec du 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 en sortie:

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

Que devrais-je utiliser comme expression régulière?

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

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

Où le * indique une ou plusieurs occurrences correspondant au groupe.

(J'ai réduit l'utilisation d'un split () car certaines correspondances contiennent des espaces en elles-mêmes (c'est-à-dire var3 ...) et ne donneraient donc pas les résultats souhaités.)

Avec l'expression régulière ci-dessus, je ne reçois que:

0: var1=100 var2

Est-ce possible dans une expression régulière? Ou un code d'addition requis?

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

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

Tirages:

<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 l'expression régulière à la chaîne plusieurs fois. La deuxième fois, il continuera à correspondre là où la dernière correspondance s'est terminée dans la chaîne.

Maintenant, pour l'expression régulière: (?:^|\s+) correspond soit au début de la chaîne, soit à un groupe d'un ou plusieurs espaces. Ceci est nécessaire, donc lorsque l'expression régulière sera appliquée la prochaine fois, nous ignorerons 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 de la clé et de la valeur). \S+ correspond au nom de la variable. Ensuite, nous ignorons toute quantité d'espaces et un signe égal entre les deux. Finalement, ("[^"]*"|\S*)/ correspond à deux guillemets avec n'importe quelle quantité de caractères entre les deux, ou n'importe quelle quantité de caractères non-espace pour la valeur. Notez que la correspondance de devis est assez fragile et ne gérera pas correctement les citations échappées, par exemple "\"quoted\"" entraînerait "\".

MODIFIER:

Puisque vous voulez vraiment obtenir l'assignation entière, et non les clés/valeurs simples, voici un one-liner qui les extrait:

my @list = $string =~ /(?:^|\s+)((?:\S+)\s*=\s*(?:"[^"]*"|\S*))/g;
42
jkramer

Avec les expressions régulières, utilisez une technique que j'aime appeler le tack-and-stretch: ancrer sur les éléments dont vous savez qu'ils seront là (tack) puis saisir ce qu'il y a entre (stretch).

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

\b\w+=.+

et vous en avez plusieurs répétées dans $string. N'oubliez pas que \b signifie limite de mot:

Une limite de mot (\b) est un espace entre deux caractères qui a 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 affectations peuvent être un peu délicates à décrire avec une expression régulière, mais vous savez également que chaque valeur se terminera par un espace - bien que pas nécessairement le premier espace rencontré! - suivi d'une autre affectation ou 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 prospective (?=...) pour étirer la correspondance juste assez loin pour capturer la valeur entière tout en l'empêchant de se répandre dans le nom de variable suivant.

Correspondance avec votre modèle dans le contexte de la liste avec m//g donne le comportement suivant:

Le /g le modificateur spécifie la correspondance globale des motifs, c'est-à-dire la correspondance autant de fois que possible dans la chaîne. Son comportement dépend du contexte. Dans un contexte de liste, il renvoie une liste des sous-chaînes auxquelles correspondent toutes les parenthèses de capture dans l'expression régulière. S'il n'y a pas de parenthèses, il renvoie une liste de toutes les chaînes correspondantes, comme s'il y avait des parenthèses autour de l'ensemble du modèle.

Le motif $assignment utilise non gourmand .+? pour couper la valeur dès que l'anticipation voit une autre affectation ou fin de ligne. N'oubliez pas que la correspondance renvoie les sous-chaînes de tous les sous-modèles de capture, donc l'alternance de l'anticipation utilise la non capture (?:...). Le qr//, en revanche, contient des parenthèses de capture 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 = bonjour 
 3: var3 = "a, b, c" 
 4: var7 = test 
 5: var3 = bonjour
8
Greg Bacon

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 le module Regexp :: Grammars is vraiment excellent à cela. Si vous devez cultiver cela, vous constaterez que cela vous facilitera la vie. Je l'utilise un peu ici - c'est une sorte 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 {%/};

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
Evan Carroll

Un peu au-dessus peut-être, mais une excuse pour moi de regarder http://p3rl.org/Parse::RecDescent . Que diriez-vous de faire un analyseur?

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

rendements:

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

PS. Notez le double var3, si vous souhaitez que la dernière affectation é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 diviser sur '=' mais cela échouerait si une chaîne contenait '=' et comme les regexps sont presque toujours mauvais pour l'analyse, eh bien j'ai fini par l'essayer et cela fonctionne.

Edit: Ajout de la prise en charge des guillemets échappés dans les chaînes entre guillemets.

4
nicomen

J'ai récemment dû analyser les lignes "Objet" des certificats x509. Ils avaient un formulaire similaire à celui que vous avez fourni:

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]

Brève description de l'expression régulière:

(\w+\=.+?) - capture les mots suivis de '=' et de tout symbole suivant en mode non gourmand
(?=(?:, \w+\=|$)) - qui sont suivis par un autre , KEY=val ou fin de ligne.

La partie intéressante de l'expression régulière utilisée est:

  • .+? - Mode non gourmand
  • (?:pattern) - Mode sans capture
  • (?=pattern) assertion d'anticipation positive de largeur nulle
3
Delian Krustev

Celui-ci vous fournira également des échappements communs entre 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
#!/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
Sinan Ünür

Vous avez demandé une solution RegEx ou un autre code. Voici une solution (principalement) non regex utilisant uniquement des modules de base. Le seul 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 lien d'Alan Moore commentaire avec son code sur IDEone est le gaz!

1
dawg

Il est possible de le faire avec des expressions régulières, mais c'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
szbalint