Изучаем Perl, часть 2
Автор: © Ben Okopnik
|
"И тут я понял, что существовала большая экологическая ниша между языком C и UNIX-shell. С был хорош для манипулирования сложными комплексными проектами - назовем это "манипулексией". А shell-ы идеально подходили для для быстрого решения повседневных проблем - я называю это 'whipupitude' (от англ. whip up - быстро решать проблему). Но была обширная пустая область, где ни C, ни shell не были столь хороши, и вот туда-то я и нацелил Perl." Обзор В первой части мы говорили о некоторых основах и общих вещах в Perl -
написание скрипта, заголовке скрипта, стиле - и вдобавок о некоторых
специальных вещах, таких как скаляры, хэши, операторы и правила кавычек.
В этом месяце мы рассмотрим присущие Perl инструменты, которые делают его таким легким
в использовании из командной строки, а также их эквиваленты в скриптах. Еще мы
немного глубже рассмотрим механизм действия кавычек, сделаем небольшой шаг в раccмотрении
регулярных выражений - одного из наиболее мощных инструментов в Perl, который
заслуживает для описания отдельной книги. [1]
Механизм действия кавычек Большинство из вас будут на "ты" со стандартным механизмом кавычек в Unix: одинарные и двойные кавычки, о которых я упомянул в предыдущей статье, имеют такую же функциональность в Perl, как и в shell. Хотя иногда экранирование всех метасимволов в строке может быть немного затруднительным. Представьте попытку напечатать строку похожую на эту: ``/// Don't say "shan't," "can't," or "won't." ///'' Вот не было печали! Что мы можем сделать с такой неприятностью? Ну, мы могли бы всунуть целую связку "экранов" ("\"), но это будет болезненно - как в случае с ССИДДФР (Синдром склонности использования дубины для филигранной работы): print '\`\`\/\/\/ Don\'t... Бр-р-р.. Очевидно - это не есть хороший ответ. Для случаев, похожих на этот, Perl предлагает альтернативный механизм кавычек: q// # Одиночные кавычки
Учтите также, что разделитем может быть не '/', а любой другой символ. Теперь наша работа становится немного легче: print q-``/// Don't say "shan't," "can't," or "won't." ///''-; Простенько, да? Между прочим, такую штучку вы можете вставить только внутрь скрипта;
механизм интерпретации команд shell воспринял бы все это, особенно нагромождение
обратных кавычек и слэшей, как ужасный беспорядок, если бы вы попытались сделать так из командной строки.
Вызов Perl "Услышь мои мольбы, о Perl, Великий и Мудрейший!" Ладно, не берите в голову; я думаю, такие реплики были стандартом при работе с Perl3, но теперь они обесценились... :) Наиболее часто используемый ключ при вызове Perl, если вы вызываете его из командной строки - это '-e'; он говорит Perl, что нужно выполнить команды расположенные сразу после него. Фактически, '-e' должен быть последним переключателем, используемым в командной строке, потому что все, что после него, рассматривается как часть скрипта! perl -we 'print "Боги начали этот трэд в конференции, чтобы родился Web.\n"' "-w" - "предупреждающий" ключ, о котором я упомянул в прошлый раз. Он говорит вам о всех не-фатальных ошибках в вашем коде, включая информацию о переменных, которые вы определили, но не использовали (неоценимо при поиске ошибок, допущенных при вводе имен переменных), и еще о многих и многих других вещах. Настоятельно рекомендую всегда - да, всегда использовать "-w", неважно, в командной строке или в скрипте. "-n" это "не-печатающий циклический" переключатель, который заставляет Perl обработать весь ввод строчка за строчкой (строки при этом не выводятся) - несколько напоминает "awk". Если хотите напечатать данную строку, вы должны определить условие для этого: perl -wne 'print if /holiday/' schedule.txt Perl "пройдет" через весь "schedule.txt" и напечатает все строки, которые содержат слово "holiday". Будьте осторожны: так вы можете впасть в депрессию, узнав о том, как мало праздников у вас намечается. "-p" вызов "цикла печатания", действует точно как "-n" за исключением того, что печатает каждую строку, которую обрабатывает Perl. Это очень полезно для "sed"-подобных операций, например модификация файла и вывод его (мы обсудим 's///', оператор замены, только вскользь): perl -wpe 's/holiday/Party time!/' schedule.txt Эта команда выполнит замену первого вхождения слова 'holiday' на 'Party time!' в каждой строке файла schedule.txt (см. "perldoc perlre" для рассмотрения модификаторов, используемых с 's///', таких как 'g'- глобальный.) "-i" - этот переключатель хорошо работает в комбинации с любым из вышеупомянутых, в зависимости от желаемого действия; это позволяет выполнять "замещающее" редактирование, т.е. делать изменения в определенном файле (при желании можно сохранить копию, содержащую первоначальные данные), а не печатать его на экране. Учтите, что мы не можем просто добавить "i" в строку "wpe": требуется необязательный параметр - раcширение, которое будет добавлено к файлу, содержащему резервную копию обрабатываемого файла. perl -i~ -wpe 's/holiday/Party time!/' schedule.txt Эта строка поризведет файл файл "schedule.txt", содержащий модифицированный
текст, и "schedule.txt~", содержащий первоначальные данные. "-i" без всякого расширения
перепишет оригинальный файл; это гораздо более удобно, чем создание измененного
файла и переименование его, но при этом будьте уверены в правильности вашего кода
или вы попрощаетесь с вашими первоначальными данными!
Регулярные выражения или "Опять по моей клавиатуре кошка гуляла?" Одним из мощнейших инструментальных средств, достпных в Perl, является регулярное выражение - это способ выразить соответствие почти любой символьной последовательности. Здесь (по необходимости) я обьясню только самые основы; если вам покажется, что вы нуждаетесь в большем количестве информации, копайте в "perlre" manpage, которая поставляется вместе с Perl. Это займет вас на некоторое время. :) RE (regular expressions = регулярные выражения) используются для сопоставления с шаблоном, обычно, в операторах "m//" (соответствие) и "s///" (замена). Учтит, что разделители в них, точно также как и в правилах кавычек, не ограничены только символом '/'; фактически, первая 'm' в операторе сопоставления требуется только если используется разделитель не определенный по умолчанию. Другими словами, просто "//" - достаточно. Приведем несколько метасимволов, используемых в RE. Учтите, что их гораздо больше, но перечисленных хватит для начала: . соответствует любому символу, кроме '/n' -конец строки
Скажем, у нас есть файл со списком имен: Anne Bonney
и мы хотим заменить первое имя словом 'Captain'. Очевидно, мы должны пройтись по файлу с "циклом печати" и сделать замену соответствие критерию: s/^.+ /Captain /; Символ ('^') соответствует началу строки, ".+" говорит "любой символ, поторенный 1 или больше раз", и пробел соответствует пробелу. Как только мы найдем, что ищем, мы заменим это на слово 'Captain' со следующим за ним пробелом - так как исходная строка, которую мы заменяем, содержит пробел, мы должны вернуть его. Скажем, что мы также знали, что где-то в файле есть пара имен , содержащих апострофы (Francois L'Ollonais), и мы бы хотели пропустить их, а также еще все те, что содержит 'не-буквенные' символы. Давайте немного расширим наше регулярное выражение: s/^[A-Z][a-z]* /Captain /; Мы использовали спецификатор "класс символов" - "[]", первому соответствует символ между 'A' и 'Z' - учтите только один символ, очень важное ограничение! - следующему соответствует один символ от 'a' до 'z', и астериск, который говорит опять "ноль и или большее число предшествующих символов". Оп-ля, подождите! А как насчет "KuoHsing"? На букве 'H' произойдет ошибка сопоставления, так как символы верхнего регистра не включены в определенный диапазон. OK, мы модифицируем рег. выражение: s/^\w* /Captain /; '\w' - это "буквенный символ" - еще раз, он соответствует только одному символу, который может быть: 'A-Z', 'a-z', и '_'. Это предпочтительнее чем [A-Za-z_], потому что используется значение $LOCALE (системное значение), чтобы определить какие символы могут являтся частями слов - это важно для языков, отличных от английского. К тому же, '\w' напечатать легче, чем '[A-Za-z_]'. Давайте попытаемся сделать нечто немножечко другое: Что если мы захотели найти имена, но теперь, вместо того, чтобы заменить их, мы пожелали поменять их местами с фамилиями, разделив запятыми, и перед фамилиями вставить слово 'Captain'? С регулярными выражениями в нашей команде - это не проблема: s/^(\w*) (\w*)$/Captain $2, $1/; Обратите внимание на круглые скобки и переменные "$1" и "$2": скобки "захватывают" заключенную в них часть рег. выражения, к которой мы теперь можем обратиться через переменные (содержимое первых скобок $1, вторых - в $2, и т.д.) Итак, здесь, приведенное выше регулярное выражение по-русски: Стартуем с начала строки, (начинаем "наполнять" переменную $1) ищем соответствие любому "буквенному символу", встречающегося ноль или больше раз (конец) и следующий затем пробел, (начинаем "наполнять" переменную $2) следующий затем любой "буквенный символ", повторенный ноль или более раз (конец) пока не достигнем конца строки. Возвращаем слово 'Captain', затем пробел, за которым следует значение переменной $2, запятая, пробел и значение переменной $1. Я бы сказал, что регулярные выражения очень компактный способ для выражения всего этого. В подобных случаях становится довольно очевидно, что Larry Wall - профессиональный лингвист. :) Это только простые примеры подхода к построению регулярных выражений.
Я должен признаться в небольшом обмане: разбор имен, вероятно, одна из широчайших
задач, и я мог бы плести этот пример так долго, как хотел бы.
Рассмотрение возможностей включения "John deJongh", "Jan
M.
van de Geijn", "Kathleen O'Hara-Mears", "Siu Tim Au Yeung", "Nang-Soa-Anee
Bongoj Niratpattanasai", и "Mjölby J. de Wærn" (не забудьте об использовании
LOCALE соответствий, правильно?), поле довольно широко и очень неоднородно.
(Мисс Niratpattanasai, посмотрев на что-нибудь типа "John Smith".
вероятно согласилась бы. :)
Есть важный фактор в работе механизма регулярных выражений: по умолчанию делаются "жадные соответствия". Другими словами, возьмем фразу похожую на Acciones son amores, no besos ni apachurrones и регулярное выражение типа /A.*es/ получим следующее соответствие: Acciones son amores, no besos ni apachurrones
Хммм. Все от первой 'A'(следующими за ней нулем или больше символами) до последней 'es'. Как найти первое же совпадение, а? Чтобы противодействовать жадности, Perl предоставляет модификатор "щедрости" к кванторам типа '*', '+', и '?': /A.*?es/ Acciones son amores, no besos ni apachurrones
Ну вот. Гораздо лучше. На будущее запомните: если вы разбиваете строку для
проверки на соответствие серии регулярных выражений, и последние "куски" пусты,
вы вероятно получили проблему "жадности".
Заданный по умолчанию буфер/переменная Некоторые из вас, особенно те, кто в прошлом программировал, вероятно были озадачены некоторыми конструкцями в приведенном выше коде, например print if /holiday/; "Напечатать что? если что? Где та переменная, которую мы проверяем на соответствие? Разве это не должно выглядеть как-то так 'if $x == /holiday/'?" Я рад, что вы задали этот вопрос. :) Perl использует интересную концепцию, найденную также в нескольких других языках, буфера по умолчанию - также известному нам как переменная по умолчанию и default pattern space. Не удивительно, что это используется в конструкциях выполнения цикла, когда мы используем синтаксис "-n/-p" при вызове Perl, это переменная использована для "содержания" текущей строки - так же как при замене и поиска соответствия и в некоторых других местах. Переменная '$_' - является переменной по умолчанию для всего вышеперечисленного; когда переменная не определена там, где вы ожидаете, "виновником" обычно является '$_'. Фактически применение '$_' довольно трудно объяснить - она появляется в таком количстве мест, что алгоритм выглядит невозможным - но само использование чудесно легко и интуитивно понятно, как только у вас появляется идея. Рассмотрим следующее: perl -wne 'if ( $_ =~ /Henry/ ) { print $_; } pirates Если в строке в файле "pirates" есть соответствие "Henry", то она будет напечатана. Прекрасно; а теперь, давайте сыграем в любителя "Perl Golf" - это конкурс среди хакеров Perl, чтобы посмотреть сколько (ключевых) штрихов можно удалить из этого куска кода и оставить ту же его функциональность. Так как мы уже знаем, что Perl читает каждую строку в '$_', мы просто удалим все явные объявления этой переменной: perl -wne 'if ( /Henry/ ) { print; } pirates Perl "знает" что мы ищем соответствие в переменной по умолчанию, и он "знает", что оператор "print" применяется к ней же. Теперь применим маленькую Perl идиому: perl -wne 'print if /Henry/' pirates Здорово, правда? Perl фактически позволяет вам писать код с условием,
следующим за действием; это почти также, как вы бы сказали это по-английски.
О, и мы отсекли точку с запятой в конце, потому что она нам не нужна:
это разделитель операторов, а никакого следующего оператора нет
<grin> Для тех кто играет один дома, попытайтесь разобраться perl -ne'/Henry/&&print' pirates Не нужно быть слишком крутым чтобы понять; оператор '&&'
в Perl работает также как, он работает в shell. Perl Golf - это забава для игры,
но будьте осторожны: легко написать код, который будет работать, но требующий
поломать голову, чтоб понять его. Не Делайте Так. Может быть завтра я буду
сопровождать ваш код... точно также, как вам, быть может, придется сопровождать мой.
В первом примере обратите внимание на "связывающий оператор" '=~', который проверяет на соответствие переменную. Это то , что вы бы использовали, если бы искали соответствие не с "$_", а с другой переменной. Оператор "не-соответствия" '!~', который возвращает истину, если совпадения не было найдено (инверсный '=~'.) Учтите также, что доступные операторы для простых выражений, типа
упомянутых выше, включают не только "if", но также и "unless", "while", "until",
и "for". О них и многом другом в Части 3...
Ben Okopnik
[1]. Фактически "Мастерство регулярных выражений" by Jeffrey E. Friedl может быть справочником по предмету. Она включает некоторые замечательные примеры, и буквально учит читателя "думать по-регулярному". Ссылки: Relevant Perl man pages (available on any pro-Perl-y configured system): perl - overview
perlfaq - Perl FAQ
"perldoc", "perldoc -q" and "perldoc -f"
|
Copyright © 2001, Ben Okopnik. Copying license http://www.linuxgazette.com/copying.html Published in Issue 64 of Linux Gazette, March 2001 |
Вернуться на главную страницу |