Операция замены в Perl

()

Если вы использовали Perl больше 15 минут, вы без сомнения видели (и возможно использовали) очень полезную операцию замены, которая обычно выглядит как s/old/new/. Давайте поговорим о вещах, которые вы возможно уже знаете, и о некоторых вещах, которые вы возможно еще не знаете, используемых при операциях замены.

Наиболее важная вещь, о которой следует сказать - это то, что операция замены по умолчанию действует для переменной "$_":

$_ = "hello";
s/ell/ipp/; # $_ теперь "hippo"

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

$_ = "hello";
s/e.*l/ipp/; # $_ теперь "hippo"

Здесь ".*" заменяет любое количество любых символов. Если использовать выражение ".*?", то будет изменена только часть текста до первого вхождения символа (в данном случае символа "l"). Например:

$_ = "hello";
s/e.*?l/ipp/; # $_ теперь "hipplo"

Кроме того, мы можем использовать вместо переменной "$_" другие, для этого надо использовать конструкцию "=~". В отличие от операции сравнения, мы должны определить переменную типа lvalue (как имя переменной), а не rvalue (результат выражения).

my $text = "hello";
$text =~ s/ell/ipp/; # $text теперь "hippo"

Я иногда узнаю, что люди путаются в возвращаемых значениях операции замены. Итак, если в переменной $text сейчас содержится новое значение, будет ли оно тем же, что и в более широком контексте?

my $text = "hello";
my $result = ($text =~ s/ell/ipp/);

Ответ нет. Несмотря на то, что операция замены действительно меняет переменную $text, она возвращает значение true/false, а не результат замены. В данном случае $result равен true. Это свойство используется в операция с условиями:

if (s/foo/bar/) { # если foo было найдено, то оно стало bar...
... выполняем какие-гибудь действия ...
} else {
... мы не нашли foo, и $_ не изменилось ...
}

Замена выполняется в первом возможном месте:

$_ = "hello";
s/l/p/; # $_ теперь "heplo";
s/l/p/; # $_ теперь "heppo";

Повторение операции для всех неперекрывающихся совпадений произойдет, если мы добавим суффикс "g":

$_ = "hillo";
s/l/p/g; # $_ is now "hippo";

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

$_ = "aaa,bbb,ccc,ddd,eee,fff,ggg";
s/,.*?,/,XXX,/g; # заменяем все поля на XXX

Когда мы проверим результат, мы увидим:

aaa,XXX,ccc,XXX,eee,XXX,ggg

Почему же не произошло полной замены? В первом совпадении мы получили ,bbb, и заменили его на ,XXX,. Но после этого ,ccc, не будет считаться как совпадение из-за того что является перекрывающимся (то есть является концом предыдущей замены)!

Мы можем исправить это если добавим предпросмотр замыкающей запятой:

$_ = "aaa,bbb,ccc,ddd,eee,fff,ggg";
s/,.*?(?=,)/,XXX/g; # заменяем все поля на XXX

Сейчас замыкающая запятая не является частью строки поиска, поэтому она не пропускается при поиске. Заметьте, что я также изменил строку для замены, теперь она не добавляет запятую в конце. Теперь результат будет лучше:

aaa,XXX,XXX,XXX,XXX,XXX,ggg

Все еще не заменено начало. Это понятно, так как мы требуем запятую перед буквами. И не заменен конец, так как мы требуем замыкающую запятую, даже если она не является частью совпадения. Мы можем решить обе эти проблемы немного исправив текст для замены:

$_ = "aaa,bbb,ccc,ddd,eee,fff,ggg";
s/(^|(?<=,)).*?((?=,)|$)/XXX/g; # заменяем все поля на XXX

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

s/
(
^ # начало строки
| # или
(?<=,) # запятая слева
)
.*? # сколько угодно знаков
(
(?=,) # запятая справа
| # или
$ # конец строки
) /XXX/gx;

Так гораздо приятнее читать.

Кроме того, мы можем использовать альтернативные разделители для левой и правой части выражения замены:

$_ = "hello";
s%ell%ipp%; # $_ теперь "hippo"

Правила немного сложнее, но это работает точно как хотел Larry Wall. Если разделитель не является специальным символом, который начинает пару, то мы используем его второй раз к обоим частям и для выражения замены и для завершения операции замены, как это показано в примере ниже.

Однако, если мы используем начальный символ парного набора (круглые скобки, фигурные скобки, квадратные скобки или знаки "больше" и "меньше"), мы завершаем выражением соответствующим знаком. Затем мы приступаем к следующему разделителю, используя те же правила. Например, все эти строки делают одно и тоже:

s/ell/ipp/;
s%ell%ipp%;
s;ell;ipp;; # не делайте так!
s#ell#ipp#; # одна из моих любимых
s[ell]#ipp#; [] для строки поиска, # для замены
s[ell][ipp]; [] и для строки поиска и для замены
s<ell><ipp>; <> и для строки поиска и для замен
s{ell}(ipp); {} для строки поиска, () для замены

Не важно, что закрывающий разделитель используется и для строки поиска и для замены, мы можем добавить этот знак если используем обратную косую черту (backslash):

$_ = "hello";
s/ell/i\/n/; # $_ теперь "hi/no";
s/\/no/res/; # $_ теперь "hires";

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

$_ = "hello";
s%ell%i/n%; # $_ is now "hi/no";
s%/no%res%; # $_ is now "hires";

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

$_ = "aaa,bbb,ccc,ddd,eee,fff,ggg";
s((^|(?<=,)).*?((?=,)|$))(XXX)g; # Заменяем все поля на XXX

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

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

$replacement = "ipp";
$_ = "hello";
s/ell/$replacement/; # $_ теперь "hippo"

Левая часть выражения тоже упрощается подобной заменой:

$pattern = "ell";
$replacement = "ipp";
$_ = "hello";
s/$pattern/$replacement/; # $_ теперь "hippo"

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

$pattern = qr/ell/;
$replacement = "ipp";
$_ = "hello";
s/$pattern/$replacement/; # $_ теперь "hippo"

Операция qr создает объект Regexp, который подставляется в выражение и имеет наибольшую скорость выполнения.

Надеюсь вам понравилась эта статья об операцие замены, однако она не может служить заменой справочных страниц, таких как perlre, perlretut, perlrequick, и perlreref. Смотрите их для более полной информации.

Автор оригинального текст (на английском языке) - Randal L. Schwartz

Оригинальный текст

Ключевые слова: perl.

Комментарии:

Новый комментарий



© 2006-2016 Вадим Калинников aka MooSE