Perl выборочно разбивать на пробелы - PullRequest
2 голосов
/ 01 августа 2020

Я пытаюсь разбить строку на пробелы между элементами в perl. Однако каждый элемент может также содержать пробелы (заключенные в двойные кавычки или заключенные в скобки).

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

for element in hydrogen   helium  "carbon  14"    $(some stuff "here")   FILE

Я хотел бы получить массив например, (hydrogen, helium, "carbon 14", "$(some stuff "here")", FILE)

Я могу работать с битом for element in, а все остальное получить как одну строку. Я пробовал делать

@elements = split /(?<=\"[^\"]*\")\s+(?=\"[^\"]*\")/, $list

, и хотя регулярное выражение ДЕЙСТВИТЕЛЬНО соответствует ТОЛЬКО пробелу между кавычками (проверено на regexr.com), программа perl дает мне Lookbehind longer than 255 not implemented in regex.

Может быть, есть лучший способ использовать split в пробелах, который учел бы это? Что я делаю не так с моим регулярным выражением?

Ответы [ 2 ]

5 голосов
/ 01 августа 2020

Соответствует выражению в кавычках или скобках, затем чередуется с последовательностью без пробелов

my @elems = $string =~ / ( "[^"]+" | \S*\( [^)]+ \)\S* | \S+ ) /gx;

Проверено с вашей строкой и некоторыми простыми вариантами.

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

Я разрешил использовать непробельную последовательность символов до и после круглой скобки, чтобы разместить $ впереди. Отрегулируйте это, если он действительно может только быть долларом впереди.

2 голосов
/ 01 августа 2020

В этих ситуациях я бы использовал go для синтаксического анализа. Таким образом, вам не нужно придумывать регулярное выражение, которое делает несколько разных вещей. Это важно, поскольку сложность строки меняется. Несмотря на то, что это выглядит как дополнительный код, это базовый c Perl, и вы помещаете его в подпрограмму. Я могу легко добавить другой тип токена, не нарушая механику кода или не переписывая шаблон. Я также использовал этот трюк в Как мне получить неизвестное количество захватов из шаблона? :

use v5.10;

my $string = 'for element in hydrogen   helium  "carbon  14"    $(some stuff "here")   FILE';

# The types of things you can match, going from most specific
# to least specific. Now you only need to describe what each
# individual thing looks like. Each pattern is responsible for
# the capture group $1, which is the thing we'll save.
my @patterns = (
    qr/ ( \$\( .+? \) ) /x,
    qr/ ( " .+? " )     /x,
    qr/ ( \S+ )         /x,
    );

my @tokens;
# The magic is global matching in scalar context,
# using /g. The \G anchor starts matching at the
# last position you matched in the prior match of
# the same string (that's in pos()). Normally that
# position is reset when a match fails, but /c
# prevents that so you can try other patterns. Once
# you match a pattern, save what you matched and
# move on.
#
# The pattern here also takes care of trailing whitespace.
while( pos($string) < length($string) ) {
    foreach my $pattern ( @patterns ) {
        next unless $string =~ m/ \G $pattern \s*/gcx;
        push @tokens, $1;
        last;
        }
    }

use Data::Dumper;
say Dumper( \@tokens );

Вы можете сделать то же самое с оператором сброса ветки для каждого захвата в чередовании: $1:

use v5.10;

my $string = 'for element in hydrogen   helium  "carbon  14"    $(some stuff "here")   FILE';

my @tokens = $string =~ m/
    (?|
        (?: ( \$ \( .+? \) ) ) |
        (?: ( " .+? "      ) ) |
        (?: ( \S+          ) )
    )
    /gx;

use Data::Dumper;
say Dumper( \@tokens );

Это немного сложнее, чем ответ zdim , но он гораздо более гибкий. Скажем, например, вы решили, что вам не нужны кавычки около "carbon 14". Это очень легко исправить, потому что структура регулярного выражения не меняется. Вы изменяете только тот подшаблон, который работает с этим токеном:

    (?|
        (?:   ( \$ \( .+? \) )   ) |
        (?: " ( .+?          ) " ) |
        (?:   ( \S+          )   )
    )

Эта дополнительная гибкость может не понадобиться. Обычно я сталкиваюсь с дополнительными странными ситуациями при выполнении таких задач, поэтому я начинаю с гибкого решения. Это не имеет большого значения после того, как вы сделали это пару раз.


Что касается вашей ошибки, вы получили:

Lookbehind дольше 255 не реализовано в регулярном выражении.

До версии 5.30 вы не могли иметь просмотр назад с переменной шириной . Теперь это экспериментальная функция, но шаблон должен знать заранее, что он не go более 255 символов. В вашем шаблоне (?<=\"[^\"]*\"), а * - ноль или больше. Это значение может быть больше 255, поэтому это недопустимый шаблон.

regexr.com использует PCRE, который раньше обозначал «Perl совместимый», но они разошлись настолько, что некоторые вещи, которые выглядят как они может работать нормально на других языках, но не работать в Perl. Обычно это не проблема, но ретроспективный просмотр - одно из отличий.

...