Regex Group в Perl: как захватить элементы в массив из группы регулярных выражений, которая соответствует неизвестному количеству/множественных/переменных вхождений из строки?
В 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
Возможно ли это в регулярном выражении? Или требуется дополнительный код?
Посмотрел уже на существующие ответы при поиске "perl regex multiple group", но не хватает подсказок:
Ответы
Ответ 1
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";
}
Печать
<var1> => <100>
<var2> => <90>
<var5> => <hello>
<var3> => <"a, b, c">
<var7> => <test>
<var3> => <hello>
Пояснение:
Последний фрагмент: флаг g
в конце означает, что вы можете применить регулярное выражение к строке несколько раз. Второй раз он будет продолжать сопоставлять, где последний матч закончился в строке.
Теперь для регулярного выражения: (?:^|\s+)
совпадает либо начало строки, либо группа из одного или нескольких пробелов. Это необходимо, поэтому, когда регулярное выражение применяется в следующий раз, мы пропустим пробелы между парами ключ/значение. ?:
означает, что содержимое скобок не будет записано как группа (нам не нужны пробелы, только ключ и значение). \S+
соответствует имени переменной. Затем мы пропускаем любое количество пробелов и знак равенства между ними. Наконец, ("[^"]*"|\S*)/
соответствует двум кавычкам с любым количеством символов между ними или любым количеством непространственных символов для значения. Обратите внимание, что сопоставление котировок довольно хрупкое и не будет правильно обрабатывать escpaped кавычки, например. "\"quoted\""
приведет к "\"
.
EDIT:
Так как вы действительно хотите получить все задание, а не отдельные ключи/значения, здесь один слой, который извлекает их:
my @list = $string =~ /(?:^|\s+)((?:\S+)\s*=\s*(?:"[^"]*"|\S*))/g;
Ответ 2
С помощью регулярных выражений используйте технику, которую мне нравится называть "лайк-и-растяжкой": привязка к функциям, которые, как вы знаете, будут там (клейкой), а затем захватить то, что между (растяжкой).
В этом случае вы знаете, что одно присваивание соответствует
\b\w+=.+
и многие из них повторяются в $string
. Помните, что \b
означает границу слова:
Граница слов (\b
) - это пятно между двумя символами, имеющее a \w
с одной стороны от него и a \w
с другой стороны (в любом порядке), считая мнимые символы начало и конец строки в соответствии с \w
.
Значения в присваиваниях могут быть немного сложными для описания с регулярным выражением, но вы также знаете, что каждое значение будет заканчиваться пробелом, но не обязательно первым встреченным пробелом! - после другого назначения или окончания -string.
Чтобы избежать повторения шаблона утверждения, скомпилируйте его один раз с помощью qr//
и повторно используйте его в своем шаблоне вместе с look-ahead assertion (?=...)
, чтобы растянуть совпадение достаточно далеко, чтобы зафиксировать все значение, а также не позволять ему перетекать в следующее имя переменной.
Соответствие вашему шаблону в контексте списка с помощью m//g
дает следующее поведение:
Модификатор /g
определяет глобальное сопоставление шаблонов, то есть сопоставление как можно больше в строке. Как он себя ведет, зависит от контекста. В контексте списка он возвращает список подстрок, соответствующих любым скобкам в регулярном выражении. Если круглых скобок нет, он возвращает список всех совпадающих строк, как если бы вокруг всего шаблона были круглые скобки.
В шаблоне $assignment
используется не-жадный .+?
, чтобы отключить значение, как только смотровое окно увидит другое назначение или конец строки. Помните, что совпадение возвращает подстроки из всех захваченных подшаблонов, поэтому альтернативное чередование использует не захватывающий (?:...)
. 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
Ответ 3
Я не говорю, что это то, что вы должны делать, но то, что вы пытаетесь сделать, это написать Грамматику. Теперь ваш пример очень прост для грамматики, но Damian Conway модуль Regexp::Grammars действительно замечательно. Если вам нужно вырастить это вообще, вы обнаружите, что это сделает вашу жизнь намного проще. Я использую его совсем немного - это своего рода 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 {%/};
Вывод:
$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'
}
]
Ответ 4
Немного позади, может быть, но для меня поводом для изучения 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. Обратите внимание на двойную переменную var3, если вы хотите, чтобы последнее назначение было перезаписано первым, вы можете использовать хеш для хранения значений, а затем использовать их позже.
ПФС. Моя первая мысль заключалась в том, чтобы разделить на "=", но это не получится, если строка содержит "=", и поскольку регулярные выражения почти всегда плохи для синтаксического анализа, я в конечном итоге попытался это сделать, и он работает.
Изменить: добавлена поддержка экранированных кавычек внутри цитируемых строк.
Ответ 5
Мне недавно пришлось разбирать строки x509 "Тема". Они имели схожую форму с той, которую вы предоставили:
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]
Краткое описание регулярного выражения:
(\w+\=.+?)
- захватывать слова, за которыми следует '=', и любые последующие символы в не жадном режиме
(?=(?:, \w+\=|$))
- за которым следует либо другой , KEY=val
, либо конец строки.
Интересная часть используемого регулярного выражения:
-
.+?
- Нежелательный режим
-
(?:pattern)
- Режим без захвата
-
(?=pattern)
положительное утверждение с ожидающей надежностью нулевой ширины
Ответ 6
Это даст вам также общее экранирование в двойных кавычках, например, 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
Ответ 7
#!/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;
Ответ 8
Вы запросили решение 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
Если вам действительно нужно решение регулярного выражения, Alan Moore комментарий, связанный с его кодом на IDEone, это газ!
Ответ 9
Это можно сделать с помощью регулярных выражений, однако оно хрупкое.
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;