Использование регулярных выражений Perl для подстановки и транслитерации.
В одной из прошлых публикаций мы начали разговор о регулярных выражениях языка Perl, а точнее, о том, как с помощью этих выражений составляются шаблоны для поиска различных данных. Однако зачастую одним только поиском задача не исчерпывается. Как правило, возникает необходимость совершить некое действие над найденным фрагментом данных. В этой статье мы рассмотрим варианты решения нескольких задач такого типа. Предполагается, что читатель внимательно ознакомился с
предыдущей публикацией
, что он знаком с азами записи и исполнения Perl-скриптов, протестирует приведенные здесь примеры решения задач, поэкспериментирует с заменой метасимволов и так далее.
Меняем формат даты
Проблема:
в тексте встречаются даты, записанные в формате
ГГГГ-ММ-ДД
, например,
2004-09-30
. Требуется изменить формат записи на более привычный русскоязычному пользователю
ДД.ММ.ГГГГ
. То есть дата должна выглядеть так:
30.09.2004
.
Синтаксис операций замены по шаблону или, иными словами,
операций подстановки
, в языке Perl выглядит следующим образом:
s/
что меняем
/
на что меняем
/
.
Какие же фрагменты текста мы должны найти, чтобы изменить? Эти фрагменты выглядят следующим образом:
четыре цифры - две цифры - две цифры
.
Как их записать в формате регулярных выражений Perl? Например, так:
(\d{4})-(\d{2})-(\d{2})
.
Обратите внимание на круглые скобки. Они не только разбивают регулярное выражение на некие фрагменты, но и заставляют Perl присваивать значения этих фрагментов переменным вида
$n
, где
n
- положительное число. В нашем случае переменной
$1
будет присвоено значение
\d{4}
, то есть, год, переменной
$2
-
\d{2}
(месяц), переменной
$3
-
\d{2}
(день). Зная об этом, мы сможем записать вторую часть подстановки, то есть
на что меняем
.
Примечание:
Метасимвол
\d
соответствует цифрам, метасимвол-повторитель
{n}
обозначает количество повторений предыдущего символа.
Выглядеть эта часть будет так:
$3\.$2\.$1
.
Целиком выражение подстановки запишется следующим образом:
s/
(\d{4})-(\d{2})-(\d{2})
/
$3\.$2\.$1
/
.
Как это выражение можно использовать на практике? Предлагаю скопировать и выполнить в браузере следующий небольшой скрипт:
#!/usr/bin/perl
print "Content-Type: text/html\n\n";
# 1-я строка
print "<html>";
# 2
$old_date = "2004-09-30";
# 3
$text_in = "Допустим, в некотором тексте встречается дата
<b>$old_date</b>.";
# 4
print "$text_in<br>";
# 5
($text_out = $text_in) =~
s/(\d{4})-(\d{2})-(\d{2})/$3\.$2\.$1/;
# 6
$text_out =~
s/\D+/В результате применения поиска и замены по шаблону
формат даты изменился: <b>/;
# 7
print "$text_out</b><br>";
# 8
print "<hr>Обратите внимание,
что первоначальный текст не изменился:<br>
<i>$text_in</i><br>
Это и понятно, ведь в шестой строке скрипта мы обрабатывали
не саму переменную <b>\$text_in</b>, а только ее копию.";
# 9
Примечание:
Обратный слеш
\
заставляет воспринимать следующую за ним точку именно как точку, а не как метасимвол подстановки любого символа.
Что происходит при исполнении этого скрипта? В четвертой строке создается определенный текст (переменная
$text_in
), содержащий дату в "неправильном" формате. В шестой строке создается переменная
$text_out
, в которую копируется значение переменной
$text_in
, а затем применяется составленное нами выражение подстановки. Таким образом, в новую переменную записывается тот же текст, но дата в нем меняет свой формат. В седьмой строке мы слегка изменяем текст "на выходе". Для этого опять-таки применяется операция подстановки: с помощью метасимвола
\D+
мы находим все нечисловые символы в тексте, от его начала до первой встречающейся цифры, и заменяем их на другой текст. Кстати, попробуйте удалить метасимвол-повторитель
+
и взгляните, как изменится результат работы скрипта.
Препарируем путь к файлу
Проблема:
известен полный путь к файлу, например,
c:/temp/somefile.txt
. Необходимо выделить в нем все составляющие части (имя диска, каталог, имя файла и его расширение), а затем сменить имя диска:
j
:/temp/somefile.txt
.
Эта задача довольно проста. В общем случае путь к файлу выглядит следующим образом:
латинская буква, соответствующая имени диска, и двоеточие после нее,
имена каталогов, в которых расположен файл, ограниченные символом слеша
/
с обеих сторон,
имя файла,
расширение файла.
Запишем эти составные части в синтаксисе регулярных выражений:
([a-z]:)(.*\/)(.*)(\..*)
. Здесь:
[a-z]:
- обозначает одну любую букву латинского алфавита и двоеточие после нее,
.*\/
- обозначает любое количество символов, включая последний слеш в пути к файлу,
.*
- обозначает любое количество символов,
\..*
- обозначает любое количество символов от последней точки в пути к файлу.
Примечание:
Операнд связывания
=~
применяется при поиске и замене по шаблону
Теперь нам достаточно проверить имеющееся значение переменной на соответствие данному шаблону. Для этого необязательно использовать подстановку, ограничимся простой операцией поиска по шаблону. Причем, как мы помним, значения всех фрагментов регулярного выражения, помещенных в круглые скобки, Perl будет автоматически присваивать переменным $1, $2 и так далее. Отсюда выводим простенькую функцию:
$file_path_and_name = "c:/temp/somefile.txt";
# Проверяем соответствие значения переменной
# составленному нами шаблону.
# в случае успеха переменным
$n
#
присваиваются некоторые значения:
if ($file_path_and_name
=~ /([a-z]:)(.*\/)(.*)(\..*)/
) {
# Если условие выполнено,
# выводим на экран необходимые значения:
print "Файл расположен на диске <b>$1</b><br>
Файл расположен в каталоге <b>$1$2</b><br>
Имя файла: <b>$3</b><br>
Расширение файла: <b>$4</b><br>";
# Делаем подстановку для изменения имени диска:
($new_disc = $file_path_and_name) =~ s/[a-z]/
j
/;
print "<hrgt;В результате подстановки файл
\"лег\" на другой диск: <b>$new_disc</b>";
}
# Если значение переменной не соответствует шаблону,
будет выведено предупреждение:
else {
print "
Внимание!
Значение переменной не соответствует шаблону пути к файлу!";
}
Обратите внимание на сделанную нами подстановку. В результате этой подстановки первая латинская буква, встреченная в пути файла, будет заменена на
j
. Здесь важно понимать: для того чтобы скрипт отработал нормально, данная подстановка должна быть включена в тело оператора
if
. Зачем? Для дополнительной подстраховки. Вдруг в переменной по каким-то причинам первой стоит не латинская буква, а знак препинания или буква из другого алфавита? Например:
я
:/temp/somefile.txt
. В этом случае подстановка найдет первый символ, соответствующий условию
[a-z]
(в нашем примере это
t
) и заменит его на букву
j
:
я
:/
j
emp/somefile.txt
. Если же мы включим операцию подстановки в тело оператора
if
, то любое несоответствие шаблону приведет к выводу на экран предупреждающего сообщения, описанного в теле оператора
else
, а неправильных подстановок не произойдет.
Простая транслитерация
Проблема:
некоторые браузеры и почтовые клиенты неверно отображают отдельные спецсимволы типа короткого и длинного тире, типографских кавычек "елочкой", буквы
ё
и т.д. Необходимо заменить все подобные символы, встречающиеся в тексте, на ближайшие аналоги (простые кавычки, дефис, букву
е
и т.д.).
Для подобной операции можно применить так называемую
транслитерацию
. Синтаксис таков:
=~ tr/
какие символы заменить
/
на какие символы заменить
/
.
Основное преимущество транслитерации перед подстановкой заключается в более высокой скорости работы, поскольку нет необходимости в компиляции сложных регулярных выражений, как в случае подстановки. Однако за скорость приходится платить некоторыми ограничениями. В нашем конкретном случае важно понимать три вещи:
В обоих списках транслитерации можно использовать одиночные символы или диапазоны (перечни) вроде
[a-z]
,
[а-я]
,
[0-9]
и т.д. Например,
tr/[A-Z]/[a-z]/
обозначает замену букв верхнего регистра на соответствующие буквы нижнего.
Порядок следования символов и диапазонов в списках замены должен совпадать. Скажем, если длинное тире в первом списке стоит на пятой позиции, то во втором списке дефис так же должен стоять на пятой позиции.
В отдельных случаях можно использовать в списках замены непосредственно одиночные символы, например, тот же дефис. Но для пущей корректности правильно использовать коды, например, метасимволы вида
\x
DD
, где
DD
- шестнадцатеричный код символа (см.
предыдущую публикацию
на тему регулярных выражений).
Для демонстрации одного из решений описанной выше задачи предлагаем воспользоваться такой функцией:
Здесь в списки транслитерации подставлены следующие шестнадцатеричные коды:
xAB
- открывающие типографские кавычки,
xBB
- закрывающие типографские кавычки,
x97
- длинное тире,
xB8
- буква ё,
x22
- обычные кавычки,
x2D
- дефис,
xE5
- кириллическая буква е,
Таков список задач на сегодня. Еще раз напомню, что ни сами задачи, ни их решения никоим образом не претендуют на высокое звание образцов для подражания. Они лишь призваны наглядно продемонстрировать некоторые основные принципы использования регулярных выражений в программировании на языке Perl. Ждем ваших вопросов и собственных вариантов решений в комментариях к этой статье.