Как использовать переменную в качестве модификатора регулярного выражения в Perl?

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

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

Что-то вроде этого:

sub ask {
    my ($prompt, $validationRe, $caseSensitive) = @_;
    my $modifier = ($caseSensitive) ? "" : "i";
    my $ans;
    my $isValid;

    do {
        print $prompt;
        $ans = <>;
        chomp($ans);

        # What I want to do that doesn't work:
        # $isValid = $ans =~ /$validationRe/$modifier;

        # What I have to do:
        $isValid = ($caseSensitive) ?
            ($ans =~ /$validationRe/) :
            ($ans =~ /$validationRe/i);

    } while (!$isValid);

    return $ans;
}

Итог: есть ли способ динамически указывать модификаторы регулярного выражения?


person livefree75    schedule 20.12.2014    source источник
comment
или есть лучший способ сделать то, что я хочу сделать?   -  person livefree75    schedule 20.12.2014


Ответы (4)


Итог: есть ли способ динамически указывать модификаторы регулярного выражения?

С 1_:

"(?adlupimsx-imsx)" "(?^alupimsx)" Один или несколько встроенных модификаторов соответствия шаблону, которые необходимо включить (или выключить, если перед ними стоит "-") для оставшейся части шаблона или оставшейся части закрывающая группа шаблонов (если есть).

Это особенно полезно для динамических шаблонов, таких как те, которые считываются из файла конфигурации, берутся из аргумента или указываются где-то в таблице. Рассмотрим случай, когда некоторые шаблоны хотят быть чувствительными к регистру, а некоторые нет: нечувствительные к регистру просто должны включать «(?i)» в начале шаблона.

Что дает вам что-то вроде строк

$isValid = $ans =~ m/(?$modifier)$validationRe/;

Просто не забудьте принять соответствующие меры предосторожности при принятии пользовательского ввода таким образом.

person Slade    schedule 20.12.2014


Избавьтесь от параметра $caseSensitive, так как во многих случаях он будет бесполезен. Вместо этого пользователи этой функции могут закодировать необходимую информацию непосредственно в регулярном выражении $validationRe.

Когда вы создаете объект регулярного выражения, такой как qr/foo/, шаблон в этот момент компилируется в инструкции для механизма регулярных выражений. Если вы преобразуете объект регулярного выражения в строку, вы получите строку, которая при интерполяции обратно в регулярное выражение будет иметь точно такое же поведение, как и исходный объект регулярного выражения. Самое главное, это означает, что все флаги, предоставленные или опущенные в литерале объекта регулярного выражения, будут сохранены и не могут быть переопределены! Это сделано специально, чтобы объект регулярного выражения продолжал вести себя одинаково независимо от того, в каком контексте он используется.

Это немного сухо, так что давайте использовать пример. Вот функция match, которая пытается применить пару похожих регулярных выражений к списку строк. Какой из них будет соответствовать?

use strict;
use warnings;
use feature 'say';

# This sub takes a string to match on, a regex, and a case insensitive marker.
# The regex will be recompiled to anchor at the start and end of the string.
sub match {
    my ($str, $re, $i) = @_;
    return $str =~ /\A$re\z/i if $i;
    return $str =~ /\A$re\z/;
}

my @words = qw/foo FOO foO/;
my $real_regex = qr/foo/;
my $fake_regex = 'foo';

for my $re ($fake_regex, $real_regex) {
    for my $i (0, 1) {
        for my $word (@words) {
            my $match = 0+ match($word, $re, $i);
            my $output = qq("$word" =~ /$re/);
            $output .= "i" if $i;
            say "$output\t-->" . uc($match ? "match" : "fail");
        }
    }
}

Выход:

"foo" =~ /foo/  -->MATCH
"FOO" =~ /foo/  -->FAIL
"foO" =~ /foo/  -->FAIL
"foo" =~ /foo/i -->MATCH
"FOO" =~ /foo/i -->MATCH
"foO" =~ /foo/i -->MATCH
"foo" =~ /(?^:foo)/     -->MATCH
"FOO" =~ /(?^:foo)/     -->FAIL
"foO" =~ /(?^:foo)/     -->FAIL
"foo" =~ /(?^:foo)/i    -->MATCH
"FOO" =~ /(?^:foo)/i    -->FAIL
"foO" =~ /(?^:foo)/i    -->FAIL

Во-первых, мы должны заметить, что строковое представление объектов регулярных выражений имеет эту странную форму (?^:...). В группе без захвата (?: ... ) модификаторы шаблона внутри группы могут быть добавлены или удалены между вопросительным знаком и двоеточием, а ^ указывает набор флагов по умолчанию.

Теперь, когда мы смотрим на фальшивое регулярное выражение, которое на самом деле является просто интерполируемой строкой, мы видим, что добавление флага /i имеет ожидаемое значение. Но когда мы используем настоящий объект регулярного выражения, это ничего не меняет: внешний /i не может переопределить флаги (?^: ... ).

Вероятно, лучше всего предположить, что все регулярные выражения уже являются объектами регулярных выражений и не должны вмешиваться. Если вы загружаете шаблоны регулярных выражений из файла, вы должны потребовать, чтобы регулярные выражения использовали синтаксис (?: ... ) для применения флагов (например, (?^i:foo) как эквивалент qr/foo/i). Например. загрузка одного регулярного выражения на строку из дескриптора файла может выглядеть так:

my @regexes;
while (<$fh>) {
    chomp;
    push @regexes, qr/$_/;  # will die here on regex syntax errors
}
person amon    schedule 21.12.2014

Вам нужно использовать функцию eval. Следующий код будет работать:

sub ask {
    my ($prompt, $validationRe, $caseSensitive) = @_;
    my $modifier = ($caseSensitive) ? "" : "i";
    my $ans;
    my $isValid;

    do {
        print $prompt;
        $ans = <>;
        chomp($ans);

        # What I want to do that doesn't work:
        # $isValid = $ans =~ /$validationRe/$modifier;

        $isValid = eval "$ans =~ /$validationRe/$modifier";

        # What I have to do:
        #$isValid = ($caseSensitive) ?
        #    ($ans =~ /$validationRe/) :
        #    ($ans =~ /$validationRe/i);

    } while (!$isValid);

    return $ans;
}
person Amit Anand    schedule 20.12.2014
comment
Привет, прежде чем пометить его, может кто-нибудь объяснить, что не так с этим кодом ?? Test: $ans = "ABCDEF"; $modifier = "i"; $validationRe = "cD"; $isValid = eval "$ans =~ /$validationRe/$modifier"; print $isValid; result: 1 - person Amit Anand; 21.12.2014
comment
Этот код использует eval в опасной форме. Ужасно легко сломать такой код и даже сделать опасные вещи (посмотрите, что происходит с $validationRe = '/; print "You have been pwned\n"; exit -1; #' или $ans = 'die "This code is broken!"; #'). Поскольку вы даже интерполируете $ans, очень мало случаев, когда это вообще сработает. Вы хоть тестировали этот код? - person amon; 21.12.2014
comment
Да, я проверил этот код. вы можете увидеть образец в моем первом комментарии. Если вы попытаетесь играть без надлежащей проверки, может произойти что угодно. Пользователь искал способ прочитать модификатор из переменной, я просто предложил один способ. - person Amit Anand; 21.12.2014
comment
Этот ответ правильный. Хорошая работа @Амит. Людям нужно перестать голосовать за код из-за воображаемых и параноидальных соображений безопасности. $eval$ мощный, полезный и неспроста написанный на Perl. Это не обязанность вопроса-ответа представлять всякие издевательства и уколы. Если да, то единственный правильный ответ на все вопросы по программированию: # отключитесь от интернета, выключите компьютер, найдите новую работу, однажды вас могут забанить. - person Beracah; 13.03.2017