Переменные регулярного выражения Perl и подстановка совпадающих шаблонов

Может ли кто-нибудь объяснить замену текста регулярным выражением, когда регулярное выражение содержится в переменной? Я пытаюсь обработать некоторый текст, на самом деле спецификации конфигурации Clearcase, и заменять текст по ходу. Правила подстановки хранятся в массиве хэшей, которые содержат регулярное выражение для сопоставления и текст для замены.

Вводимый текст выглядит примерно так:

element  /my_elem/releases/...  VERSION_STRING.020 -nocheckout

Большинство замен - это просто удаление строк, содержащих определенную текстовую строку, это отлично работает. В некоторых случаях я хочу заменить текст, но повторно использовать текст VERSION_STRING. Я пробовал использовать $ 1 в выражении подстановки, но это не сработало. $ 1 получает строку версии в совпадении, но замена $ 1 не работает при замене.

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

element  -directory  /my_elem/releases/... VERSION_STRING.020 -nocheckout
element  /my_elem/releases/.../*.[ch]  VERSION_STRING.020 -nocheckout

т.е. Один строковый ввод превратился в два вывода, и строка версии была повторно использована.

Код выглядит примерно так. Сначала регулярные выражения и замены:

my @Special_Regex = (   
                  { regex => "\\s*element\\s*\/my_elem_removed\\s*\/main\/\\d+\$",                  subs => "# Line removed" },
                  { regex => "\\s*element\\s*\/my_elem_changed\/releases\/\.\.\.\\s*\(\.\*\$\)", 
                    subs => "element  \-directory  \/my_elem\/releases\/\.\.\. \\1\nelement  \/my_elem\/releases\/\.\.\.\/\*\.\[ch\]  \\1" }

                );

Во втором регулярном выражении переменная $ 1 определена в части (. * \ $), И это работает правильно. Однако выражение subs не заменяет его.

 foreach my $line (<INFILE>)
        {
        chomp($line);
        my $test = $line;
        foreach my $hash (@Special_Regex)
        {
            my $regex = qr/$hash->{regex}/is;
            if($test =~ s/$regex/$hash->{subs}/)
                {
                print "$test\n";
                print "$line\n";
                print "$1\n";
                }
         }
}

Что мне не хватает? Заранее спасибо.


person 0xDEADBEEF    schedule 03.11.2010    source источник
comment
Не используйте ddoouubbllee slackbashed строки для регулярных выражений, а затем все это время компилируйте их. Просто сделайте хеш-значения qr// строкой напрямую. Не используйте \\1 в правой части замен! И, пожалуйста, избавьтесь от этих уродливых LTS-строк.   -  person tchrist    schedule 03.11.2010
comment
Я уверен, что кто-то захочет прочитать этот пост. А пока, пожалуйста, сделайте одолжение себе и всем, кто должен прочитать, и поищите \Q в perldoc perlreref.   -  person Sinan Ünür    schedule 03.11.2010
comment
Справедливый комментарий. Пока я экспериментировал, этот код претерпел несколько изменений - я удалил qr, чтобы контролировать, что удалось избежать, а что нет. Вы можете мне поверить, что регулярные выражения работают, за исключением замены $ 1 \ 1.   -  person 0xDEADBEEF    schedule 03.11.2010


Ответы (2)


Для выражения замены не существует компиляции. Итак, единственное, что вы можете сделать, это exec или eval с флагом e:

if($test =~ s/$regex/eval qq["$hash->{subs}"]/e ) { #...

работал у меня после изменения \\1 на \$1 в строках замены.

s/$regex/$hash->{subs}/

заменяет только совпавшую часть на буквальное значение, хранящееся в $hash->{subs} как полная замена. Чтобы подстановка работала, вы должны заставить Perl оценивать строку как строку, а это означает, что вам даже нужно добавить dquotes обратно, чтобы получить интерполяционное поведение, которое вы ищете. для (потому что они не являются частью строки.)

Но это немного неуклюже, поэтому я заменил выражения замены на подпрограммы:

my @Special_Regex 
    = ( 
        { regex => qr{\s*element\s+/my_elem_removed\s*/main/\d+$}
        , subs  => sub { '#Line removed' }
        }
    ,   { regex => qr{\s*element\s+/my_elem_changed/releases/\.\.\.\s*(.*$)}
        , subs  => sub { 
            return "element  -directory  /my_elem/releases/... $1\n"
                 . "element  /my_elem/releases/.../*.[ch]  $1"
                 ; 
          }
        }

    );

Я избавился от кучи вещей, которые не нужно убирать с помощью выражения подстановки. Поскольку вы хотите интерполировать значение $1 в заменяющую строку, подпрограмма делает это просто. И поскольку $1 будет виден до тех пор, пока не будет найдено что-то еще, это будет правильное значение, когда мы запустим этот код.

Итак, теперь замена выглядит так:

s/$regex/$hash->{subs}->()/e

Конечно, выполнение прохода $1 делает его более пуленепробиваемым, потому что вы не зависите от глобального $1:

s/$regex/$hash->{subs}->( $1 )/e

Конечно, вы бы изменили саб так:

subs => sub {
    my $c1 = shift;
    return "element  -directory  /my_elem/releases/... $c1\n"
         . "element  /my_elem/releases/.../*.[ch]  $c1"
         ; 
}

И последнее замечание: "\.\.\." не сделал то, что вы думаете. Вы только что получили '...' в регулярном выражении, которое соответствует любым трем символам.

person Axeman    schedule 03.11.2010
comment
Большое спасибо за ответ - обе версии работали хорошо, и ваш ответ очень информативный. Я выбрал вашу чуть более элегантную «вспомогательную» версию, но без параметров на случай, если регулярное выражение имеет более одной совпадающей переменной. Только один небольшой момент - разве в регулярном выражении подстановки отсутствует окончательная оценка e? s / $ regex / $ hash - ›{subs} -› () / e у меня работает. - person 0xDEADBEEF; 04.11.2010

Строка подстановки в вашем регулярном выражении оценивается только один раз, что преобразует $hash->{subs} в ее строку. Вам нужно снова оценить его, чтобы интерполировать его внутренние переменные. Вы можете добавить модификатор e в конец регулярного выражения, который сообщает Perl о необходимости выполнения подстановки через eval, который, помимо прочего, может выполнять вторую интерполяцию. Вы можете применить несколько e флагов для оценки более одного раза (если у вас есть проблема, которая в этом нуждается). Как услужливо указывает tchrist, в этом случае вам понадобится ee, поскольку первый eval просто расширяет переменную, а второй необходим для раскрытия переменных в раскрытии.

Более подробную информацию об операторе s можно найти в perlop.

person Eric Strom    schedule 03.11.2010
comment
Эрик, обратите внимание, что наличие RHS на замене be $foo - то же самое с /e и без /e, поэтому для подобных вещей всегда требуется /ee. - person tchrist; 03.11.2010