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

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+';
    }
}
4
Matthew 14 Апр 2019 в 22:29

2 ответа

Лучший ответ

Не используйте 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/.../ вместо /.../.

Я понятия не имею, почему возникает эта ошибка, однако в документации говорится:

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

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

4
Dada 14 Апр 2019 в 20:24

Другой вариант вместо цепочки 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 в качестве значений вашего хэша, что позволяет ему выполнять произвольный {{ X0}} на основе совпадения.

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

1
Dave Sherohman 15 Апр 2019 в 07:48