Regex Group в Perl: как захватить элементы в массив из regex-группы, которая соответствует неизвестному количеству/многим/переменным вхождениям из строки?

В Perl, как я могу использовать одну группировку regex для захвата более чем одного вхождения, соответствующего ей, в несколько элементов массива?

Например, для строки:

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

Что я мог бы использовать в качестве regex?

Общность между вещами, которые я хочу сопоставить здесь, это шаблон строки присвоения, поэтому что-то вроде:

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

Где * указывает на одно или более вхождений, соответствующих группе.

(Я отказался от использования split(), так как некоторые совпадения содержат пробелы внутри себя (например, var3...) и поэтому не дадут желаемых результатов)

С помощью вышеприведенного регекса я получаю:

0: var1=100 var2

Возможно ли это в регексе? Или требуется дополнительный код?

Просмотрел уже существующие ответы, когда искал "perl regex multiple group", но недостаточно подсказок:

47
задан Community 23 May 2017 в 12:09
поделиться

8 ответов

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 *) / соответствует либо двум кавычкам с любым количеством символов между ними, либо любому количеству непробельных символов для значения. Обратите внимание, что кавычка сопоставление довольно хрупкое и не будет обрабатывать скрытые кавычки должным образом, например "\" quoted \ "" приведет к "\" .

РЕДАКТИРОВАТЬ:

Поскольку вы действительно хотите получить все назначение, а не отдельные ключи / значения, вот однострочник, который извлекает их:

my @list = $string =~ /(?:^|\s+)((?:\S+)\s*=\s*(?:"[^"]*"|\S*))/g;
43
ответ дан 26 November 2019 в 19:47
поделиться

Возможно, немного преувеличенно, но для меня это повод заглянуть в 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, если вы хотите, чтобы последнее назначение перезаписало первое, вы можете использовать хеш для хранения значений, а затем использовать их позже.

ППС. Моей первой мыслью было разделить на '=', но это не удалось, если строка содержала '=', а поскольку регулярные выражения почти всегда плохо подходят для синтаксического анализа, в конце концов я попробовал это, и он работает.

Редактировать: Добавлена ​​поддержка экранированных кавычек внутри строк в кавычках.

4
ответ дан 26 November 2019 в 19:47
поделиться

Вы запросили решение 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

Если вам действительно нужно решение с регулярным выражением, комментарий Алана Мура, ссылающийся на его код в IDEone, - это газ!

1
ответ дан 26 November 2019 в 19:47
поделиться

Это можно сделать с помощью регулярных выражений, но это ненадежно.

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
ответ дан 26 November 2019 в 19:47
поделиться

Я не говорю, что это то, что вы должны делать, но то, что вы пытаетесь сделать, это написать грамматику. Сейчас ваш пример очень прост для грамматики, но модуль Damian Conway 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 {%/};

Output:

$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
ответ дан 26 November 2019 в 19:47
поделиться

Это обеспечит вам также обычное экранирование в двойных кавычках, как, например, 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
2
ответ дан 26 November 2019 в 19:47
поделиться

В регулярных выражениях используйте технику, которую я люблю называть "прихватить и растянуть": зацепитесь за признаки, которые, как вы знаете, будут там (прихватите), а затем захватите то, что между ними (растяните).

В данном случае вы знаете, что подходит одно присваивание

\b\w+=.+

и у вас есть много таких присваиваний, повторяющихся в $string. Помните, что \b означает границу слова:

Граница слова (\b) - это место между двумя символами, которое имеет \w с одной стороны и \W с другой стороны (в любом порядке), считая воображаемые символы от начала и конца строки как совпадающие с \W.

Значения в присваиваниях могут быть немного сложными для описания регулярным выражением, но вы также знаете, что каждое значение будет заканчиваться пробелом - хотя не обязательно первым встреченным пробелом! - за которым следует либо другое присваивание, либо конец строки.

Чтобы не повторять шаблон утверждения, скомпилируйте его один раз с помощью qr// и повторно используйте его в шаблоне вместе с утверждением look-ahead (?=...) чтобы растянуть совпадение достаточно далеко, чтобы захватить все значение, но при этом предотвратить его перетекание в имя следующей переменной.

Сопоставление с образцом в контексте списка с помощью 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";
}

Output:

0: var1=100
1: var2=90
2: var5=hello
3: var3="a, b, c"
4: var7=test
5: var3=hello
8
ответ дан 26 November 2019 в 19:47
поделиться
#!/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
ответ дан 26 November 2019 в 19:47
поделиться
Другие вопросы по тегам:

Похожие вопросы: