Собственно, реализации UTF-8 в Perl было две. Первая появилась в Perl 5.6, но была достаточно сырой и неудобной. Начиная с Perl 5.8 механизм работы с уникодом был радикально пересмотрен, и модули на CPAN запестрили забавными проверками на версию интерпретатора. Все, что написано ниже, относится именно к этой, второй реализации.
За и против
Если Вы до сих пор не задумывались о кодировках, спокойно разрабатывали одноязычные приложения и собираетесь продолжать в том же духе — уникод вам почти наверняка не нужен. Данные в однобайтных кодировках в любом случае более компактны, обрабатываются они быстрее, а иметь с ними дело легко и приятно.
Вам наверняка понадобится UTF-8, если Вы не знаете наперед, в каком виде придет приложению очередная порция данных, или разрабатываете международный проект. Ведь даже если Ваш сайт на английском языке, на нем вполне может зарегистрироваться какой-нибудь немец с умляутами в ФИО, или даже житель поднебесной. Простейший способ не задумываться о том, что окажется после этого в БД (ну и о том, как вы будете показывать имя китайца в любимой latin-1) — работать в кодировке, поддерживающей множество языков.
И еще один случай, когда без знакомства с Perl UTF не обойтись — интеграция с работающими в этом формате сторонними компонентами. Например, библиотека
XML::LibXML
возвращает результаты разбора XML-файлов именно в этом формате.The Perl Way
Вероятно, майнтейнеры рассуждали примерно так: мы хранили в переменных цепочки байт, теперь нам надо научиться хранить там символы. Длина символа в UTF-8 непостоянна и может быть больше одного байта. Если регулярки и функции для работы со строками (типа
length
, substr
) начнут себя вести по-другому, нам спасибо не скажут. Значит, нужно сделать строки двух типов — для работы по-старой схеме, с байтами, и для работы по новой схеме, с символами. Как это сделать? А давайте введем для скаляров скрытый флаг. Если флаг установлен, строка воспринимается как состоящая из логических символов (назовем это Perl Internal Format), если нет — из байтов.Если взять две одинаковые unicode-переменные и у одной из них просто опустить флаг, переменные будут обрабатываться перлом по-разному (например, у них скорее всего будет разная длина). Однако, сами данные при этом не изменяются — это можно увидеть, например, если обе переменных вывести в файл, либо на экран.
Стоит упомянуть, что символы UTF-8 в терминологии Perl часто называются wide characters. Если у вас попадаются варнинги с этими словами, значит дело касается уникодных строк.
Вариантов для работы с уникодными данными в Perl несколько. Основные из них это:
- принудительное указание уникодных символов в строке — через конструкцию вида
\x{0100}
; - ручная перекодировка строки при помощи модуля
Encode
, либо функций из пакетаutf8
; - включение прагмы
use utf8
— флаг поднимается у всех констант, которые встретились в коде; - чтение из дескриптора ввода-вывода с указанием IO-Layers
:encoding
или:utf8
— все данные автоматически перекодируются во внутренний формат.
Модуль Encode
Модуль входит в поставку Perl 5.8, так что использовать его имеет смысл не только для уникода, но и для любых других преобразований кодировки. Работа с модулем не слишком сложна. Единственная проблема — научиться не путать функцию
encode
с функцией decode
:-). Интерфейс у них одинаковый, а логика наименования не настолько очевидна, как хотелось бы. Поскольку формат строк с unicode-флагом считается внутренним форматом, в него нужно декодировать данные из произвольной кодировки (в том числе и UTF-8 без флага), и наоборот, при желании перевести данные в некую внешнюю кодировку, их нужно из внутреннего формата закодировать в нее. Выглядит это примерно так:$bytes = encode(`cp1251`, $string); # перекодировали строку из внутреннего представления в cp1251
$string = decode(`cp1251`, $bytes); # и обратно
Поскольку не все символы можно без потерь гонять из одной кодировки в другую, есть еще и третий параметр, который определяет, как себя вести в случае проблем. О нем можно прочитать в документации на модуль
Encode
, там этому посвящен целый раздел.Если Вы точно уверены, что в вашей переменной находятся байты в UTF-8, можно просто поднять флаг у переменной, не производя перекодировку и проверку — при помощи
_utf8_on
. Определить наличие флага у строки (и при желании проверить валидность лежащих там данных) поможет функция is_utf8
. Ну а сбрасывается флаг, как можно догадаться, через _utf8_off
. Единственное «но» — эти функции помечены как INTERNAL, и рассчитывать на их неизменность не стоит.Начиная с Perl 5.8.1 часть функций модуля
Encode
стала доступна в неймспейсе utf8::
— это функции is_utf8
, encode
, decode
. Последние две отличаются от синонимов из модуля Encode
тем, что изменяют значение переданной переменной вместо возвращения результата, и не требуют указания кодировки (подразумевается, что работа происходит с данными UTF-8 без поднятого флага). Все эти функции встроены в интерпретатор, и писать use utf8
для доступа к ним не нужно — более того, это может привести к дополнительным эффектам (о них чуть позже).use utf8;
Прагма
use utf8
сообщает интерпретатору, что все константы и регулярные выражения, записанные в зоне ее действия и имеющие не-ASCII символы, должны трактоваться как уникодные и автоматически приводиться ко внутреннему формату. Для отмены действия прагмы, как обычно, используется конструкция no utf8
.Cуществует и противоположная по смыслу прагма
use bytes
, в зоне действия которой даже данные с флагом UTF-8 трактуются, как состоящие из байтов.PerlIO
Тема Perl IO Layers в принципе заслуживает отдельной статьи. Идея в том, что с некоторых пор старая добрая функция
open
обзавелась трехаргументным синтаксисом:open $fh, $mode, $filename
Кроме стандартных значений типа
`>`
и `<`
в $mode
можно указывать также кодировку файла. При этом загружаемые данные автоматически конвертируются во внутренний формат Perl:open $fh, "<:encoding(cp1251)", $filename
Если речь идет о файле, содержащем данные в UTF-8, код можно слегка упростить:
open $fh, "<:utf8", $filename
Безусловно, использовать данные модификаторы можно и для модификации файлов — эффект будет обратным.
Кстати, в Perl есть возможность сделать потоки ввода-вывода уникодными раз и навсегда при помощи ключа командной строки
-C
. Подробности можно посмотреть, как всегда, в perldoc.Грабли
Конечно же, они есть.
Во-первых, некоторые функции по определению работают именно с байтами, а не с символами, и строки во внутреннем представлении встают им поперек горла. К числу таких функций относятся часто используемые функции из модуля
Digest::MD5
. Так, приведенный пример отвалится с ошибкой Wide character in subroutine entry at test.pl line 3.
:use Digest::MD5 `md5_hex`;
print md5_hex("\x{400}");
Во-вторых, данные далеко не всегда приходят в том виде, в котором их ожидает увидеть программа. Наивно ожидать, например, что в обработчик HTML-формы всегда будет приходить валидный UTF-8. Результаты излишнего доверия к источникам могут быть довольно разнообразными, начиная с порчи данных, и заканчивая фатальными ошибками при попытке их перекодировать в другую кодировку (например, при формировании email`а).
И наконец, самая частая и интересная проблема возникает при попытке конкатенации двух строк, только одна из которых хранится во внутреннем перловом формате. Допустим, у нас есть такой файлик (записанный в UTF-8):
use Encode;
$a = decode(`utf8`, "Мне нравится "); # строка во внутреннем формате
$b = "на Хабре"; # последовательность из 15 байт
$c = $a.$b;
В последней строке Perl пытается привести строки к общему
$b
он воспринимает как цепочку байт, каждый байт этой строки перекодируется в UTF-8. В результате получится примерно такая каша (с поднятым, кстати, флагом):$c = "Мне нравится на ХабÑе"
Глюк достаточно хорошо виден невооруженным взглядом по специфичным для уникода кракозябрам — ни с чем не спутаешь.
Заключение
В статье остались нераскрытыми многие тонкости. Ряд полезностей из модулей
Encode
, utf8
остался за кадром. Не нашлось места для упоминания вариации внутреннего формата, чувствительной к невалидным с точки зрения UTF-8 символам. Совершенно опущены вопросы, связанные с регулярными выражениями. Если Вы хотите вникнуть в эту тему до конца, обратите внимание на мануалы:Если остались вопросы, постараюсь на них ответить.