- Подписка на печатную версию:
- Подписка на электронную версию:
- Подшивки старых номеров журнала (печатные версии)
LXF70:Perl. Сортируем наш код
Материал из Linuxformat.
Perl Марко Фиоретти |
---|
Perl Михаил Смирнов |
---|
Содержание |
Perl. Сортируем наш код
Часть 2. Напуганы непостижимыми операторами Perl и регулярными выражениями? Еще больше напуганы словом «непостижимые»? Марк Фиоретти (Marco Fioretti) может всё объяснить!
Перед тем, как отправиться в путешествие по королевству Perl, вы должны запомнить три типа переменных: скаляр, массив и хэш. Отмеченные символом $ скаляры содержат один кусочек информации, например строку. В нашем втором руководстве мы узнаем, как работать с более сложными величинами, массивами и хэшами, а затем покажем вам настоящую черную магию Perl — регулярные выражения.
Сортировка и перечисление
Вы помните, как массивы упорядочивают скаляры? Оператор списка Perl действует похожим образом. Это безымянная последовательность скаляров в круглых скобках. Хорошими вариантами использования оператора списка является присвоение значений двум или более переменным в одной инструкции или обмен значениями переменных тем или иным способом.
($X,$Y,$Z) = ($Y,$Z,$X); # Circular shift ($Name,$Surname,$Phone) = ('John', 'Smith',5556791); ($DARTH_VADER,@JEDI) = ('Anakin Skywalker', 'Yoda', 'Obi-Wan', 'Mace Windu');
Подсчёт числа элементов массива
Как можно узнать, сколько элементов содержится в массиве или хэше? Очень просто, присвоить их скалярной переменной! Поскольку она может содержать только одно число, Perl поместит туда число элементов массива. Тот же способ работает с хэшами. Функция keys возвращает массив, содержащий только ключи хэша, так что его размер можно узнать следующим образом:
$HOW_MANY_JEDI = @JEDI; $HOW_MANY_ITEMS_INTO_AN_HASH = keys %SOME_HASH;
Две первые строчки говорят сами за себя. В третьей строке список из левой части присваивания состоит из скаляра ($DARTH_VADER) и именованного массива (@JEDI). Все знают, что случится после такого присвоения: на Тёмную Сторону Силы юный Энакин перейдёт в одиночестве. И поскольку вторым элементом списка является массив @JEDI, все остальные рыцари из правой части выражения попадут в него, в том же самом порядке.
Давайте теперь посмотрим на функцию splice(), которая используется для удаления, добавления или замены элементов массива. Для начала определим несколько планет:
@STAR_WARS_PLANETS = ('Naboo', 'Tatooine', 'Geonosis');
Используем splice() для того, чтобы добавить Coruscant и Alderaan сразу после Tatooine:
splice (@STAR_WARS_PLANETS, 2,0, ('Coruscant', 'Alderaan'));
Первым аргументом этой функции является имя массива, STAR_WARS_PLANETS. Затем идёт индекс элемента (считая с нуля!), с которого мы хотим начать склейку, в нашем случае это Tatooine. Третьим параметром является число элементов для удаления. Сейчас мы не хотим ничего удалять, нам надо только добавить несколько планет, поэтому в качестве третьего параметра передается «0». Последний, необязательный элемент — это список, который будет добавлен в позицию, номер которой вы только что указали. Если этот аргумента не указан, то splice() не добавит в массив ничего.
Если вы хотите упорядочить содержимое массива в алфавитном порядке, то вам понадобится функция sort(). По умолчанию она рассматривает содержимое переданного массива как строки, даже если там содержатся числа. Например, если вы введёте в командной строке следующее выражение:
perl -e "@A_LIST = ('Dominions', 180, 3, '10, Downing St.','Admiralty'); \ print join( \"\n\", sort @A_LIST), \"\n\";"
то получите отсортированный по алфавиту список:
10, Downing St. 180 3 Admiralty Dominions
Однако функция sort() может руководствоваться и другими критериями:
@SORTED_LIST = sort AS_I_WANT @UNORDERED_LIST;
AS_I_WANT — это функция, принимающая два скаляра в качестве аргументов и возвращающая −1, 0 или 1 в зависимости от того, какой параметр оказался меньше согласно вашему критерию. Мы рассмотрим такие функции в последующих выпусках.
Последнее замечание про массивы. Есть одна вещь, без которой не может жить ни один Perl-хакер, хотя она вовсе не выглядит как массив. Я говорю о нашем возлюбленном STDIN, стандартном потоке ввода любой «правильной» Unix-программы. К счастью, им очень просто пользоваться. Я упомянул здесь STDIN потому, что он может быть превращен в массив одним мановением руки:
@LINES = <STDIN>;
Вот так, одной единственной инструкцией вы внесли каждую строчку ввода в отдельный элемент массива @LINES. Удобно, не правда ли?
Чистый хэш
В первой части нашего руководства я вам рассказывал про хэши и их концепцию индексирования доступа к одним скалярам другими скалярами (ключами). Как вы знаете, хэш состоит из ключей, связанных со своими значениями. Если у вас есть хэш, гораздо проще работать с ним с помощью ключей, а не их значений. В конце концов, если бы вы просто хотели собрать все эти значения вместе в определённом порядке, вы бы взяли обычный массив, не правда ли?
В отличие от массивов, хэши в Perl никак не отсортированы — ни в порядке возрастания ключей, ни в порядке добавления элементов. Это значит, что к элементу хэша нельзя обращаться по его порядковому номеру, можно только по ключу. Это верно, даже если вы собрались удалить какой-то элемент хэша и его ключ. Для этого существует функция delete(), которая принимает в качестве параметра ключ элемента, подлежащего забвению. Пользоваться этой функцией необходимо, так как если вы, например, просто присвоите пустое значение соответствующему элементу хэша, то он не будет удалён, хотя значение этого элемента и станет равным null.
Первой вещью, которую приходится делать с элементом хэша, является выяснение того, присутствует ли он в хэше, и соответствует ли его ключу какое-нибудь значение. Для этих целей в Perl существуют две функции, названные (вы уже догадались?) exists() и define(). Используют их следующим образом:
if exists ($STAR_WARS_ACTORS{'Leia'}) { # do something...}; if defined ($STAR_WARS_ACTORS{'Leia'}) { # do something else...};
Первая команда выполнится, только если в хэше есть ключ 'Leia' вне зависимости от того, какое значение присвоено этому ключу. Второе выражение идёт еще дальше, оно истинно только в том случае, если в хэше есть ключ 'Leia' и если ему явно было присвоено какое-то значение.
Регулярные выражения
Perl был создан для обработки большого количества текстовой информации, поэтому он имеет самый богатый набор инструментов работы с регулярными выражениями. Регулярное выражение — это описание структуры текстовой строки, созданное с помощью особого синтаксиса. Оно состоит из обычного текста и метасимволов, описывающих свойства строки. Основные метасимволы описаны в Шпаргалке по регулярным выражениям.
Красота и универсальность этого механизма проявляется в том, что вы можете как описать любую строку в деталях, так и дать указание скрипту найти в тексте нужные элементы и автоматически отредактировать их. Лучше всего это объяснять на примерах:
/Jedi/ /\bJedi\b/ /^Jedi$/ /Jedi/i /Jedi|Sith/
Perl, вероятно, содержит гораздо больше регулярных выражений, чем все другие языки программирования. Для того, чтобы своими глазами увидеть, насколько мощными и неудобными они могут быть, посмотрите на самое длинное из всех, что я видел — http://www.ex-parrot.com/~pdw/Mail-RFC822-Address.html. Говорят, что оно проверяет правильность адреса e-mail, но я не могу это проверить. Если хотите научиться создавать такие же, прочитайте книгу Джефри Фридла «Регулярные выражения» (Jeffrey Friedl, «Mastering Regular expressions», O'Reilly, 2002).
Первое выражение будет истинно, если в анализируемом тексте содержится подстрока Jedi, возможно в виде части более длинного слова. Если вам нужно найти только отдельное слово «Jedi», нужно обрамить его символами границы слова (\b), как это было сделано во втором выражении. Третье выражение еще более строгое, оно требует чтобы Jedi с находилось одновременно в начале (^) и в конце ($) строки, то есть, другими словами, чтобы «Jedi» было единственным словом. Регулярные выражения Perl зависят от регистра символов, так что если вам не хочется писать что-то вроде JeDi|jedi|Jedi, то используйте модификатор i, как это сделано в четвёртом примере. Последнее выражение будет истинным, если в тексте встретится хотя бы одно вхождение Jedi или Sith.
Всё это ценные знания, так как после того, как Perl смог распознать текст, описываемый регулярным выражением, он может выполнить любые действия, какие вы захотите, или сможет изменить найденную строку, руководствуясь вашими инструкциями. Вот соответствующие выражения:
if ($STRING =~ m/some regex here/) {do something} $STRING =~ s/some regex here/some other text pattern/;
Собственно регулярные выражения обрамлены косыми чертами (/). Строки связаны с ними при помощи оператора =~. Когда перед косыми чертами присутствует буква m, это значит: «Соответствует ли строка регулярному выражению?». Когда вместо m наличествует s, и добавлен какой-то текст между второй и третьей косыми чертами, это значит «Взять $STRING, и заменить в ней то, что соответствует регулярному выражению, на строку, взятую из последней пары черточек».
Регулярные выражения могут содержать скаляры и сохраняют результаты своей работы в специальных переменных. Так что это очень гибкий инструмент.
$JEDI = 'Anakin'; s/Master $JEDI/the future Darth Vader/g; s/Master (Obi-Wan|Yoda)/the Jedi Knight $1/;
A+ # Одна или несколько букв A \+ # один знак "+" \++ # Один или несколько знаков "+"
Тут мы начинаем с того, что заменяем все фразу «Master Anakin» на «the future Darth Vader» (именно «Anakin» потому, что при обработке регулярного выражения переменная $JEDI будет заменена её текущим значением). Модификатор g обозначает, что замена должна быть глобальной, без него изменение произошло бы только с самым первым вхождением искомой фразы.
Второе регулярное выражение демонстрирует еще одну интересную особенность. Оно находит все случаи, в которых перед именами «Obi-Wan» или «Yoda» стоит слово «Master». Поскольку имена взяты в круглые скобки, после удачного поиска они не забываются, а сохраняются в специальной переменной $1, так что одно и то же регулярное выражение может заменить 'Master Yoda' на 'the Jedi Knight Yoda', а 'Master Obi-Wan' на 'the Jedi Knight Obi-Wan'. Если нужно запомнить более одного элемента регулярного выражения, используются переменные $2, $3 и так далее до $9.
Регулярные выражения: шпаргалка
Здесь приведён список основных метасимволов, используемых в регулярных выражениях Perl. Скопируйте его и держите поближе к клавиатуре, он действительно помогает сэкономить время.
- . — Любой символ за исключением перевода строки
- ^ — Начало строки
- $ — Конец строки
- * — Ноль или более предыдущих символов
- + — Один или более предыдущий символ
- ? — Ноль или один предыдущий символ
- \n — Перевод строки
- \t — Табуляция
- \w — Числа и алфавитные символы, вне зависимости от регистра
- \W — Все символы, кроме букв или цифр
- \d — Старые добрые цифры: 0, 1 и так до 9.
- \D — Всё, кроме цифр.
- \s — Пробельные символы: пробел, табуляция, перевод строки.
- \S — Любой не пробельный символ.
- \b — Граница слова.
- | — Выбор из двух вариантов (например A|B).
- [] — Квадратные скобки определяют диапазон символов.
- () — Круглые скобки сохраняют соответствующую им подстроку.
Примечания: Когда вам необходимо вставить в регулярное выражение один из этих символов в его буквальном смысле, например знак «+», нужно поставить перед ним обратную косую черту (\).