Сопоставить все вхождения строки

Мой поисковый текст выглядит следующим образом.

...
...
var strings = ["aaa","bbb","ccc","ddd","eee"];
...
...

Он содержит много строк (на самом деле файл javascript), но необходимо проанализировать значения в переменных strings, то есть aaa, bbb, ccc, ddd, eee.

Ниже приведен код Perl или используйте PHP внизу.

my $str = <<STR;
    ...
    ...
    var strings = ["aaa","bbb","ccc","ddd","eee"];
    ...
    ...
STR

my @matches = $str =~ /(?:\"(.+?)\",?)/g;
print "@matches";

Я знаю, что приведенный выше сценарий будет соответствовать всем моментам, но он также будет анализировать строки ("xyz") в других строках. Поэтому мне нужно проверить строку var strings =

/var strings = \[(?:\"(.+?)\",?)/g

Используя вышеуказанное регулярное выражение, он проанализирует aaa.

/var strings = \[(?:\"(.+?)\",?)(?:\"(.+?)\",?)/g

Используя выше, получим aaa и bbb. Поэтому, чтобы избежать повторения регулярного выражения, я использовал квантификатор «+», как показано ниже.

/var strings = \[(?:\"(.+?)\",?)+/g

Но я получил только eee. Итак, у меня вопрос, почему я получил eee ТОЛЬКО при использовании квантификатора "+"?

Обновление 1: использование PHP preg_match_all (делаю это, чтобы привлечь больше внимания :-))

$str = <<<STR
    ...
    ...
    var strings = ["aaa","bbb","ccc","ddd","eee"];
    ...
    ...
STR;

preg_match_all("/var strings = \[(?:\"(.+?)\",?)+/",$str,$matches);
print_r($matches);

Обновление 2: почему он соответствует eee? Из-за жадности (?:\"(.+?)\",?)+. Удалив жадность, будет найдено соответствие /var strings = \[(?:\"(.+?)\",?)+?/ aaa. Но почему только один результат? Есть ли способ добиться этого с помощью одного регулярного выражения?


person Jithin    schedule 19.07.2012    source источник


Ответы (3)


Вот решение с одним регулярным выражением:

/(?:\bvar\s+strings\s*=\s*\[|\G,)\s*"([^"]*)"/g

\G - это утверждение нулевой ширины, которое соответствует позиции, где закончилось предыдущее совпадение (или началу строки, если это первая попытка совпадения). Итак, это действует как:

var\s+strings\s*=\s*[\s*"([^"]*)"

... с первой попытки, затем:

,\s*"([^"]*)"

... после этого, но каждый матч должен начинаться именно там, где закончился последний.

Вот демонстрация на PHP, но она также будет работать на Perl.

person Alan Moore    schedule 19.07.2012
comment
Да, это сработало. Спасибо... :-). Но не могли бы вы объяснить, почему это не сработало для /var strings = \[(?:\"(.+?)\",?)+?/? - person Jithin; 19.07.2012
comment
Если вы оставите g в своей версии Perl или вызовете preg_match вместо preg_match_all, вы увидите, что получите те же результаты; на самом деле вы проводите только один матч. В рамках этого совпадения часть в группе захвата применяется несколько раз, каждый раз перезаписывая результат последнего прохода. Я провожу несколько матчей и сохраняю результат каждого матча индивидуально. - person Alan Moore; 19.07.2012

Вы можете предпочесть это решение, которое сначала ищет строку var strings = [ с помощью модификатора /g. Это устанавливает \G для совпадения сразу после [ для следующего регулярного выражения, которое ищет все сразу следующие вхождения строк в двойных кавычках, возможно, которым предшествуют запятые или пробелы.

my @matches;

if ($str =~ /var \s+ strings \s* = \s* \[ /gx) {
  @matches = $str =~ /\G [,\s]* "([^"]+)" /gx;
}

Несмотря на использование модификатора /g, ваше регулярное выражение /var strings = \[(?:\"(.+?)\",?)+/g соответствует только один раз, потому что нет второго вхождения var strings = [. Каждое совпадение возвращает список значений переменных захвата $1, $2, $3 и т. Д., Когда совпадение завершено, а /(?:"(.+?)",?)+/ (нет необходимости избегать двойных кавычек) захватывает несколько значений в $1, оставляя там только последнее значение. Вам нужно написать что-то вроде приведенного выше, которое фиксирует только одно значение в $1 для каждого совпадения.

person Borodin    schedule 19.07.2012

Потому что + указывает ему повторить то же самое в скобках (?:"(.+?)",?) один или несколько раз. Таким образом, он будет соответствовать "eee" строке, а затем искать повторы этой "eee" строки, которых он не находит.

use YAPE::Regex::Explain;
print YAPE::Regex::Explain->new(qr/var strings = \[(?:"(.+?)",?)+/)->explain();

The regular expression:

(?-imsx:var strings = \[(?:"(.+?)",?)+)

matches as follows:

NODE                     EXPLANATION
----------------------------------------------------------------------
(?-imsx:                 group, but do not capture (case-sensitive)
                         (with ^ and $ matching normally) (with . not
                         matching \n) (matching whitespace and #
                         normally):
----------------------------------------------------------------------
  var strings =            'var strings = '
----------------------------------------------------------------------
  \[                       '['
----------------------------------------------------------------------
  (?:                      group, but do not capture (1 or more times
                           (matching the most amount possible)):
----------------------------------------------------------------------
    "                        '"'
----------------------------------------------------------------------
    (                        group and capture to \1:
----------------------------------------------------------------------
      .+?                      any character except \n (1 or more
                               times (matching the least amount
                               possible))
----------------------------------------------------------------------
    )                        end of \1
----------------------------------------------------------------------
    "                        '"'
----------------------------------------------------------------------
    ,?                       ',' (optional (matching the most amount
                             possible))
----------------------------------------------------------------------
  )+                       end of grouping
----------------------------------------------------------------------
)                        end of grouping
----------------------------------------------------------------------

Более простой пример:

my @m = ('abcd' =~ m/(\w)+/g);
print "@m";

Печатает только d. Это связано с:

use YAPE::Regex::Explain;
print YAPE::Regex::Explain->new(qr/(\w)+/)->explain();

The regular expression:

(?-imsx:(\w)+)

matches as follows:

NODE                     EXPLANATION
----------------------------------------------------------------------
(?-imsx:                 group, but do not capture (case-sensitive)
                         (with ^ and $ matching normally) (with . not
                         matching \n) (matching whitespace and #
                         normally):
----------------------------------------------------------------------
  (                        group and capture to \1 (1 or more times
                           (matching the most amount possible)):
----------------------------------------------------------------------
    \w                       word characters (a-z, A-Z, 0-9, _)
----------------------------------------------------------------------
  )+                       end of \1 (NOTE: because you are using a
                           quantifier on this capture, only the LAST
                           repetition of the captured pattern will be
                           stored in \1)
----------------------------------------------------------------------
)                        end of grouping
----------------------------------------------------------------------

Если вы используете квантификатор в группе захвата, будет использоваться только последний экземпляр.


Вот способ, который работает:

my $str = <<STR;
    ...
    ...
    var strings = ["aaa","bbb","ccc","ddd","eee"];
    ...
    ...
STR

my @matches;
$str =~ m/var strings = \[(.+?)\]/; # get the array first
my $jsarray = $1;
@matches = $array =~ m/"(.+?)"/g; # and get the strings from that

print "@matches";

Обновление: однострочное решение (но не одно регулярное выражение):

@matches = ($str =~ m/var strings = \[(.+?)\]/)[0] =~ m/"(.+?)"/g;

Но это очень нечитабельно, imho.

person simbabque    schedule 19.07.2012
comment
Да, это круто. Спасибо. Но есть ли способ сделать это в одном регулярном выражении. - person Jithin; 19.07.2012
comment
@Jithin, по какой причине вам нужно одно регулярное выражение? его трудно читать, и, кроме того, все маленькие регулярные выражения работают быстрее, чем один - person gaussblurinc; 19.07.2012
comment
@simbabque В приведенном вами простом примере он соответствует только d из-за жадности. Изменение вашего выражения на m/(\w)+?/g будет соответствовать всем, т.е. a b c d. Но почему это не сработало для группового регулярного выражения в обновлении вопроса 2? - person Jithin; 19.07.2012
comment
@loldop Дело не в однострочном регулярном выражении. Почему не сработало? :-) - person Jithin; 19.07.2012
comment
@Jithin: но m/(\w)/g - тоже - проблема в +. В настоящий момент я не могу найти его в perlre. - person simbabque; 19.07.2012
comment
@simbabque Ищите жадность в perlre. - person Jithin; 19.07.2012
comment
@simbabque: Почему m и s залпом? - person Cylian; 19.07.2012
comment
@Jithin: Извините, я не могу найти ссылку на жадность в отношении /g и parens в perlre. @Cylian: Что ты имеешь в виду? В моем посте нет s///, не так ли? - person simbabque; 19.07.2012
comment
@simbabque Дополнительные сведения см. в perlretut. - person Jithin; 19.07.2012