Perl-переключатель / регистр завершается неудачно с литеральной строкой регулярного выражения, содержащей группу без захвата '?' - PullRequest
4 голосов
/ 14 апреля 2019

У меня есть текстовые файлы, содержащие строки, такие как:

2/17/2018 400000098627 =2,000.0 $2.0994 $4,387.75
3/7/2018 1)0000006043 2,000.0 $2.0731 $4,332.78
3/26/2018 4 )0000034242 2,000.0 $2.1729 $4,541.36
4/17/2018 2)0000008516 2,000.0 $2.219 $4,637.71

Я сопоставляю их с /^\s*(\S+)\s+(?:[0-9|\)| ]+)+\s+([0-9|.|,]+)\s+\$/ Но у меня также есть некоторые файлы со строками в совершенно другом формате, которые я сопоставляю с другим регулярным выражением.Когда я открываю файл, я определяю, какой формат и назначаю $pat = '<regex-string>'; в блоке switch / case:

$pat = '/^\s*(\S+)\s+(?:[0-9|\)| ]+)+\s+([0-9|.|,]+)\s+\$/'

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

syntax error at ./report-dates-amounts line 28, near "}continue "

Если я удаляю символ ? или замену ? на \?, экранированныйили сначала назначьте $q = '?', а затем замените ? на $q внутри " строкового назначения (т. е. $pat = "/^\s*(\S+)\s+($q:[0-9|\)| ]+)+\s+([0-9|.|,]+)\s+\$/";), который скрипт скомпилирует и запустит.Если я назначу строку регулярного выражения за пределами блока switch/case, это также работает нормально.Perl v5.26.1.

Мой код также не содержит }continue, что, как сообщается в сбое компиляции, вероятно, является своего рода преобразованием кода switch/case с помощью Switch.pm в нечтородной компилятор захлебывается.Это какая-то ошибка в Switch.pm?Он не работает, даже когда я использую given/when точно таким же образом.

#!/usr/local/bin/perl

use Switch;

# Edited for demo
switch($format)
{
    # Format A eg:
    #     2/17/2018 400000098627 =2,000.0 $2.0994 $4,387.75
    #     3/7/2018 1)0000006043 2,000.0 $2.0731 $4,332.78
    #     3/26/2018 4 )0000034242 2,000.0 $2.1729 $4,541.36
    #     4/17/2018 2)0000008516 2,000.0 $2.219 $4,637.71
    #
    case /^(?:april|snow)$/i
    { # This is where the ? character breaks compilation:
        $pat = '^\s*(\S+)\s+(?:[0-9|\)| ]+)+\s+\D?(\S+)\s+\$';

      # WORKS:
      # $pat = '^\s*(\S+)\s+(' .$q. ':[0-9|\)| ]+)+\s+\D' .$q. '(\S+)\s+\$';
    }

    # Format B
    case /^(?:umberto|petro)$/i
    {
        $pat = '^(\S+)\s+.*Think 1\s+(\S+)\s+';
    }
}

Ответы [ 2 ]

4 голосов
/ 14 апреля 2019

Не используйте Switch. Как упомянуто @choroba в комментариях, Switch использует фильтр исходного кода, который приводит к таинственным и трудно отлаживаемым ошибкам, как вы констатировали.

В самой документации модуля написано:

В общем, используйте вместо данного / когда. Он был введен в Perl 5.10.0. Perl 5.10.0 был выпущен в 2007 году.

Однако, given/when не обязательно является хорошим вариантом, поскольку он экспериментален и, вероятно, изменится в будущем (кажется, что эта функция была почти удалена из Perl v5.28; так что вы определенно не хотите не хочу начинать использовать его сейчас, если вы можете избежать этого). Хорошей альтернативой является использование for:

for ($format) {
    if (/^(?:april|snow)$/i) {
       ...
    } 
    elsif (/^(?:umberto|petro)$/i) {
       ...
    }
}

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

sub pattern_from_format {
    my $format = shift;

    if ($format =~ /^(?:april|snow)$/i) {
       return qr/^\s*(\S+)\s+(?:[0-9|\)| ]+)+\s+\D?(\S+)\s+\$/;
    } 
    elsif ($format =~ /^(?:umberto|petro)$/i) {
        return qr/^(\S+)\s+.*Think 1\s+(\S+)\s+/;
    }
    # Some error handling here maybe
 }

Если по какой-то причине вы все еще хотите использовать Switch: используйте m/.../ вместо /.../.

Понятия не имею, почему происходит эта ошибка, однако в документации 1028 * написано:

Также наличие регулярных выражений, указанных с помощью raw? ...? Разделители могут вызывать загадочные ошибки. Обходной путь должен использовать m? ...? вместо этого.

Что я сначала неправильно прочитал, и поэтому попытался использовать m/../ вместо /../, что решило проблему.

2 голосов
/ 15 апреля 2019

Другой вариант, вместо цепочки if / elsif, заключается в циклическом переборе хэша, который отображает ваши регулярные выражения на значения, которые должны быть присвоены $pat:

#!/usr/local/bin/perl

my %switch = (
  '^(?:april|snow)$'    => '^\s*(\S+)\s+(?:[0-9|\)| ]+)+\s+\D?(\S+)\s+\$',
  '^(?:umberto|petro)$' => '^(\S+)\s+.*Think 1\s+(\S+)\s+',
);

for my $re (keys %switch) {
  if ($format =~ /$re/i) {
    $pat = $switch{$re};
    last;
  }
}

Дляв более общем случае (т. е. если вы делаете больше, чем просто присваиваете строку скаляру), вы можете использовать ту же общую технику, но использовать coderefs в качестве значений вашего хэша, что позволяет ему выполнять произвольный subоснованный на совпадении.

Этот подход может охватывать довольно широкий диапазон функциональных возможностей, обычно связанных с конструкциями switch / case, но учтите, что, поскольку условия извлекаются из ключей хеша,они будут оцениваться в случайном порядке.Если у вас есть данные, которые могут соответствовать более чем одному условию, вам нужно принять дополнительные меры предосторожности для их обработки, такие как наличие параллельного массива с условиями в правильном порядке или использование взамен Tie :: IxHash обычного хеша.

...