Как я могу избежать метасимволов, когда я интерполирую переменную в операторе сопоставления Perl? - PullRequest
12 голосов
/ 04 января 2010

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

foo
quux
bar

В моем коде у меня есть другой массив:

foo
baz
quux

Допустим, мы перебираем файл, вызывая каждый элемент $word и внутренний список, с которым мы проверяем, @arr.

if( grep {$_ =~ m/^$word$/i} @arr)

Это работает правильно, но в несколько возможном случае, когда у нас есть тестовый случай fo. в файле, . работает как оператор подстановочного знака в регулярном выражении, а fo. затем соответствует foo что не приемлемо.

Это, конечно, потому что Perl интерполирует переменную в регулярное выражение.

Вопрос:

Как заставить Perl буквально использовать переменную?

Ответы [ 5 ]

33 голосов
/ 04 января 2010

Используйте \Q...\E для экранирования специальных символов непосредственно в строке perl после интерполяции значения переменной:

if( grep {$_ =~ m/^\Q$word\E$/i} @arr)
17 голосов
/ 04 января 2010

От ответа perlfaq6 на Как мне сопоставить регулярное выражение в переменной? :


Нам не нужно жестко кодировать шаблоны в операторе сопоставления (или во всем, что работает с регулярными выражениями). Мы можем поместить шаблон в переменную для дальнейшего использования.

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

chomp( my $regex = <STDIN> );

if( $string =~ m/$regex/ ) { ... }

Любые специальные символы регулярного выражения в $ regex все еще являются специальными, и шаблон все еще должен быть действительным, иначе Perl будет жаловаться. Например, в этом шаблоне есть непарные скобки.

my $regex = "Unmatched ( paren";

"Two parens to bind them all" =~ m/$regex/;

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

Unmatched ( in regex; marked by <-- HERE in m/Unmatched ( <-- HERE  paren/ at script line 3.

Вы можете обойти это несколькими способами в зависимости от нашей ситуации. Во-первых, если вы не хотите, чтобы какие-либо символы в строке были специальными, вы можете экранировать их с помощью quotemeta, прежде чем использовать строку.

chomp( my $regex = <STDIN> );
$regex = quotemeta( $regex );

if( $string =~ m/$regex/ ) { ... }

Вы также можете сделать это непосредственно в операторе сопоставления, используя последовательности \ Q и \ E. \ Q сообщает Perl, где начинать экранирование специальных символов, а \ E указывает, где остановиться (подробнее см. Perlop).

chomp( my $regex = <STDIN> );

if( $string =~ m/\Q$regex\E/ ) { ... }

В качестве альтернативы, вы можете использовать qr //, оператор кавычек регулярного выражения (подробнее см. Perlop). Он цитирует и, возможно, компилирует шаблон, и вы можете применять к шаблону флаги регулярных выражений.

chomp( my $input = <STDIN> );

my $regex = qr/$input/is;

$string =~ m/$regex/  # same as m/$input/is;

Возможно, вы захотите отследить любые ошибки, обернув вокруг него блок eval.

chomp( my $input = <STDIN> );

eval {
    if( $string =~ m/\Q$input\E/ ) { ... }
    };
warn $@ if $@;

Или ...

my $regex = eval { qr/$input/is };
if( defined $regex ) {
    $string =~ m/$regex/;
    }
else {
    warn $@;
    }
12 голосов
/ 04 января 2010

Правильный ответ - не используйте регулярные выражения. Я не говорю, что регулярные выражения - это плохо, но использование их для (что равно) простой проверки на равенство излишне.

Используйте: grep { lc($_) eq lc($word) } @arr и будьте счастливы.

5 голосов
/ 04 января 2010

QuoteMeta

Возвращает значение EXPR со всеми не "словесными" символами с обратной косой чертой.

http://perldoc.perl.org/functions/quotemeta.html

2 голосов
/ 04 января 2010

Я не думаю, что вы хотите регулярное выражение в этом случае, так как вы не соответствует шаблону. Вы ищете буквальную последовательность символов, которые вы уже знаете. Создайте хеш со значениями для сопоставления и используйте его для фильтрации @arr:

 open my $fh, '<', $filename or die "...";
 my %hash = map { chomp; lc($_), 1 } <$fh>;

 foreach my $item ( @arr ) 
      {
      next unless exists $hash{ lc($item) };
      print "I matched [$item]\n";
      }
...