Группа регулярных выражений в Perl: как захватить элементы в массив из группы регулярных выражений, которая соответствует неизвестному числу вхождений/multiple/variable из строки?
в Perl, как я могу использовать одну группировку регулярных выражений для захвата более одного вхождения, которое соответствует ей, в несколько элементов массива?
например, для строку:
var1=100 var2=90 var5=hello var3="a, b, c" var7=test var3=hello
обрабатывать этот код:
$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";
}
Я хотел бы видеть в качестве вывода:
0: var1=100
1: var2=90
2: var5=hello
3: var3="a, b, c"
4: var7=test
5: var3=hello
что бы я использовал в качестве регулярного выражения?
общность между вещами, которые я хочу сопоставить здесь, - это шаблон строки назначения, поэтому что-то например:
my @array = $string =~ m/(w+=[w",s]+)*/;
где * указывает на одно или несколько вхождений, соответствующих группе.
(Я дисконтировал с помощью split (), поскольку некоторые совпадения содержат пробелы внутри себя (т. е. var3...) и поэтому не даст желаемых результатов.)
с вышеуказанным регулярным выражением я получаю только:
0: var1=100 var2
возможно ли в regex? Или требуется код добавления?
посмотрел на существующие ответы уже, при поиске "perl regex multiple group", но не достаточно подсказок:
- работа с несколькими группами захвата в нескольких записях
- несколько матчей в группе регулярных выражений?
- регулярное выражение: повторные группы захвата
- регулярное выражение матч и группировка
- как я regex матч с группировкой с неизвестным количеством групп
- awk извлекает несколько групп из каждой линия
- сопоставление нескольких групп регулярных выражений и их удаление
- Perl: удаление нескольких строк reccuring, где выполняется определенный критерий
- регулярное выражение, соответствующее нескольким группам в строке?
- PHP RegEx группировка несколько матчей
- как найти несколько вхождений с группами регулярных выражений?
9 ответов
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";
}
принты:
<var1> => <100>
<var2> => <90>
<var5> => <hello>
<var3> => <"a, b, c">
<var7> => <test>
<var3> => <hello>
объяснение:
последняя часть первая:g
флаг в конце означает, что вы можете применить регулярное выражение к строке несколько раз. Во второй раз он продолжит сопоставление, где последний матч закончился в строке.
теперь для регулярных выражений: (?:^|\s+)
соответствует либо началу строки, либо группе из одного или нескольких пробелов. Это необходимо, поэтому, когда регулярное выражение будет применено в следующий раз, мы пропустим пробелы между пары ключ / значение. The ?:
означает, что содержимое круглых скобок не будет захвачено как группа (нам не нужны пробелы, только ключ и значение). \S+
соответствует имени переменной. Затем мы пропускаем любое количество пробелов и знак равенства между ними. Наконец,("[^"]*"|\S*)/
соответствует либо двум кавычкам с любым количеством символов между ними, либо любому количеству символов без пробела для значения. Обратите внимание, что сопоставление цитат довольно хрупкое и не будет обрабатывать escpaped цитаты должным образом, например "\"quoted\""
будет в результате "\"
.
EDIT:
поскольку вы действительно хотите получить все назначение, а не отдельные ключи / значения, вот одна строка, которая извлекает их:
my @list = $string =~ /(?:^|\s+)((?:\S+)\s*=\s*(?:"[^"]*"|\S*))/g;
с регулярными выражениями используйте технику, которую я люблю называть tack-and-stretch: якорь на функциях, которые вы знаете, будут там (tack), а затем захватить то, что между (stretch).
в этом случае, вы знаете, что одно задание матчей
\b\w+=.+
и многие из них повторяются в $string
. Запомните это \b
означает границу слова:
граница слова (
\b
) - это место между двумя персонажами, что есть\w
на одном стороны и\W
С другой стороны (в любом порядке), считая воображаемые символы с начала и конца строки, соответствующие\W
.
значения в назначениях могут быть немного сложно описать с помощью регулярного выражения, но вы также знаете, что каждое значение будет заканчиваться пробелами-хотя и не обязательно первыми пробелами!- затем либо другое задание, либо конец строки.
чтобы избежать повторяя шаблон утверждения, скомпилируйте его один раз с помощью qr//
и повторно используйте его в своем шаблоне вместе с смотрим-впереди утверждение (?=...)
растянуть совпадение достаточно далеко, чтобы захватить все значение, а также предотвратить его утечку в следующее имя переменной.
сопоставление с вашим шаблоном в контексте списка с m//g
дает следующее поведение:
на
/g
модификатор задает глобальный шаблон соответствие-это соответствие как можно больше раз в строке. Его поведение зависит от контекста. В контексте списка он возвращает список подстрок соответствовать любые скобок в регулярном выражении. Если скобок нет, он возвращает список всех совпадающих строк, как если бы круглые скобки были вокруг всего шаблона.
шаблон $assignment
использует не жадный .+?
отрезать значение, как только взгляд вперед видит другой назначение или конец строки. Помните, что совпадение возвращает подстроки из все захват подзаголовков, поэтому чередование look-ahead использует не захват (?:...)
. The qr//
, напротив, содержит неявные скобок.
#! /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";
}
выход:
0: var1=100 1: var2=90 2: var5=hello 3: var3="a, b, c" 4: var7=test 5: var3=hello
Я не говорю, что это то, что вы должны do, но то, что вы пытаетесь сделать, это написать грамматика. Теперь ваш пример очень простой для грамматики, но Дамиан КонвейС Regexp:: Grammars и действительно большой в этом. Если вам нужно вырастить это вообще, вы обнаружите, что это сделает вашу жизнь намного проще. Я использую его совсем немного здесь - это своего рода perl6-иш.
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 {%/};
выход:
$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'
}
]
немного сверху, может быть, но оправдание для меня, чтобы посмотреть в http://p3rl.org/Parse::RecDescent. Как насчет создания парсера?
#!/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);
выходы:
var1 => 100
var2 => 90
var5 => hello
var3 => "a, b, c"
var7 => test
var8 => " haha \" heh "
var3 => hello
PS. Обратите внимание на double var3, если вы хотите, чтобы последнее назначение перезаписало первое, вы можете использовать хэш для хранения значений, а затем использовать их позже.
PPS. Моя первая мысль состояла в том, чтобы разделить на"=", но это не сработает, если строка содержит " = " и поскольку регулярные выражения почти всегда плохо для разбора, ну, я в конечном итоге попробовал его, и он работает.
Edit: добавлена поддержка экранированных кавычек внутри цитируемых строк.
недавно мне пришлось анализировать строки "тема" сертификатов x509. Они имели форму, аналогичную той, которую вы предоставили:
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
краткое описание регулярных выражений:
(\w+\=.+?)
- захват слов, за которыми следуют " = " и любые последующие символы в не жадном режиме(?=(?:, \w+\=|$))
- за которыми следует либо другой , KEY=val
или конец строки.
интересная часть используемого регулярного выражения:
-
.+?
- не жадный режим -
(?:pattern)
- для съемки в режиме -
(?=pattern)
нулевая ширина положительный взгляд вперед утверждение
Это обеспечит вам также общее экранирование в двойных кавычках, как, например, var3= " a, \"b, c".
@a = /(\w+=(?:\w+|"(?:[^\"]*(?:\.[^\"]*)*)*"))/g;
действие:
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
#!/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;
вы попросили решение RegEx или другой код. Вот (в основном) не регулярное решение, использующее только основные модули. Единственное регулярное выражение \s+
для определения разделителя; в этом случае один или несколько пробелов.
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";
}
или вы можете выполнить код здесь
выход:
0: var1=100
1: var2=90
2: var5=hello
3: var3=a, b, c
4: var7=test
5: var3=hello
Если вы действительно хотите решение regex, Алан Мур комментарий ссылка на его код на IDEone-это газ!
Это можно сделать с помощью регулярных выражений, однако это хрупко.
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;