Поиск книг в интернет магазине Болеро

Поиск DVD в интернет магазине DVD DOM
Внимание! Money-Hunting.Ru - Интересная Online Игра с Денежными Призами.

Иллюстрированный самоучитель по Perl

Предисловие.
Часть 1. Введение в мир Perl.
Часть 2. Структура программы.
Часть 3. Типы данных.
Часть 4. Операции и выражения.
Часть 5. Операторы.
Часть 6. Операции ввода/вывода.
Часть 7. Работа с файлами.
Часть 8. Форматы.
Часть 9. Ссылки.
Часть 10. Работа со строками.
Часть 11. Подпрограммы и функции.
Часть 12. Пакеты, библиотеки, модули.
Часть 13. Объектно-ориентированное программирование в языке Perl.
Часть 14. Запуск интерпретатора и режим отладки.
Часть 15. Язык Perl и CGI-программирование.
Часть 16. Ресурсы Perl.


Глава 1

Часть1. Введение в мир Perl


Глава 1

История языка Perl

Что такое Perl? Это сокращенное название языка программирования Practical Extraction and Report Language (Практический язык извлечений и отчетов). Что подразумевается под "извлечениями" и "отчетами"? Почему практический язык? Для чего он предназначен? Какие задачи можно решать с его помощью? Эти и многие другие вопросы возникают, естественно, у любого человека, хоть немного знакомого с информатикой, когда он впервые сталкивается с новым для него языком программирования. Эта глава и задумывалась как ответ на поставленные выше вопросы, так как зная, что может, для чего предназначен язык программирования (а время универсальных языков, кажется, миновало), программист, в конечном счете, решает, а стоит ли тратить время на его изучение. Хотя здесь также встают вопросы о легкости и быстроте освоения нового языка, доступности компиляторов, существовании службы его поддержки, стоимости и т. д. Об этом также пойдет речь в этой главе, которая познакомит читателя с огромным миром Perl-программирования, и станет той отправной точкой, с которой он, мы надеемся, стремительно и без оглядки войдет в него и останется в нем навсегда.

Язык Perl родился в недрах операционной системы Unix как реакция одного талантливого программиста на ограниченную возможность стандартных средств системного администрирования в этой операционной среде. Авторы прекрасно осознают, что большинство читателей знакомы с Unix, возможно, только по названиям книг, лежащих на полках магазинов, так как традиция изучения информационных технологий в нашей стране связана больше с операционными системами семейства Microsoft Windows [Под семейством операционных систем Microsoft Windows понимаются операционные системы Windows 95/98/NT.], чем с системой UNIX, которая является базой изучения информатики в западных университетах. Поэтому для воспитанных в традициях Windows читателей мы сделаем небольшое отступление и кратко охарактеризуем процедуру администрирования UNIX, которая радикально отличается от аналогичной работы в операционной системе Windows.

Под администрированием понимается настройка операционной системы через установку значений ее параметров таким образом, чтобы она отвечала потребностям отдельного пользователя или группы пользователей. В системах семейства Windows подобная работа выполняется с помощью Реестра, представляющего собой базу данных двоичных данных, а для изменения параметров используется специальная программа regedit. В системе UNIX настройка осуществляется через специальные конфигурационные файлы, являющиеся обычными текстовыми файлами, и все изменения осуществляются выполнением команд, написанных на специальном языке оболочки (shell) и выполняемых, как правило, из командной строки. (Несколько лет назад на персональных компьютерах была широко распространена операционная система MS-DOS фирмы Microsoft, в которой для ввода команд также использовалась командная строка, поэтому читателю, работавшему в этой операционной системе, командная строка знакома.) В системе UNIX пользователь может создавать собственные команды на основе команд интерпретатора shell, сохранять их в обычных текстовых файлах и впоследствии выполнять также, как обычные стандартные команды операционной системы через командную строку. Следует отметить, что оболочка shell операционной системы UNIX является интерпретатором, в связи с чем команды пользователя имеют еще одно название — их называют сценариями или скриптами (script). Администратору операционной системы UNIX приходится писать большое количество скриптов, которые обрабатывают другие скрипты — текстовые файлы. Для этих целей обычно кроме командного языка оболочки shell используются специальные программы обработки текстовых файлов:

  • awk — программа сопоставления с образцами и генератор отчетов;
  • sed — пакетный редактор текстовых файлов.

Обе эти программы являются фильтрами, которые последовательно считывают строки входных файлов и выполняют применимые к строке действия, определенные с помощью команд этих программ. Основными действиями являются выделение цепочек символов по заданным шаблонам, замена их по определенным правилам и генерирование новых файлов.

Теперь можно перейти и к объекту нашего изучения — языку Perl, тем более что, как нам кажется, читателю уже должно быть понятно, почему он называется языком извлечений и отчетов. И начнем мы с истории его создания и разработки, которая, по существу, позволяет полнее понять его содержание.

Perl был разработан Ларри Уоллом (Larry Wall) в 1986 году, когда он являлся системным администратором одного проекта UNIX, связанного с созданием многоуровневой безопасной сети, объединявшей несколько компьютеров, разнесенных на большие расстояния. Работа была выполнена, но потребовалось создание отчетов на основе большого числа файлов с многочисленными перекрестными ссылками между ними.

Первоначально Ларри предполагал использовать для этих целей фильтр awk, но оказалось, что последний не мог управлять открытием и закрытием большого числа файлов на основе содержащейся в них же самих информации о расположении файлов. Его первой мыслью было написать специальную системную утилиту, решающую поставленную задачу, но вспомнив, что до этого ему уже пришлось написать несколько утилит для решения задач, не "берущихся" стандартными средствами UNIX, он принял кардинальное решение — разработать язык программирования, который сочетал бы в себе возможности обработки текстовых файлов (sed), генерации отчетов (awk), решения системных задач (shell) и низкоуровневое программирование, доступное на языке С. Результатом этого решения и явился язык Perl, интерпретатор для которого был написан на С.

По утверждению самого Ларри Уолла при создании языка Perl им двигала лень — не в прямом смысле, а в смысле того, что для решения стоявшей перед ним задачи следовало бы написать большое количество программ на разных языках, входящих в состав инструментальных средств UNIX, а это достаточно утомительное занятие.

Новый язык программирования сочетал в себе возможности системного администрирования и обработки файлов — две основные задачи, решаемые обычно при программировании в системе UNIX. Причем следует отметить, что язык Perl появился из практических соображений, а не из-за желания создать еще одно "красивое" средство для работы в UNIX, поэтому-то он и получил широкое распространение среди системных администраторов, когда Ларри Уолл предоставил его широкому кругу пользователей. С появлением языка Perl появилась возможность решать задачи с помощью одного инструмента, и не тратить время на изучение нескольких языков среды программирования UNIX.

Первая версия языка не содержала многих возможностей, которые можно найти в последней версии Perl, с которой читатель познакомится в нашей книге и которая идентифицируется как версия 5.005_03 и считается устойчивой. Первоначально язык включал:

  • простой поиск по строковым образцам (шаблонам) в файлах;
  • дескрипторы файлов;
  • скалярные переменные;
  • форматы.

Вся документация умещалась на 15 страницах, но Perl решал задачи быстрее, чем sed или awk, и быстро стал использоваться не только для решения задач системного администрирования.

В дальнейшем сам Ларри Уолл позаимствовал у Генри Спенсера (Henry Spencer) пакет для работы с регулярными выражениями и модифицировал его для языка Perl. Другие функциональные возможности были разработаны не только Ларри Уоллом, но и его друзьями и коллегами, и включены в состав языка. Опубликование в Internet привело к появлению сообщества единомышленников, которые не только эксплуатировали, но и развивали язык. Он и по настоящее время продолжает интенсивно развиваться за счет разработки пакетов, реализующих новые применения языка к развивающимся информационным технологиям. В табл. 1.1 представлена динамика появления новых версий языка Perl, начиная с самой первой:

Таблица 1.1. Версии языка Perl и даты их выпуска

Версия | Дата выпуска
perl 1 | Январь, 1988
реrl 2 | Июнь, 1988
реrl 3 | Октябрь, 1989
реrl 4 | Март, 1991
perl 5 | Октябрь, 1994

В настоящее время, как уже отмечалось ранее, устойчивой версией считается версия Perl 5.005_03, но уже существует версия 5.005_67. Их все можно получить с основного узла Web, поддерживающего язык Perl, по адресу http://www.perl.com/ .

Замечание
 В литературе по языку Perl принято, ссылаясь на сам язык, писать его с прописной буквой (Реrl), а строчными буквами (perl) обозначать интерпретатор языка. По образному высказыванию самого Ларри Уолла: "perl — это ничего более как всего лишь интерпретатор Perl".


Глава 1

Характерные черты Perl

Perl — это интерпретируемый язык, оптимизированный для просмотра содержимого текстовых файлов, выделения из них информации и генерирования отчетов на основе этой информации, а также просто хороший язык для выполнения многих задач системного администрирования UNIX. Он обладает большим набором преимуществ как язык сценариев общего назначения, которые проявляются через его характерные черты и возможности.

Первым в цепочке достоинств языка Perl мы назовем его интерпретируемость. Конечно, некоторые программисты, прочитав это, скажут: "Ну вот, нашли себе достоинство. Посмотрим, как быстро будет выполняться программа Perl длиной, скажем, в тысячу операторов?". Что ж, замечание существенное, если рассматривать Perl как язык создания больших информационных систем, и совершенно не выдерживающее критики, если вспомнить, для чего он предназначен — задач администрирования и обработки текстовых файлов — небольших по размерам сценариев, решающих нетрадиционные задачи, для программирования которых могло бы потребоваться взаимодействие нескольких специализированных языков. Разработка подобных решений с помощью компилируемых языков программирования потребовала бы на много больше времени, чем использование одного интерпретируемого: ведь цикл разработки программ на таком языке короче и проще, чем на компилируемом. Мы постепенно создаем программу, добавляя необходимые операторы, и сразу же получаем результаты, когда она завершена: интерпретатор perl постепенно компилирует все операторы во внутренний байт-код и программа готова к выполнению, как только в ней поставлена последняя точка (точнее точка с запятой, завершающая Последний оператор). Для небольших по объему программ — это достаточное преимущество, так как отладка занимает много времени. Да, интерпретируемая программа, естественно, будет выполняться медленнее программы, представленной в формате двоичного файла и выполняющейся без предварительной обработки интерпретатором, но если в этом возникнет необходимость, то можно решение на языке Perl использовать в качестве прототипа для компилируемого языка, например С. Суммируя все сказанное, можно заключить, что Perl позволяет легко и быстро получить требуемое решение задачи, сочетая в себе элементы компилируемых и интерпретируемых языков программирования.

Замечание
Интерпретатор perl, как, вероятно, заметил внимательный читатель, отличается от традиционных интерпретаторов тем, что программа транслируется в промежуточный байт-код, и только после этого выполняется. В традиционных интерпретаторах каждый вводимый оператор интерпретируется и сразу же выполняется, что может приводить к синтаксическим ошибкам во время выполнения. Peri-программа свободна от этого "недостатка", так как все синтаксические ошибки обнаруживаются во время трансляции в байт-код.

Вторым преимуществом использования Perl для решения соответствующих задач (мы имеем в виду сетевые возможности) является его доступность для большинства серверных платформ:

  • практически все варианты UNIX;
  • MS-DOS;
  • Windows NT;
  • Windows 95/98;
  • OS/2;
  • Macintosh.

Для всех перечисленных платформ разработаны и свободно распространяются интерпретаторы perl вместе с документацией по их установке и работе, что приятно отличает его от других программных средств. И здесь уместно сказать несколько слов об условиях использования и распространения самого Perl и разработанных на нем программ. (О том, где можно найти и получить интерпретатор perl, см. часть 16)

Одним из способов распространения свободно распространяемого программного обеспечения, а именно таков интерпретатор perl, является использование Общей открытой лицензии GNU. По условиям этой лицензии файлы исходного текста программного продукта распространяются совершенно свободно и могут быть использованы любым лицом. Однако любые версии программы, созданные путем модификации этого кода, должны реализоваться также на условиях Общей открытой лицензии GNU, т. е. следует предоставлять файлы исходных текстов нового продукта любому, кто их захочет иметь. Этого зачастую вполне достаточно, чтобы защитить интересы автора первоначального программного продукта, однако может приводить к большому количеству производных версий исходного продукта, что приводит к "отчуждению" автора исходного продукта от процесса модификации его детища. Более того, в связи с большим количеством разнообразных версий, пользователям становится трудно определить, какая версия пакета является на текущий момент окончательной, будут ли написанные им сценарии, если речь идет о perl, правильно работать с имеющейся у него версией, и т. п.

В связи с изложенными недостатками лицензии GNU, интерпретаторы языка Perl выпускаются на условиях лицензии Artistic License (Артистической лицензии), которая является некоторой вариацией лицензии GNU, и ее смысл заключается в том, что любой, кто выпускает пакет, полученный на основе Perl, должен ясно осознавать, что его пакет не является истинным пакетом Perl. Поэтому все изменения должны быть тщательно документированы и отмечены, выполнимые модули, в случае изменения, должны быть переименованы, а исходные модули должны распространяться вместе с модифицированной версией. Эффект от подобных условий заключается в том, что автор первоначального продукта всегда определяется как его владелец. При использовании Artistic License все условия Общей открытой лицензии GNU остаются в силе, т. е. она продолжает применяться.

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

  • полнотой;
  • простотой использования;
  • эффективностью.

Под полнотой Perl понимается его способность решать все возникающие в системе UNIX в связи с ее администрированием задачи. И это действительно так! Ведь язык Perl, как отмечалось выше, вобрал в себя все наилучшие возможности стандартных средств администрирования UNIX, перечисленных в табл. 1.2.

Таблица 1.2. Стандартные средства администрирования UNIX

Язык программирования | Характеристика
awk | Язык выделения по образцам информации из текстовых файлов
C | Компилируемый язык общего назначения для решения задач низкого уровня
shell | Основной командный язык запуска программ и скриптов, написанных на других языках программирования
sed | Потоковый редактор обработки текстовых файлов

Эти средства продолжают использоваться, так как каждое из них является прекрасным инструментом для выполнения тех задач, для которых они предназначены, однако все то, что можно выполнить, комбинируя эти средства, можно реализовать в одной Perl-программе, изучив только один язык. Но возможности Perl не ограничиваются только задачами администрирования. Подключаемые пакеты и модули позволяют легко и быстро решать и другие задачи, для которых, возможно, пришлось бы использовать язык программирования С. Начиная с версии 5.0, язык Perl поддерживает технологию объектно-ориентированного программирования, причем пакеты и модули можно оформить в виде объектов и использовать без знания содержащегося в них кода (хотя придется изучить большое количество объектных моделей со своими свойствами и методами).

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

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

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

И здесь, в первую очередь, следует обратить внимание на простое включение в Peri-программу вызовов библиотечных процедур языка С, что позволяет использовать огромное количество кода, написанного для этого популярного языка. В поставку Perl входят утилиты, конвертирующие заголовки библиотек С в соответствующие эквиваленты языка Perl. Конвертирование осуществляется с помощью XS-интерфейса, который представляет собой простой программный интерфейс, преобразующий среду вызова функций С в среду вызова подпрограмм Perl. Последующий вызов функций С ничем не отличается от вызова подпрограмм самого Perl. Более того, программы Perl версии 5.0 легко интегрируются в приложения С и C++ через интерфейс, реализованный в наборе функций perl_call_* .

Для работы с базами данных можно самому написать соответствующее приложение на языке С, а можно воспользоваться свободно распространяемыми модулями дополнительных расширений возможностей Perl, включающих работу с многочисленными популярными системами управления базами данных: Oracle, Ingres, Informix, Interbase, Postgre, Sybase 4 и др.

Способность Perl работать с сокетами TCP/IP сделала его популярным для реализации информационных систем взаимодействия с сетевыми серверами любых типов, использующих сокеты в качестве механизма обмена информацией. Именно эта возможность в сочетании с использованием Perl для создания CGI-сценариев послужила широкому распространению языка на других многочисленных платформах.

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

(Некоторые адреса можно найти в части 16)


Глава 1

Области применения Perl

Наиболее широко Perl используется для разработки инструментов системного администрирования, однако в последнее время он получил огромную популярность в области разработки Internet-приложений: CGI-сценарии,

системы автоматической обработки электронной почты и поддержки узлов Web. В этом параграфе мы кратко охарактеризуем возможности Perl в каждой из указанных областей.

Системная поддержка UNIX

Как отмечалось ранее, именно задача соединения в одном языке программирования возможностей различных средств системного администрирования UNIX и послужила толчком к разработке и созданию языка Perl. Он и разрабатывался таким образом, чтобы оптимизировать решение именно этих задач, не прибегая к другим инструментам. На настоящий момент язык Perl является основным средством администрирования UNIX, который может выполнять работу нескольких других традиционных средств администрирования. Именно эта его универсальность и способствовала его широкому распространению среди системных администраторов и программистов UNIX, тем более, что он решает задачи обычно быстрее, чем другие аналогичные средства.

CGI-сценарии

Одной из первых, но продолжающей и по настоящее время широко применяться в Интернете технологией реализации динамических эффектов является технология CGI-сценариев, суть которой заключается в обработке информации, получаемой от пользователя, которую он вводит в поля формы страницы HTML, просматриваемой с помощью программы-обозревателя Internet. Информация из полей формы пересылается на сервер с помощью протокола HTTP либо в заголовке, либо в теле запроса и обрабатывается сценарием, который после анализа полученных данных выполняет определенные действия и формирует ответ в виде новой страницы HTML, отсылаемой обратно клиенту. Сценарий может быть написан, собственно говоря, на любом языке программирования, имеющем доступ к так называемым переменным среды, но сценарии Perl получили наибольшее распространение из-за легкости создания и оптимизационных возможностей языка Perl при обработке текстовых файлов. В Internet можно найти буквально тысячи примеров динамического CGI-программирования на Perl.

Его большая популярность для реализации подобных задач на UNIX-серверах Internet привела к тому, что разработчики серверов Internet, работающих в других операционных системах, стали включать возможность подключения сценариев Perl в свои системы. В настоящее время их можно использовать и на сервере Internet Information Server фирмы Microsoft для операционных систем семейства Windows, и на серверах Apache, NCSA и Netscape для операционной системы UNIX.

Обработка почты

Другая популярная область применения Perl — автоматическая обработка электронной почты Internet. Сценарии Perl можно использовать для фильтрации почты на основе адреса или содержимого, автоматического создания списков рассылки и для решения многих других задач. Одной из наиболее популярных программ для работы с электронной почтой является программа Majordomo, полностью реализованная средствами Perl.

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

Поддержка узлов Web

Узел Web — это ничто иное, как структурированное хранилище страниц HTML, которые являются обычными текстовыми файлами в определенном специальном формате, понимаемом программами просмотра их содержимого. Perl оптимизирован для обработки большого количества текстовых файлов, — поэтому его использование для анализа и автоматического изменения содержимого узла Web само собой вытекает из тех задач, для решения которых он специально и создавался. Его, например, можно использовать для решения задачи проверки правильности перекрестных ссылок на страницах узла Web, как, впрочем, и для проверки правильности ссылок на другие узлы (правда, здесь придется воспользоваться его сетевыми возможностями работы с сокетами).

Его возможности записи и чтения в/из сокетов позволяют использовать сценарии Perl для взаимодействия с другими узлами и получения информации на основе протокола HTTP. Следует отметить, что существуют даже серверы, написанные на Perl. Как упоминалось ранее, именно эти возможности Perl можно использовать для удаления со страниц HTML узла Web ссылок на несуществующие другие узлы.

Perl может работать и с протоколом FTP. Это позволяет автоматизировать получение файлов с других узлов, а в сочетании с его возможностями обработки текстовых файлов позволяет создавать сложные информационные системы.

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


Глава 1

Вопросы для самоконтроля

  1. Назовите полное наименование языка Perl.
  2. Что послужило толчком для разработки и создания Perl?
  3. Каково назначение Peri-программы?
  4. В чем заключаются преимущества и недостатки интерпретируемых языков?
  5. Перечислите основные достоинства языка Perl.
  6. Перечислите области применения Perl.


Глава 1

Часть 10. Работа со строками


Глава 1

Регулярные выражения

Язык, созданный первоначально с главной целью — облегчить обработку большого количества отчетов, просто обязан располагать развитыми средствами для работы с текстом. Напомним, что в среде UNIX, из которой вышел язык Perl, средства для обработки текстовых строк имеются в различных утилитах: sed, awk, grep, cut. Командный интерпретатор shell, также обладающий некоторыми средствами для обработки строк, позволяет организовать совместную работу этих утилит, передавая выход одной программы на вход другой через механизм, называемый конвейером. Такой подход требует написания достаточно изощренных скриптов на языке shell в сочетании с обращением к внутренним командам утилит обработки текста sed или awk. Язык Perl, являясь средством создания программ-сценариев, в то же время один обладает всеми возможностями перечисленных утилит и даже их превосходит.

Типичная задача, возникающая при обработке текстового файла, заключается в том, чтобы найти в нем фрагмент, удовлетворяющий заданным условиям, и выполнить над ним некоторую операцию: удалить, заменить на другой фрагмент, извлечь для дальнейшего использования и т. д. Условия поиска можно достаточно просто выразить словами. Например: найти строку, содержащую слово Perl. Или: найти все фрагменты, находящиеся в конце строки и содержащие две цифры, за которыми следует произвольное количество прописных букв. Для формализованной записи подобных условий используются регулярные выражения, позволяющие описать образец или шаблон поиска при помощи специальных правил. Манипуляции с регулярными выражениями осуществляются при помощи соответствующих операций, которые мы также рассмотрим в этой главе.

Регулярное выражение, по сути, представляет собой набор правил для описания текстовых строк. Сами правила записываются в виде последовательности обычных символов и метасимволов, которая затем в качестве образца используется в операциях поиска и замены текста. Метасимволы — это символы, имеющие в регулярном выражении специальное значение. Пользователи DOS/Windows хорошо знают метасимвол *, используемый для порождения имен файлов и обозначающий любую допустимую последовательность. Регулярные выражения применяются многими программами UNIX, в том числе интерпретатором shell. Каждая из них использует свое множество метасимволов. В большинстве случаев они совпадают.

Метасимволы

В языке Perl к Метасимволам относятся следующие символы:

"\", ".", "^", "$", "|", "[", "(", ")", "*", "+", "?", "}".

Различные метасимволы выполняют в регулярном выражении различные функции, в частности, используются для обозначения одиночного символа или их группы, обозначают привязку к определенному месту строки, число возможных повторений отдельных элементов, возможность выбора из нескольких вариантов и т. д.

Регулярное выражение, подобно арифметическому выражению, строится с соблюдением определенных правил. В нем можно выделить операнды (элементы) и операции.

Простейшее регулярное выражение состоит из одного обычного символа. Обычный символ в регулярном выражении представляет самого себя. Соответственно, последовательность обычных символов представляет саму себя и не нуждается в дополнительной интерпретации. Для использования в операциях в качестве образца регулярное выражение заключается между двумя одинаковыми символами-ограничителями. Часто в качестве ограничителя используется символ "/" (косая черта). Например, образцу /Peri/ будут соответствовать все строки, содержащие слово Perl.

Если, в регулярном выражении какой-либо метасимвол требуется использовать в буквальном, а не специальном значении, его нужно экранировать или маскировать при помощи другого метасимвола — "\". Например, образцу /\\\*/ соответствует фрагмент текста \*. Здесь первый метасимвол "\" экранирует второй метасимвол "\", а третий метасимвол "\" экранирует метасимвол "*".

Метасимвол "." представляет любой одиночный символ, кроме символа новой строки. Так, образцу /./ будет соответствовать любая непустая строка. Если в операциях сопоставления с образцом установлен флаг s, то метасимволу "." соответствует также и символ новой строки.

Метасимвол "[" используется в конструкции [...] для представления любого одиночного символа из числа заключенных в скобки, т. е. он представляет класс символов. Два символа, соединенные знаком минус, задают диапазон значений, например, [A-za-z] задает все прописные и строчные буквы английского алфавита. Если первым символом в скобках является символ " ^ ", вся конструкция обозначает любой символ, не перечисленный в скобках. Например, [ ^ о-9] обозначает все нецифровые символы. Ниже мы рассмотрим и другие способы представления классов символов.

Метасимволы " ^ " и "$" используются для задания привязки к определенному месту строки. Метасимвол " ^ " в качестве первого символа регулярного выражения обозначает начало строки. Метасимвол "$" в качестве последнего символа регулярного выражения обозначает конец строки. Например, следующим образцам соответствуют:

/ ^ $/ — пустая строка (начало и конец, между которыми пусто); / ^ рег1/ — слово Perl в начале строки; /Per 1$/ —слово Perl в конце строки.

Метасимвол "|" можно рассматривать как символ операции, задающей выбор из нескольких вариантов (подобно логической операции ИЛИ). Например, образцу /а|Ь|с/ соответствует фрагмент текста, содержащий любой из символов а, Ь, с, Если вариантами выбора являются одиночные символы, как в данном примере, то лучше использовать конструкцию, определяющую класс символов, в данном случае [abc]. Но в отличие от конструкции [...] операция "|" применима и тогда, когда вариантами выбора являются последовательности символов. Например, образцу /Word|Excel!windows/ соответствует фрагмент^текста^содержащий любое из слов Word, Excel, Windows.

Следующая группа метасимволов служит в качестве коэффициентов или множителей, определяющих количество возможных повторений отдельных атомарных элементов регулярного выражения.

  • r* — нуль и более повторений
  • r+— одно и более повторений
  • r? — нуль или одно повторение
  • r{n} — ровно п повторений
  • r{n}, — n и более повторений
  • r{n,m} — минимум n, максимум m повторений r

Атомарные элементы, или атомы — это простейшие элементы, из которых строится регулярное выражение. Это не обязательно одиночный символ.

Образец Соответствие

/•*/ любая строка;

/• + / любая непустая строка;

/ [ о-9 ] {з} / любая последовательность из трех цифр;

/\ [ +/ последовательность, состоящая из любого числа символов [.

В первых двух примерах атомом является метасимвол ".". В третьем образце в качестве атома выступает конструкция [0-9], определяющая класс цифровых символов. В четвертом образце атом — это пара символов "\[", включающая метасимвол "\", отменяющий специальное значение следующего за ним метасимвола "[". Полный список атомов мы приведем после изучения всех необходимых синтаксических конструкций.

Алгоритм, применяемый в операциях поиска и замены (см. ниже) для обработки регулярных выражений, содержащих множители, является "жадным": он пытается найти для образца, снабженного множителем, максимальный сопоставимый фрагмент текста. Рассмотрим, например, что происходит при поиске в строке

"Скроен колпак не по-колпаковски, надо колпак переколпаковать" фрагмента, удовлетворяющего образцу /. * колпак/.

Алгоритм найдет максимальный фрагмент, удовлетворяющий выражению . * (вся строка без завершающего символа новой строки), затем начнет двигаться назад, отбрасывая в найденном фрагменте по одному символу, до тех пор, пока не будет достигнуто соответствие с образцом. Найденный фрагмент будет иметь вид "Скроен колпак не по-колпаковски, надо колпак переколпак".

Можно заставить алгоритм работать иначе, снабдив множитель "*" модификатором "?". В этом случае алгоритм из "жадного" превращается в "ленивый" и будет для образца, снабженного множителем, искать минимальный соответствующий фрагмент. "Ленивый" алгоритм для множителя "*?" начнет поиск в строке с пустого фрагмента "", добавляя к нему по одному символу из строки до тех пор, пока не достигнет соответствия с образцом. В этом случае найденный фрагмент будет иметь вид "Скроен колпак". Все сказанное справедливо и для других множителей. Например, в строке "1234567" будет найден:

  • для образца /\d*/ — максимальный фрагмент "1234567";
  • для образца /\d+/ — максимальный фрагмент "1234567";
  • для образца /\d?/ — максимальный фрагмент "1";
  • для образца /\d{2,5}/ — максимальный фрагмент "12345";
  • для образца /\d*?/ — минимальный фрагмент "";
  • для образца /\d+?/ — минимальный фрагмент "1";
  • для образца /\d??/ — минимальный фрагмент "";
  • для образца /\d{2,5}?/ — минимальный фрагмент "12".

Метапоследовательности

Символ "\", непосредственно предшествующий одному из метасимволов, отменяет специальное значение последнего. Если же "\" непосредственно предшествует обычному символу, то, напротив, такая последовательность во многих случаях приобретает специальное значение. Подобного рода последовательности будем называть метапоследователъностями. Метапоследова-тельности в регулярном выражении служат, в основном, для представления отдельных символов, их классов или определенного места в строке, дополняя и иногда дублируя/функции метасимволов. Рассмотрим существующие метапосл едовател ьности.

  • \nnrt — представляет\символ, восьмеричный код которого равен лил. Например, последовательность \120\145\162\154 представляет слово Perl (\120 — восьмеричный код буквы Р, \145 — буквы е, \1б2 — буквы г, \154- буквы!);
  • \хпп — представляет символ, шестнадцатеричный код которого равен пп. Слово Perl, например, представляется последовательностью \x50\x65\х72\хбс;
  • \сп — представляет управляющий символ, который генерируется при нажатии комбинации клавиш <Ctrl>+<N>, где N — символ, например, \со соответствует <Ctrl>+<D>;
  • \$ — символ "$"; G \@ — символ "@"; О \% — символ "%";
  • \а — представляет символ с десятичным ASCII-кодом 7 (звонок). При выводе производит звуковой сигнал;
  • \е — символ Esc, десятичный ASCII-код 27;
  • \f — символ перевода страницы, десятичный ASCII-код 12;
  • \п — символ новой строки, десятичный ASCII-код 10;
  • \г — символ "возврат каретки", десятичный ASCII-код 13;
  • \t — символ горизонтальной табуляции, десятичный ASCII-код 9;
  • \v — символ вертикальной табуляции, десятичный ASCII-код 11;
  • \з — представляет класс пробельных символов. К пробельным символам относятся: пробел, имвол табуляции, возврат каретки, символ новой строки и символ перевода страницы. То же самое, что и [\t,\r,\n,\fj;
  • \s — представляет класс непробельных символов, то же самое, что и класс [ л \t,\r,\n,\f];
  • \d — класс цифровых символов, то же, что и [0-9]; П \о — класс нецифровых символов, то же, что и [ Л о-9];
  • \w — представляет класс буквенно-цифровых символов, состоящий из букв, цифр и символа подчеркивания "_". То же самое, что и [a-zA-z_ o-9j. Обратите внимание, что в этот класс входят только буквы английского алфавита;
  • \w — представляет класс небуквенно-цифровых символов. То же, что и Pa-zA-Z_0-9];
  • \д — обозначает начало строки;
  • \z — обозначает конец строки;

Замечание

Последовательность \А эквивалентна метасимволу ^ в начале регулярного выражения, а последовательность \z— метасимволу $ в конце регулярного выражения, за исключением одного случая. Назовем строку, содержащую внутри себя символы новой строки (ASCII 10), мульти-строкой. Фактически муль-ти-строка состоит из отдельных строк, разделенных ограничителями — символами новой строки. При выводе мульти-строка отображается в виде нескольких строк. Если к мульти-строке применяется операция поиска или замены с опцией /т (см. ниже), то последовательности \А и \z обозначают соответственно начало и конец всей мульти-строки, а метасимволам ^ и $ соответствуют еще и границы внутренних строк, образующих мульти-строку.
  • \b — обозначает границы слова. Под словом понимается последовательность символов из класса \w. Граница слова определяется как точка между символами из класса \w и символами из класса \w;
  • \B — обозначает не-границы слова, т. е. класс символов [ Л \Ь];
  • \1 — обозначает, что следующий символ регулярного выражения преобразуется к нижнему регистру. Например, запись /\ip/ означает, что символ Р будет преобразован к нижнему регистру, после чего новый образец /р/ может быть использован в соответствующей операции поиска или замены;
  • \n — обозначает, что следующий символ регулярного выражения преобразуется к верхнему регистру;
  • \b.. .\Е — обозначает, что все символы в регулярном выражении между \ь и \Е преобразуются к нижнему регистру;
  • \n.. .\Е — обозначает, что все символы в регулярном выражении между \U и \Е преобразуются к верхнему регистру;
  • \Q...\E— обозначает, что все метасимволы в регулярном выражении между \Q и \Е экранируются при помощи символа "\". Например, запись /\0 Л *?+\Е/ эквивалентна записи /\ Л \*\?\+У;
  • \с — обозначает точку, в которой закончился предыдущий поиск m//g (см. описание операции поиска т//).

Атомы

Из всех метасимволов, перечисленных в начале раздела, нам осталось рассмотреть "(" и "). Эти метасимволы служат для группирования ряда элементов, входящих в состав образца, в один элемент. Например, образцу /<abc)+/ соответствует строка, состоящая из одного или более повторений последовательности abc, в то время, как образцу /abc+/ — строка, состоящая из начальных символов аb, за которыми следует один или более символов с.

Теперь мы можем перечислить "атомы", из которых строится регулярное выражение.

  • Регулярное выражение в скобках, представляющее несколько элементов, сгруппированных в один.
  • Любой обычный символ (не метасимвол).
  • Символ ".", представляющий любой одиночный символ, кроме символа новой строки.
  • Конструкция [...], представляющая класс символов, перечисленных в квадратных скобках.
  • Метапоследовательность, представляющая символ или класс символов: \&, \n, \r, \t, \f, \e, \d, \D, \w, \w, \s, \s.
  • Метапоследовательность вида \nnn, определяющая символ при помощи его восьмеричного ASCII-кода nnn
  • Метапоследовательность вида \хnn, определяющая (символ при помощи его шестнадцатеричного ASCII-кода nn .
  • Метапоследовательность вида \сn, представляющая управляющий символ ctri-n .
  • Конструкция вида \number, представляющая обратную ссылку.
  • Любая конструкция вида \character, не имеющая специального значения, а представляющая собственно символ character, например: \*, \ у , \ь.
  • Напомним, что в регулярном выражении множители *, +, ?, {n,m} применяются именно к атому, расположенному непосредственно слева.

Обратные ссылки

Ранее мы установили, что группу элементов регулярного выражения можно заключить в скобки и рассматривать как один элемент. Заключение группы элементов в скобки имеет дополнительный и весьма полезный эффект. Если в результате поиска будет найден фрагмент текста, соответствующий образцу, заключенному в скобки, то этот фрагмент сохранится в специальной переменной. Внутри регулярного выражения к нему можно будет обратиться, используя запись \number, где number—номер конструкции () в исходном регулярном выражении. Запись \number, указывающую на найденный по образцу фрагмент текста, будем называть обратной ссылкой. Можно задать любое количество конструкций вида () и ссылаться на соответствующие найденные фрагменты текста, как на \i, \2 и т. д. Например, образцу /(. + ) -\1/ соответствуют слова "ха-ха", "хи-хи", "ку-ку" и т. п., а образцу /{.)(.). ?\2\1/ — все палиндромы из четырех или пяти букв. (Палиндром — слово или предложение, которое одинаково читается слева направо и справа налево.)

Внутри образца конструкция \n (n=i,...,9) всегда обозначает обратную ссылку. Запись вида \пп также интерпретируется как обратная ссылка, но только в том случае, если в исходном выражении задано не менее, чем пп скобочных конструкций вида (). Иначе запись \пп обозначает символ с восьмеричным кодом nn.

Для ссылки на найденный фрагмент текста за пределами регулярного выражения, например, при задании замещающего текста в операции замены, вместо записи \number используется запись $пшпЬег. Например, операция замены

$str=~s/(\S+)\s+(\S+)/$2 $1/

меняет местами первые два слова в строке $str.

Область действия переменных $1, $2 и т. д, распространяется до наступления одного из следующих событий: конец текущего блока; конец строки, являющейся аргументом функции evai; следующее совпадение с образцом. Аналогичным образом определяется область действия и для следующих предопределенных переменных, используемых в операциях сопоставления с образцом.

  • $& — часть строки, найденная при последней операции сопоставления с образцом.
  • $' — часть строки, стоящая перед совпавшей частью при последней успешной операции сопоставления с образцом.
  • $' — часть строки, стоящая после совпавшей части при последней успешной операции сопоставления с образцом.

Например, в результате выполнения операции поиска

$str=~m/two/

в строке $str="one two three" образца /two/ переменным будут присвоены следующие значения:

$& - "two"; $* - "one"; $' - "three".

Эти значения будут сохраняться до наступления одного из перечисленных выше событий, и их можно использовать, например, для формирования строки с обратным порядком следования слов: $rstr=$'.$&.$". Строка $rstr будет иметь ВИД "three two one".

Следует отметить, что, если обращение к одной из переменных $&, $', $• встречается где-либо в программе, то интерпретатор peri будет вычислять и запоминать их для каждой операции сопоставления с образцом, что, в свою очередь, замедляет выполнение всей программы. Поэтому не следует использовать данные переменные без особой необходимости.

Расширенный синтаксис регулярных выражений

Выше мы использовали скобки для группирования нескольких элементов регулярного выражения в один элемент. Побочным эффектом данной операции является запоминание найденного фрагмента текста, соответствующего образцу, заключенному в скобки, в специальной переменной. Если скобки используются только для группирования элементов регулярного выражения, то найденный фрагмент текста можно не запоминать. Для этого после открывающей скобки "(" следует поместить конструкцию "?:", например, в случае задания альтернативы / (?: pattern) /.

?:pattern

Конструкция относится к классу конструкций общего вида (?...), добавляющих новые возможности для задания образцов за счет расширения синтаксиса регулярного выражения, а не за счет введения новых метасимволов или метапоследовательностей. Символ, следующий за символом "?", опреде- , ляет функцию, выполняемую данной синтаксической конструкцией. В на^ стоящее время определены около десяти расширенных конструкций регулярного выражения, большая часть которых рассмотрена в данном разделе. Оставшиеся конструкции, на наш взгляд, не являются необходимыми для первоначального знакомства с языком.

(?#text)

Текст после символа tt и до закрывающей скобки ), комментарий, игнорируется интерпретатором и используется для добавления непосредственно в регулярное выражение.

(?:pattern)

(?imsx-imsx:pattern)

Использовать скобки только для группирования элементов без создания обратных ссылок. Символы imsx-imsx между вопросительным знаком и двоеточием интерпретируются как флаги, модифицирующие функцию данного выражения (см. ниже).

(?=pattern)

Следующий фрагмент в тексте должен соответствовать образцу pattern. Обычно образец для операций поиска или замены задается при помощи регулярного выражения. Результатом операции поиска является фрагмент, соответствующий образцу, который сохраняется в специальной переменной $&. Конструкция (?=pattern) в составе регулярного выражения позволяет задать условие поиска, не включая найденный фрагмент, соответствующий образцу pattern, в результат, сохраняемый в переменной $&. Конструкция (?=pattern) в регулярном выражении задает условие, что следующий фрагмент текста должен удовлетворять образцу pattern. Обращаем внимание на слово следующий. Данная конструкция неприменима для задания условия, что предыдущий фрагмент текста должен соответствовать заданному образцу. Например, образцу /b+(?=с+)/ соответствует часть строки, состоящая из одной или более литер ь, за которыми следуют одна или более литер с, причем найденный фрагмент текста будет содержать только последовательность литер ь без последовательности литер с.

Рассмотрим строку

$str = "aaabbbcccddd";

В результате операции поиска

$str =~ m/b+(?=c+)/;

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

$ ч — ааа $& - bbb $' — cccddd

Если в операции поиска указать образец /b+с+/, то значения специальных переменных будут следующими:

$" — ааа $& — bbbссс $' - ddd

В свою очередь, операция поиска по образцу / (?=b+) с+/ в нашем примере не даст результата. Данный образец задает условие, что следующий фрагмент текста должен содержать непустую последовательность литер ь. В нашей строке такой -фрагмент будет найден (это фрагмент bbb>, но не будет включен в результат поиска. Следующий фрагмент, в соответствии с образцом, должен представлять непустую последовательность литер с, но в нашем случае этого соответствия не будет, так как мы остановились перед фрагментом ььь, не включив его в результат, и поэтому следующим фрагментом будет bbb, а не ссс.

Конструкцию (?=pattern) будем называть регулярным выражением с положительным постусловием.

(?!pattern)

Конструкция (? [pattern) в регулярном выражении задает условие, что следующий фрагмент текста не должен удовлетворять образцу pattern. Найденный фрагмент не запоминается в переменной $&. Например, результат операции поиска

$str =~ m/b+(?!c+)/;

в рассмотренной выше строке $str будет зафиксирован в следующих значениях специальных переменных:

$' — ааа

$& - bb

$' — bcccddd

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

По аналогии с предыдущей данную конструкцию назовем регулярным выражением с отрицательным постусловием.

(?<=pattern)

Конструкция (?<=pattern) в регулярном выражении задает условие, что предыдущий фрагмент текста должен удовлетворять образцу pattern. Найденный фрагмент не запоминается в переменной $&. Образец pattern должен иметь фиксированную длину, т. е. не содержать множителей.

В нашем примере в результате операции поиска

$str =~ m/(?<=b)b+/;

значения специальных переменных будут распределены следующим образом:

$' — aaab

$& - bb

$' - cccddd I

Данную конструкцию назовем регулярным выражением с положительным предусловием. _/

(?<!pattern)

Конструкция (?<! pattern) в регулярном выражении задает условие, что предыдущий фрагмент текста не должен удовлетворять образцу pattern. Найденный фрагмент не запоминается в переменной $&. Образец pattern должен иметь фиксированную длину, т. е. не содержать множителей.

В нашем примере в результате операции поиска

$str =~ m/(?<!b)c+/;

специальные переменные получат следующие значения:

$' — aaabbbc $& — ее $' - ddd

Данную конструкцию будем называть регулярным выражением с отрицательным предусловием.

(?imsx-imsx)

Задание флагов операции сопоставления с образцом осуществляется в самом образце. Флаги модифицируют выполнение операции и обычно являются частью синтаксиса самой операции. Расширенная конструкция (?imsx-imsx) позволяет задать флаги операции внутри самого образца. Эта возможность может быть полезной, например, в таблицах, когда разные элементы таблицы требуется по-разному сопоставлять с заданным образцом, например, некоторые элементы — с учетом регистра, другие — без учета. Допустимыми являются следующие флаги.

  • i — поиск без учета регистра;
  • dm — строка трактуется как мульти-строка, состоящая из нескольких строк, разделенных символом новой строки;
  • s — строка трактуется как одна строка, в этом случае метасимволу "." соответствует любой одиночный символ, включая символ новой строки;
  • х — разрешается использовать в образцах пробелы и комментарии. При использовании флага х пробелы в образцах игнорируются. Признаком комментария является символ #, как и в основном тексте Peri-программы. Пробелы позволяют сделать образец более читаемым.

Одна из литер i, m, s, x после знака "-" обозначает отмену соответствующего флага.

При помощи данной расширенной конструкции можно задать, например, следующий образец

/(?ix) peri # игнорирование регистра при поиске/

Флаг i предписывает не учитывать регистр в операциях сопоставления с образцом, так что образцу будет соответствовать и слово peri, и слово Perl. Флаг х позволяет выделить слово "peri" пробелами и использовать в образце комментарий. И пробелы, и комментарий не будут учитываться в операции сопоставления с образцом.

Сводка результатов

Изложенное в данном разделе можно суммировать в виде набора правил, которыми следует руководствоваться при работе с регулярными выражениями.

  • Любой одиночный символ, не являющийся метасимволом, представляет самого себя.
  • Специальное значение метасимвола можно отменить, поместив перед ним специальный экранирующий метасимвол "\".
  • Можно определить класс символов, заключив их в квадратные скобки. Если первым после открывающей скобки "[" является символ " ^ ", то вся конструкция обозначает символы, не перечисленные в скобках. Внутри скобок два символа, соединенные знаком "-", определяют диапазон. Чтобы включить в состав класса символ "-", его следует поместить в начале или в конце списка, или экранировать при помощи символа "\".
  • Символы можно задавать при помощи метапоследовательностей, состоящих из символа "\", за оторым следует обычный символ или последовательность символов.
  • Альтернативный выбор задается перечислением альтернативных вариантов, разделенных символом "|". Обычно вся конструкция при этом заключается в круглые скобки.
  • Внутри регулярного выражения можно выделить подобразец, заключив его в круглые скобки. На и-ю конструкцию в скобках можно затем сослаться, используя нотацию \п внутри и $п — вне регулярного выражения.

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

Таблица 10.1. Символы, имеющие специальное значение у в регулярных выражениях Perl

Метасимвол | Интерпретация
\ | Отменяет (экранирует) специальное значение следующего за ним метасимвола
- | Любой одиночный символ, кроме символа новой строки
 | Любой одиночный символ, включая символ новой строки, если в операции сопоставления с образцом задан флаг s
л | Обозначает начало строки, если является первым символом образца
$ | Обозначает конец строки, если является последним символом образца
I | Разделяет альтернативные варианты
[...] | Любой одиночный символ из тех, которые перечислены в квадратных скобках. Пара символов, разделенных знаком минус, задает диапазон символов. Например, [A-Za-z] задает все прописные и строчные буквы английского алфавита. Если первым символом в скобках является символ " ^ ", то вся конструкция обозначает любой символ, не перечисленный в скобках. Внутри скобок символы ".", "*", "[" и "\" теряют свое специальное значение
(...) | Группирование элементов образца в один элемент
* | Нуль и более повторений регулярного выражения, стоящего непосредственно перед *
+ | Одно или более повторений регулярного выражения, стоящего непосредственно перед +
9 | Одно или ни одного повторения регулярного выражения, стоящего непосредственно перед ?
{ n, m } | Минимальное n и максимальное m число повторений регулярного выражения, стоящего перед {n, m). Конструкция {n} означает ровно n повторений, (m } — минимум n повторений
\0nn | Символ, восьмеричный код которого равен nn
\а | При выводе производит звуковой сигнал
\А | Обозначает начало строки
\b | Обозначает границы слова.
 | Под словом понимается последовательность символов из класса \w (см. ниже). Граница слова определяется как точка между символами из класса \w и символами из класса \w (см. ниже)
\В | Обозначает не-границы слова
\сп | Управляющий символ, который генерируется при нажатии комбинации клавиш <Ctrl>+<N>
\d | Любой цифровой символ, то же, что и [0-9]
\D | Любой нецифровой символ, то же, что и [ л О-9]
\е | Символ Esc, ASCII 27
\Е | Ограничитель последовательностей \L, \u, \Q
\f | Символ перевода страницы, ASCI1 1 2
\G | Обозначает точку, в которой закончился предыдущий поиск m/ /g
\1 | Преобразует следующий символ регулярного выражения к нижнему регистру

Таблица 10.2. Метапоследовательности в регулярных выражениях Perl

Метапоследовательность | Значение
\L | Преобразует все последующие символы в регулярном выражении к нижнему регистру до тех пор, пока не встретится последовательность \Е
\n | Символ новой строки, ASCII 10
\Q | Эквивалентно экранированию всех последующих метасимволов в регулярном выражении при помощи символа "\" до тех пор, пока не встретится последовательность \Е
\r | Символ "возврат каретки", ASCII 1 3
\s | Класс пробельных символов: пробел (space), символ табуляции (tab), возврат каретки (carriage return), символ перевода строки (line feed) и символ перевода страницы {form feed); эквивалентное \t,\r,\n, \f]
\S | Класс непробельных символов
\t | Символ табуляции, ASCII 9
\u | Преобразует следующий символ к верхнему регистру
\U | Преобразует все последующие символы в регулярном выражении к верхнему регистру до тех пор, пока не встретится последовательность \Е
\v | Символ вертикальной табуляции, ASCII i 1
\w | Любая буква, цифра или символ подчеркивания
\W | Любой символ, не являющийся буквой, цифрой или символом подчеркивания
\xnn | Символ, шестнадцатеричный код которого равен пп
\z | Обозначает конец строки


Глава 1

Операции с регулярными выражениями

В данной главе неоднократно упоминались операции с регулярными выражениями, такие как поиск по образцу. Основными среди них являются: операция поиска т//, операция замены s/// и операция транслитерации tr///.

Операция поиска

m/PATTERN/cgimosx

Операция поиска HI/PATTERN/ осуществляет поиск образца PATTERN. Результатом операции является значение 1 (ИСТИНА) или пустая строка"

(ЛОЖЬ). По умолчанию поиск осуществляется в строке, содержащейся в специальной переменной $_. Можно назначить другую строку для поиска в ней заданного образца при помощи операций связывания =~ или ! ~:

$var =~ m/PATTERN/cgimosx

В результате последней операции поиск образца PATTERN будет осуществляться в строке, задаваемой переменной $var. Если в правой части операции связывания стоит операция поиска т//, то в левой части может находиться не обязательно переменная, а любое строковое выражение.

Операция ! ~ отличается от операции =~ тем, что возвращает противоположное логическое значение. Например, в результате поиска в строке "aaabbbccc" образца /b+/Г

$s="aaabbbccc" =~ m/b+/; $s="aaabbbccc" !~ m/b+/;,

в обоих случаях будет найден фрагмент ььь. Но в первом случае возвращаемое значение, сохраненное в переменной $s, будет равно 1 (ИСТИНА), а во втором случае — пустой строке " (ЛОЖЬ).

Образец PATTERN может содержать переменные, значения которых подставляются при каждом выполнении поиска по данному образцу.

Флаги cgimosx модифицируют выполнение операции поиска. Флаги imsx имеют тот же смысл, что и в рассмотренном выше случае конструкции расширенного регулярного выражения (?imsx-imsx) .

  • i — поиск без учета регистра;
  • m — трактуется как мульти-строка, состоящая из нескольких строк, разделенных символом новой;
  • s — строка трактуется как одна строка, в этом случае метасимволу "." со-, ответствует любой одиночный символ, включая символ новой строки;
  • х — разрешается использовать в образцах пробелы и комментарии.
  • стальные флаги выполняют следующие функции.
  • g — Задает глобальный поиск образца в заданной строке. Это означает, что будут найдены все фрагменты текста, удовлетворяющие образцу, а не только первый из них, как имеет место по умолчанию. Возвращаемое значение зависит от контекста. Если в составе образца имеются подоб-разцы, заключенные в скобки (), то в контексте массива для каждого заключенного в скобки подобразца возвращается список всех найденных фрагментов. Если в составе образца нет подобразцов, заключенных в скобки, то в контексте массива возвращается список всех найденных фрагментов, удовлетворяющих образцу. В скалярном контексте каждая операция m//g осуществляет поиск следующего фрагмента, удовлетворяющего образцу, возвращая значение 1 (ИСТИНА), если он найден, и пустую строку ", если не найден. Позиция строки, в которой завершился последний поиск образца при установленном флаге g, может быть получена при помощи встроенной функции роз о (см. ниже). Обычно при неудачном поиске начальная позиция поиска восстанавливается в начало строки. Флаг с отменяет восстановление начальной позиции поиска при неудачном поиске образца.

Рассмотрим следующий скрипт.

$str="abaabbaaabbbaaaabbbb";

tt контекст массива, нет подобразцов в скобках

@result=$str =~m/a+b+/g;

print "контекст массива, нет конструкций в скобках:\n";

print "\@result=@result\n";

# контекст массива, есть конструкции в скобках, задающие обратные ссылки

@result=$Str =~m/(a+)(b+)/g;

print "контекст массива, есть конструкции в скобках:\n";

print "\@result=@result\n";

# скалярный контекст

print "скалярный контекст:\n";

while ($result=$str =~m/(a+)(b+)/g) {

print "result=$result, current match is $&, position=",pos($str),"\n"; }

Результатом его выполнения будет вывод:

контекст массива, нет конструкций в скобках:

@result=ab aabb aaabbb aaaabbbb

контекст массива, есть конструкции в скобках:

@result=a b aa bb ааа bbb aaaa bbbb

скалярный контекст:

result=l, current match is ab, position=2

result=l, current match is aabb, position=6

result=l, current match is 'aaabbb, position=12

result=l, current match is aaaabbbb, position=20

HI с — Используется совместно с флагом g. Отменяет восстановление начальной позиции поиска при неудачном поиске образца.

Рассмотрим скрипт

$str="abaabbaaabbbaaaabbbb";

while ($result=$str =~m/(a+)(b+)/g) {

print "result=$result, current match is $&, position=",pos($str),"\n";

} • print "last position=", pos($str), "\n";

Здесь поиск образца /а+ь+/ в строке $str осуществляется в цикле до первой неудачи. При последнем (неудачном) поиске начальная позиция поиска по умолчанию устанавливается в начало строки, в этом случае вывод имеет вид:

result=l, current match is ab, position=2 result=l, current match is aabb, position=6 result=l, current match is aaabbb, position=12 result=l, current match is aaaabbbb, position=20 last position=

Если глобальный поиск осуществлять при установленном флаге с:

while ($result=$str =~m/ (a+) (b+)/gc) {

то при последнем неудачном поиске начальная позиция поиска не переустанавливается. Вывод имеет вид:

result=l, current match is ab r position=2 result=l, current match is aabb, position=6 result=l, current match is aaabbb, position=12 result=l, current match is aaaabbbb, position=20 last position=20

При задании образца для глобального поиска m//g можно использовать ме-тапоследовательность \с, представляющую точку, в которой закончился последний поиск m//g. Например, в результате выполнения скрипта .

^^х

$str="l) abc 2) aabbcc 3) aaabbbccc 4) aaaabbbbcccc";

$str=~m/3\)\s+/g; \

! $str=~m/\Ga+/; ,'

сначала по образцу будет найден фрагмент "3)", а затем фрагмент, удовлетворяющий образцу /а+/ и расположенный сразу за точкой, в которой завершился последний поиск. Этим фрагментом является "ааа".

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

@pattnlist=("a+", "Ы-", "с+", "d+") ; foreach $pattn (@pattnlist) (

$line = <STDIN>; $line =~ m/$pattn/o;

print "pattn=$pattn \$&= $&\n"; }

Массив gpattniist содержит список образцов "a+", "b+", "с+" и "d+". В цикле по элементам этого списка в переменную $iine считывается очередная строка из стандартного ввода. В ней осуществляется поиск по образцу, совпадающему с текущим элементом списка. Поскольку использован флаг о, подстановка значений в образце /$pattn/ будет осуществлена один раз за время жизни данной Peri-программы, т. е. в качестве образца на каждом шаге цикла будет использовано выражение "а+". Если операцию поиска осуществлять без флага о:

$line =~ m/$pattn/,

то в качестве образца будут последовательно использованы все элементы списка "а+", н b+", "с+" и "d+".

В качестве символов-ограничителей для выделения образца можно использовать любую пару символов, не являющихся цифрой, буквой или пробельным символом. Если в качестве ограничителя используется символ "/", то литеру m в обозначении операции можно опустить и использовать упрощенную форму /PATTERN/ BMeCTO m/PATTERN/.

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

Если в качестве ограничителя используется символ "?": ?PATTERN?, то при применении операции поиска находится только одно соответствие. Например, в результате выполнения скрипта

$str="abaabbaaabbbaaaabbbb";

while ($result = $str =~ m?a+b+?g) (

print "result=$result, current match is $&, position=", pos($str),"\n";

}

будет найдено только первое соответствие образцу:

result=l, current match is ab, position=2

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

s/PATTERN/REPLACEMENT/egimosx

Операция замены S/PATTERN/REPLACEMENT/ осуществляет поиск образца PATTERN и, в случае успеха, замену найденного фрагмента текстом REPLACEMENT. Возвращаемым значением является число сделанных замен или пустая строка (ЛОЖЬ), если замен не былоТПо умолчанию поиск и замена осуществляются в специальной переменной $_. Ее можно заменить другой строкой при помощи операций связывания =~ или ! ~:

$var =~ s/PATTERN/REPLACEMENT/egimosx

Флаг $ задает глобальную замену всех фрагментов, удовлетворяющих образцу PATTERN,TeKCTOM REPLACEMENT.

Флаг е означает, что заменяющий текст REPLACEMENT следует рассматривать как Peri-выражение, которое надо предварительно вычислить. Например, в результате выполнения скрипта

$str="abaabbaaabbbaaaabbbb";
$result=$str =~s[ (a+b+)]<length($l)>ge; print "result=$result new
str=$str\n";

будет выведено число сделанных замен $ result и новое значение строки $str, в которой каждый найденный фрагмент, соответствующий образцу [а+b+], заменен числом, равным его длине:

result=4 new str=2468

Флаги imosx имеют тот же смысл, что для операции поиска m//.

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

s(pattern)<replacement>.

Операция транслитерации

tr/SEARCHLIST/REPLACEMENTLIST/cds

Преобразует каждый символ из списка поиска SEARCHLIST в соответствующий символ из списка замены REPLACEMENTLIST и возвращает число преобразованных символов. По умолчанию преобразования осуществляются в строке, задаваемой переменной $_. Как и в рассмотренных выше операциях поиска и замены, при помощи операций связывания =~ и ! ~ можно задать для преобразования строку, отличную от принятой по умолчанию

$str =~ tr/SEARCHLIST/REPLACEMENTLIST/cds

Списки SEARCHLIST и REPLACEMENTLIST задаются перечислением символов, могут содержать диапазоны — два символа, соединенных знаком "-", и иметь собственные символы-ограничители, например, tr(a-j) /0-9/. Операция tr/// имеет синонимичную форму, используемую в потоковом редакторе sed:

y/SEARCHLIST/REPLACEMENTLIST/cds

Флаги cds имеют следующий смысл.

  • с — вместо списка поиска SEARCHLIST использовать его дополнение до основного множества символов (обычно расширенное множество ASCII).
  • d — удалить все символы, входящие в список поиска SEARCHLIST, для которых нет соответствия в списке замены REPLACEMENTLIST. Если флаг d не установлен и список замены REPLACEMENTLIST короче, чем список поиска SEARCHLIST, то вместо недостающих символов в списке замены используется последний символ этого списка. Если список замены пуст, то символы из списка поиска SEARCHLIST преобразуются сами в себя, что удобно использовать для подсчета числа символов в некотором классе. П s — все последовательности символов, которые были преобразованы в один и тот же символ, заменяются одним экземпляром этого символа.

$str =~ tr/A-Z/a-z/; # преобразование прописных букв в строчные $count=$str=~tr/\000//c; # подсчет числа символов в строке $str $str =~ tr/\200-\377/ /cs; # любая последовательность символов

с ASCII-кодами от 128 до 255 преобразуется

в единственный пробел

Следующий скрипт преобразует русский текст в DOS-кодировке 866, содержащийся в файле "866.txt", в русский текст в Windows-кодировке 1251, и записывает преобразованный текст в файл "1251. txt".

open(IN866, "866.txt"); open(OUT1251,">125I.txt"); while ($line=<IN866>) { .

$line=~tr/\200-\257\340-\361/\300-\377\250\270/; print OUT1251 $line;

>

close(IN866); close(OUT1251);

Операция заключения в кавычки qr//

qr/STRING/imosx

Операция qr// по своему синтаксису похожа^наГдругие операции заключения в кавычки, такие как q//, qq//, qx//, qw//. Она обсуждается в данном разделе, так как имеет непосредственное отношение к регулярным выражениям. Регулярное выражение, содержащее переменные, метасимволы, мета-последовательности, расширенный синтаксис, перед использованием должно быть обработано компилятором. Операция qr// осуществляет предварительную компиляцию регулярного выражения STRING, преобразуя его в некоторое внутреннее представление, с тем, чтобы сохранить скомпилированное регулярное выражение в переменной, которую затем можно использовать без повторной компиляции самостоятельно или в составе других регулярных выражений.

Преимущества от применения операции qr// проявляются, например, в следующей ситуации. Допустим, что мы собираемся многократно использовать в качестве образца достаточно сложное регулярное выражение, например, / ^ ([ ^ ]*) *([ ^ ]*)/. Его можно использовать непосредственно в операции сопоставления с образцом

if ($line =~ / ^ (Г ]*) *([ ^ ]*)/) {...},

или сохранить в переменной $pattern= ll/^ ([ ^ ]*) *([ ^ ]*) и обращаться к переменной:

if ($line =~ /$pattern/) (...},

В обоих случаях регулярное выражение при каждом обращении обрабатывается компилятором, что при многократном использовании увеличивает время выполнения. Если сохранить образец при помощи операции qr//:

$pattn = qr/~(r ]*) *<Г ]*)/,

то переменная $pattn будет содержать откомпилированное регулярное выражение, которое можно неоднократно использовать без дополнительной компиляции.

Флаги imosx имеют тот же смысл, что и в операции замены т//. Например, в следующем тексте операция qr// применяется с флагами ох:

$s="aA!Bb2cC3Dd45Ee";

@pattns=("\\d+ # последовательность цифр",

"[A-Z]+ t последовательность прописных букв", "[a-z]+ # последовательность строчных букв"); foreach $pattn Opattns) { my $pattern=qr/$pattn/ox; while ($s=~/$pattern/g) { $p=$p.$&; . } } print "s=$s p=$p\n";

В данном примере определен массив @pattns, состоящий из регулярных выражений. В цикле по элементам массива проверяется наличие в заданной строке $з фрагмента, соответствующего текущему образцу. Найденный фрагмент добавляется в конец строки $р. Флаг х в операции qr// позволяет использовать образцы в том виде, в каком они хранятся в массиве — с пробелами и комментариями. Если в операции qr// флаг о не установлен, то в результате выполнения скрипта строка $р будет состоять из символов строки $s, расположенных в следующем порядке: сначала все цифры, затем все прописные буквы, затем все строчные буквы. Если, как в данном ^тексте, флаг о установлен, то в операции $pattern=qr/$pattn/ox подстановка переменной $pattn произойдет только один раз, и строка $s будет три раза проверяться на наличие фрагмента, удовлетворяющего первому образцу $pattns[i]. В результате строка $р будет состоять только из цифр, входящих в строку $s, повторенных три раза.


Глава 1

Функции для работы со строками

В данном разделе мы рассмотрим некоторые встроенные функции языка Perl, предназначенные для работы со строками текста. Часть из них использует рассмотренное выше понятие регулярного выражения.

ФуНКЦИЯ chop () chop [list]

удаляет последний символ из всех элементов списка list, возвращает последний удаленный символ. Список может состоять из одной строки. Если аргумент отсутствует, операция удаления последнего символа применяется к встроенной переменной $_. Обычно применяется для удаления завершающего символа перевода строки, остающегося при считывании строки из входного файла.

ФуНКЦИЯ length() length EXPR

возвращает длину скалярной величины EXPR в байтах.

#!/usr/bin/perl \^_____,.

$input = <STDIN>;

$Len = length($input);

print "Строка до удаления последнего символа: $input\n";

print "Длина строки до удаления последнего символа: $Len\n";

$Chopped = chop($input};

$Len = length($input);

print "Строка после удаления последнего символа: $input\n";

print "Длина строки после удаления последнего символа: $Len\n";

print "Удаленный символ: <$Chopped>\n";

Если после запуска данного скрипта ввести строку "qwerty", то вывод будет иметь вид:

qwerty

Строка до удаления последнего символа: qwerty

Длина строки до удаления последнего символа: 7 Строка после удаления последнего символа: qwerty Длина строки после удаления последнего символа: 6 Удаленный символ: < >

Последним символом, удаленным функцией chop (), является символ новой строки, сохраненный в переменной $chopped. При выводе он вызывает переход на следующую строку, поэтому в данном выводе третья строка — пустая. В последней операции print вывод осуществляется в две строки, так как переменная $chopped содержит символ новой строки.

Функции lc(), uc(), Icfirst(), ucfirstO

предназначены для преобразования строчных букв в прописные и наоборот.

ФуНКЦИЯ 1с EXPR

возвращает выражение, полученное из выражения EXPR преобразованием всех символов в строчные.

ФуНКЦИЯ UC EXPR

возвращает выражение, полученное из выражения EXPR преобразованием всех символов в прописные.

ФуНКЦИЯ Icfirst EXPR

возвращает выражение, полученное из выражения EXPR преобразованием первого символа в строчный.

ФуНКЦИИЯ ucfirst EXPR

возвращает выражение, полученное из выражения EXPR преобразованием первого символа в прописной.

#!/usr/bin/perl

print "\n Ф-ция uc() преобразует ",$s="upper case"," в ",uc $s;

print "\n Ф-ция ucfirst() преобразует ",$s="uPPER CASE"," в ",ucfirst $s;

print "\n Ф-ция 1с() преобразует ", $s="LOWER CASE"," в ",1с $s;

print "\n ф-ция IcfirstO преобразует ",$s="Lower case"," в ",lcfirst $s;

В результате выполнения данного скрипта будут напечатаны строки:

Ф-ция uc() преобразует upper case в UPPER CASE Ф-ция ucfirst() преобразует UPPER CASE в UPPER CASE Ф-ция 1с() преобразует LOWER CASE в lower case Ф-ция IcfirstO преобразует Lower case в lower case

ФуНКЦИЯ join() join EXPR, LIST

объединяет отдельные строки списка LIST в одну, используя в качестве разделителя строк значение выражения EXPR, и возвращает эту строку.

Функция split ()

split [/PATTERN/[, EXPR[, LIMIT]]]

разбивает строку EXPR на отдельные строки, используя в качестве разделителя образец, задаваемый регулярным выражением PATTERN. В списковом контексте возвращает массив полученных строк, в скалярном контексте — их число. Если функция split о вызывается в скалярном контексте, выделяемые строки помещаются в предопределенный массив @_. Об этом не следует забывать, так как массив §_ обычно используется для передачи параметров в подпрограмму, и обращение к функции split о неявно в скалярном контексте эти параметры уничтожит.

Если присутствует параметр LIMIT, то он задает максимальное количество строк, на которое может быть разбита исходная строка. Отрицательное значение параметра LIMIT трактуется как произвольно большое положительное число.

Если параметр EXPR опущен, разбивается строка $_. Если отсутствует также и параметр PATTERN, то в качестве разделителей полей используются пробельные символы после пропуска всех начальных пробельных символов (что соответствует заданию образца в виде /\s+/). К пробельным символам относятся пробел (space), символ табуляции ЛаЬ), возврат каретки (carriage feturn), символ перевода строки (line feed) и^симврл перевода страницы (form feed). .

Замечание

Предопределенная глобальная переменная $_ служит для обозначения используемой по умолчанию области ввода и поиска по образцу. Обычно мы осуществляем ввод при помощи операции "0" ("ромб"). Внутри угловых скобок о может стоять дескриптор файла ввода, например, <STDIN>. Если дескриптор файла отсутствует, то в качестве файлов ввода используются файлы, переданные программе Perl в качестве аргументов командной строки. Пусть, например, программа содержится в файле script. pi.

#!/usr/bin/perl while (<>) {

print; };

Программа вызвана следующим образом script.pl filel file2 file3

Тогда операция о будет считывать строки сначала из файла filel, затем из файла file2 и, наконец, из файла file3. Если в командной строке файлы не указаны, то в качестве файла ввода будет использован стандартный ввод.

Только в случае, когда условное выражение оператора while состоит из единственной операции "ромб", вводимое значение автоматически присваивается предопределенной переменной $_. Вот что означают слова о том, что переменная $_ применяется для обозначения используемой по умолчанию области ввода. Аналогично обстоит дело с поиском по образцу.

I!/usr/bin/perl while (<>} {

chop;

print "Число полей во входной строке '$_' равно ", $n=split;

print "ХпВходная строка разбита на строки:\n";

foreach $i (@_) {

print $i . "\n"; }

print "Объединение списка строк в одну строку через ' +':\n";

$joined = join "+", @_;

print "$joined\n"; }

В результате применения операции ввода О внутри условного выражения оператора while вводимая строка будет присвоена переменной $_. Функция chop о без параметров применяется к переменной $_. В операции print вторым операндом является выражение $n=spiit, в котором функция split вызывается в скалярном контексте и без параметров. Поэтому она применяется по умолчанию к переменной $_. В качестве разделителей полей по умолчанию используется множество пробельных символов, а результат помещается в масссив @_. Затем к массиву @_ применяется функция joint), объединяющая строки-элементы массива в одну строку.

Если ввести строку "one two three", то вывод будет иметь вид:

one two three

Число полей во входной строке 'one two three' равно 3

Входная строка разбита на строки:

one

two

three

Объединение списка строк в одну строку через '+':

one+two+three

ФУНКЦИЯ index()

index STR, SUBSTR[, POSITION]

находит первое, начиная с позиции POSITION, вхождение подстроки SUBSTR в строку STR, и возвращает найденную позицию. Если параметр POSITION не задан, по умолчанию принимается значение POSITION = $[. Если подстрока SUBSTR не найдена, возвращается значение $ [ - 1.

Замечание

Предопределенная переменная $ [ содержит индекс первого элемента в массиве и первого элемента в строке. По умолчанию ее значение равно 0. В принципе его можно изменить, но делать это не рекомендуется. Таким образом, по умолчанию значение параметра POSITION полагается равным 0, а функция index возвращает-1, если не найдена подстрока SUBSTR.

ФУНКЦИЯ rindex{}

rindex STR, SUBSTR, POSITION

находит последнее, ограниченное справа позицией POSITION, вхождение подстроки SUBSTR в строку STR, и возвращает найденную позицию. Если подстрока SUBSTR не найдена, возвращается значение $ [ - i.

f!/bin/peri • ,

$STR = "Этот безумный, безумный, безумный, безумный мир!";

$ SUBSTR = "безумный"; ^~— --''

$POS = 7;

print "Индекс первого символа строки по умолчанию равен $[\n";

print "Позиция первого вхождения подстроки '$SUBSTR'

в строку '$STR' = ",index($STR, $SUBSTR), "\n"; print "Позиция первого после позиции $POS вхождения подстроки '$SUBSTR'

в строку '$STR' = ",index($STR, $SUBSTR, $POS), "\n"; print "Позиция последнего вхождения подстроки '$SUBSTR'

в строку '$STR 1 = ",rindex($STR, $SUBSTR), "\n"; print "Позиция последнего перед позицией $POS вхождения подстроки '$SUBSTR'

в строку '$STR' = ",rindex($STR, $SUBSTR, $POS), "\n";

$[=2;

print "ХпИндекс первого символа строки по умолчанию изменен на $[\n"; print "Позиция первого вхождения подстроки '$SUBSTR'

в строку '$STR' = ",index($STR, $SUBSTR), "\n"; print "Позиция первого после позиции $POS вхождения подстроки '$SUBSTR'

в строку '$STR' = ",index($STR, $SUBSTR, $POS), "\n";

print "Позиция последнего вхождения подстроки '$SUBSTR' в строку '$STR' = ",rindex($STR, $SUBSTR), "\n";

print "Позиция последнего перед позицией $POS вхождения подстроки '$SUBSTR' в строку '$STR' = ",rindex($STR, $SUBSTR, $POS), "\n";

В результате выполнения скрипта будут выведены следующие строки:

Индекс первого символа строки по умолчанию равен О Позиция первого вхождения подстроки 'безумный'

в строку 'Этот безумный, безумный, безумный, безумный мир!' = 5 Позиция первого после позиции 7 вхождения подстроки 'безумный'

в строку 'Этот безумный, безумный, безумный, безумный мир!' = 15 Позиция последнего вхождения подстроки 'безумный'

в строку 'Этот безумный, безумный, безумный, .безумный мир!' = 35 Позиция последнего перед позицией 7 вхождения подстроки 'безумный'

в строку 'Этот безумный, безумный, безумный, безумный мир!' = 5

Индекс первого символа строки по умолчанию изменен на 2 Позиция первого вхождения подстроки 'безумный'

в строку 'Этот безумный, безумный, безумный, безумный мир!' = 7 Позиция первого после позиции 7 вхождения подстроки 'безумный'

в строку 'Этот безумный, безумный, безумный, безумный мир!' = 7 Позиция последнего вхождения подстроки 'безумный'

в строку 'Этот безумный, безумный, безумный, безумный мир!' = 37 Позиция последнего перед позицией 7 вхождения подстроки 'безумный'

в строку 'Этот безумный, безумный, безумный, безумный мир!' = 7

Функция substr ()

substr EXPR, OFFSET [,LENGTH [,REPLACEMENT ]]

извлекает из выражения EXPR подстроку и возвращает ее. Возвращаемая подстрока состоит из LENGTH символов, расположенных справа от позиции OFFSET. Если параметр LENGTH опущен, возвращается вся оставшаяся часть выражения EXPR. Если параметр LENGTH отрицательный, его абсолютное значение задает количество символов от конца строки, не включаемых в результирующую подстроку. Если параметр OFFSET имеет отрицательное значение, смещение отсчитывается с конца строки. Функция substr о может стоять в левой части операции присваивания. Например, в результате выполнения операторов

$Str = "Язык Pascal"; substr($Str, 5,6) = "Perl";

переменная $str получит значение "язык Peri". Тот же результат будет достигнут, если указать параметр REPLACEMENT, значение которого будет подставлено в EXPR вместо выделенной подстроки. Сама подстрока в этом случае возвращается в качестве значения функции substr ().

#!/bin/peri

# Исходная строка

$Str = "Карл у Клары украл кораллы";

$0ffset = 13;

print "Исходная строка:'$Str'\n";

# Смещение 13, длина подстроки не задана

$Substr = substr $Str, $Offset;

print "Смещение $0ffset, длина подстроки не задана, результат:\n";

print "$Substr\n";

# Смещение 13, длина подстроки +5

$Substr = substr $Str, $0ffset, 5;

print "Смещение $0ffset, длина подстроки +5, результат:\n";

print "$Substr\n"; /

# Смещение 13, длина подстроки -1 \

$Substr = substr $Str, $0ffset, -1;

print "Смещение $0ffset, длина подстроки -1, результат:\n";

print "$Substr\n";

# Отрицательное смещение -7, длина подстроки +7

$0ffset = -7;

$Substr = substr $Str, $Offset, 7;

print "Отрицательное смещение $Offset, длина подстроки +7, результат:\n";

print "$Substr\n";

f Отрицательное смещение -7, длина подстроки -1

$Substr = substr $Str, $0ffset, -1;

print "Отрицательное смещение $Offset, длина подстроки -1, результат:\n";

print "$Substr\n";

t Замена подстроки

$Repl = "бокалы";

$Substr = substr $Str,$Offset,7,$Repl;

print "В строке '$Str' слово '$Repl' заменяет слово '$Substr'\n";

Вывод выглядит следующим образом:

Исходная строка:'Карл у Клары украл кораллы'

Смещение 13, длина подстроки не задана, результат:

украл кораллы

Смещение 13, длина подстроки +5, результат:

украл

Смещение 13, длина подстроки -1, результат:

украл коралл

Отрицательное смещение -7, длина подстроки +7, результат:

кораллы

Отрицательное смещение -7, длина подстроки -1, результат:

коралл

В строке 'Карл у Клары украл бокалы' слово 'бокалы' заменяет слово 'кораллы'

Функция eval () eval EXPR

рассматривает параметр EXPR как текст Peri-программы, компилирует его и, если не обнаруживает ошибок, выполняет в текущем вычислительном окружении. Если параметр EXPR отсутствует, вместо него по умолчанию используется глобальная переменная $_. Компиляция программного кода EXPR осуществляется при каждом вызове функции eval () во время выполнения основной программы. Если выполнение мини-программы EXPR завершается успешно, функция eval о возвращает значение последнего выражения, вычисленного внутри EXPR. Если код EXPR содержит синтаксические ошибки, или обращение к функции die (}, или возникла ошибка во время выполнения EXPR, то в специальную переменную $@ помещается сообщение об ошибке, а функция eval () возвращает неопределенное значение.

Замечание
Основным применением функции eval о является перехватывание исключений. Исключением мы называем ошибку, возникающую при выполнении программы, когда нормальная последовательность выполнения прерывается (например, при делении на нуль). Обычной реакцией на исключение является аварийное завершение программы. Язык Perl предоставляет возможность перехватывать исключения без аварийного выхода. Если исключение возникло в основной программе, то программа завершается. Если ошибка возникла внутри мини-программы функции eval (), то аварийно завершается только функция eval(), а основная программа продолжает выполняться и может реагировать на возникшую ошибку, сообщение о которой ей доступно через переменную $@.

В следующем примере функция eval о применяется для перехватывания ошибки, связанной с делением на 0 при вычислении функции ctg(x). Используются встроенные функции sin, cos и warn. Последняя функция осуществляет вывод сообщения, задаваемого ее аргументом, на стандартное устройство вывода ошибок STDERR.

#!/bin/peri

$fi = 0.314159265358979;

$f = '$ctg = cos($x)/sin($x)

for $i (0..10) {

$x = $i*$fi;

eval $f;

print "x= $x ctg(x) = $ctg\n" if defined $ctg;

warn "x= $x ", $@ if not defined $ctg; };

Вывод программы выглядит следующим образом

х= 0 Illegal division by zero at (eval 1) line 1. x= 0.314159265358979 ctg(x) =3.07768353717526 x= 0.628318530717958 ctg(x) = 1.37638192047118

Замечание

Иногда бывает полезно искусственно вызвать исключительную ситуацию. Для этого можно воспользоваться функцией die () LIST. Назначение функции die () — генерировать исключения. Если функция die () вызывается в основной программе вне функции eval (), то она осуществляет аварийное завершение основной программы и выводит сообщение об ошибке LIST на стандарт* ное устройство вывода ошибок STDERR. Если она вызывается внутри функции eval (), то осуществляет аварийное завершение eval () и помещает сообщение об ошибке в специальную переменную $@.

Функция pos()

pos [$SCALAR]

возвращает позицию, в которой завершился последний глобальный поиск $scALAR=~m/.../g, осуществленный в строке, задаваемой переменной $SCALAR. Возвращаемое значение равно числу length($') + length($&). Следующий глобальный поиск m/.../g в данной строке начнется именно с этой позиции.

Если аргумент $ SCALAR отсутствует, возвращается позиция завершения последнего глобального поиска, осуществленного в строке $_.

$words = "one two three four"; while ($words =~ m/\w+/g) {

print "pos=",pos($words)," length(\$~)=",length($'),

" length(\$s)=",length($s),"\n"; }

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

pos=3 length($~}=0 length($&)=3

pos=7 length{$')=4 length($&)=3

pos=13 length($~)=8 length($&)=5

pos=18 length($')=14 length($&)=4

Функцию pos () можно использовать в левой части операции присваивания для изменения начальной позиции следующего поиска:

I изменение начальной позиции для последующего поиска

$words = "one two three four";

pos $words = 4;

while ($words =~ m/\w+/g) {

print pos $words, "\n"; }

В последнем случае поиск слов начнется со второго слова, и будут выведены номера позиций 7, 13 и 18.

ФУНКЦИЯ quotemeta () quotemeta [EXPR]

возвращает строку EXPR, в которой все символы, кроме алфавитно-цифровых символов и символа подчеркивания "_", экранированы символом "\". Например, в результате выполнения

print quotemeta "*****", "\n";

будет выведена строка

\*\*\*\*\*

Если аргумент EXPR отсутствует, вместо него используется переменная $_.


Глава 1

Вопросы и Упражнения

Вопросы для самоконтроля

1. Что такое регулярное выражение?

2. Какие символы имеют в регулярном выражении Perl специальное значение?

3. Что такое метапоследовательность, как она образуется?

4. Что такое обратная ссылка?

5. Какая переменная используется в операции подстановки по умолчанию?

6. Какой смысл имеет символ "$" в следующих регулярных выражениях:

/abc*$/

/[аbс*$]/

/$abc/

7. Какой смысл имеет символ " ^ " в следующих регулярных выражениях:

/ ^ аbс/

/[аbс]/

/аbс ^ /

8. Объясните, какие множества строк соответствуют следующим образцам. Приведите пример.

/a.out/

/a\.out/

/\d{2,3}-\d{2}-\d{2}/

/(.)(.).\2\1/ /(.) (.).\02\01/

9. Напишите образец, задающий палиндром из шести букв.

10. Напишите команду замены, которая:

- заменяет все символы новой строки пробелами;

- выделяет из полного маршрутного имени файла имя файла;

- выделяет из полного маршрутного имени файла имя каталога.

11. Каково значение следующих выражений, если значение переменной

$var равно "123qwerty"? $var =~ /./ $var =- /[A-Z]*/ $var =~ /\w{4-9}/ $var =~ /(\d)2(\D/ $var =~ /qwerty$/ $var =~ /123?/

11. Какое значение будет иметь переменная $var после следующих операций подстановки, если ее начальное значение равно "qwertyi23qwerty"?

$var =~ s/qwerty/XYZ/; $var =~ s/[a-zJ+/X/g; $var =~ s/B/W/i; $var =~ s/(.)\d.*\l/d/; $var =~ s/(\d+)/$l*2/e;

12. Начальное значение переменной $var равно "qwertyi23qwerty". Каким оно будет после выполнения операций транслитерации?

$var =~ tr/a-z/A-Z/; $var =~ tr/a-z/0-9/; $var =~ tr/a-z/O-9/d; $var =~ tr/231/564/; $var =~ tr/123/ /s; . $var =~ tr/123//cd;

13. Переменная $var имеет значение "qwertyqwerty". Каково значение, возвращаемое функцией?

substr ($var, 0, 3);

substr ($var, 4);

substr ($var, -2, 2);

substr ($var, 2, 0) ;

index ($var, "rt"); index ($var, "rtyu"); index ($var, "er", 1); index ($var, "er", 7); rindex ($var, "er");

Упражнения

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

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

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

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

5. Напишите программу, которая выполняет преобразование русского текста из одной системы кодировки в другую:

(Dos 866, Windows 1251, UNIX KOI8} <=> (Dos 866, Windows 1251, UNIX, KOI8}

Для выполнения задания можно воспользоваться табл. 10.3, содержащей шестнадцатеричные коды символов русского алфавита.

Таблица 10.3. Таблицы кодов русского алфавита

Символ | 866 | 1251 | KOI8 | Символ | 866 | 1251 | KOI8
А | 80 | СО | Е1 | а | АО | ЕО | С1
Б | 81 | С1 | Е2 | б | А1 | Е1 | С2
В | 82 | С2 | F7 | в | А2 | Е2 | D7
Г | 83 | СЗ | Е7 | г | A3 | ЕЗ | С7
Д | 84 | С4 | Е4 | Д | А4 | Е4 | С4
Е | 85 | С5 | Е5 | е | А5 | Е5 | С5
Ё | FO | А8 | ВЗ | е | F1 | В8 | A3
Ж | 86 | С6 | F6 | ж | А6 | Е6 | D6
3 | 87 | С7 | FA | 3 | А7 | Е7 | DA
И | 88 | С8 | Е9 | и | А8 | Е8 | С9
И | 89 | С9 | EA | Й | А9 | Е9 | СА
К | 8А | СА | EB | к | АА | ЕА | СВ
Л | 8В | СВ | EC | л | АВ | ЕВ | СС
М | 8С | СС | ED | M | АС | ЕС | CD
Н | 8D | CD | ЕЕ | Н | AD | ED | СЕ
О | 8Е | СЕ | EF | 0 | АЕ | ЕЕ | CF
П | 8F | CF | FO | П | AF | EF | DO
Р | 90 | DO | F2 | Р | ЕО | FO | D2
С | 91 | D1 | F3 | с | Е1 | F1 | D3
Т | 92 | D2 | F4 | т | Е2 | F2 | D4
У | 93 | D3 | F5 | У | ЕЗ | F3 | D5
ф | 94 | D4 | E6 | ф | Е4 | F4 | С6
X | 95 | D5 | E8 | X | Е5 | F5 | С8
Ц | 96 | D6 | E3 | ц | Е6 | F6 | СЗ
ч | 97 | D7 | FE | Ц | Е7 | F7 | DE
ш | 98 | D8 | FB | ш | Е8 | F8 | DB
Щ | 99 | D9 | FD | Щ | Е9 | F9 | DD
ъ | 9А | DA | FF | ъ | ЕА | FA | DF
ы | 9В | DB | F9 | ы | ЕВ | FB | D9
ь | 9С | DC | F8 | ь | ЕС | FC | D8
э | 9D | DD | FC | э | ED | FD | DC
ю | 9Е | DE | EO | ю | ЕЕ | FE | СО
я | 9F | DF | F1 | я | EF | FF | D1


Глава 1

Часть 11. Подпрограммы и функции

Определение подпрограммы
Вызов подпрограммы
Локальные переменные в подпрограммах
Передача параметров
В каких случаях функцию local нельзя заменить функцией mу
Прототипы
Рекурсивные подпрограммы
Вопросы и Упражнения


Глава 1

Определение подпрограммы

Подпрограммы в языке Perl играют ту же роль, что и функции в языке С, или процедуры и функции в языке Pascal. Они выполняют две основные задачи:

  • позволяют разбить одну (большую программу на несколько небольших частей, делая ее более ясной для понимания;
  • объединяют операторы в'одну группу для повторного использования. В языке Perl не различаются понятия "подпрограмма" и "функция", эти слова являются синонимами.

Подпрограмма может быть определена в любом месте основной программы при помощи описания

sub name [(proto)] ({block}};

Здесь name имя подпрограммы;

(proto) прототип, конструкция, используемая для описания передаваемых подпрограмме параметров;

{block} блок операторов, являющийся определением подпрограммы и выполняющийся при каждом ее вызове.

Форма

sub name [ (proto)];

представляет собой предварительное объявление подпрограммы без ее определения. Пользователь, предпочитающий помещать описания всех подпрограмм в конце основной программы, должен при вызове еще не определенной фуНКЦИИ ИСПОЛЬЗОВаТЬ Специальный СИНТаКСИС &лагае ИЛИ name. Если же некоторое имя предварительно объявить в качестве имени функции, то сразу после объявления к этой функции можно обращаться просто по имени без применения специального синтаксиса.

#!/usr/bin/perl sub max {

my $maximum = shift @$_;

my $x;

foreach $x (@_) {

$maximum=$x if ($x > $maximum) ;

}

return $maximum } print "Наибольший аргумент=", max(3,5,17,9), "\n";

В данном примере функция max () возвращает наибольший из своих аргументов. Об использовании функции ту() и массива @_ будет рассказано ниже.

Данный способ определения подпрограмм не является единственным. Существуют и другие варианты:

  • текст подпрограммы может храниться в отдельном файле и загружаться в основную программу при ПОМОЩИ КЛЮЧеВЫХ СЛОВ do, require, use;

П строка, содержащая текст подпрограммы, может быть передана в качестве аргумента функции eval о (см. в части 10); в этом случае компиляция кода подпрограммы осуществляется при каждом вызове функции eval ();

  • анонимную подпрограмму можно определить при помощи ссылки на нее (см. в части 9).

Применение функции eval () и ссылки на анонимную подпрограмму были рассмотрены ранее.

Конструкция do filename вызывает выполнение Peri-программы, содержащейся в файле filename. Если файл filename недоступен для чтения, функ-ция do возвращает неопределенное значение и присваивает соответствующее значение специальной переменной $!. Если файл filename может быть прочитан, но возникают ошибки при его компиляции или выполнении, то функция do возвращает неопределенное значение и помещает в переменную $@ сообщение с указанием строки, содержащей ошибку. Если компиляция прошла успешно, функция do возвращает значение последнего выражения,

Вычисленного В файле filename.

Замечание

Специальная переменная $! служит для хранения сообщения о последней системной ошибке. Такая ошибка возникает при обращении „к операционной системе с запросом на предоставление некоторой услуги, как, например, создание файла, чтение или запись в него.

Специальная переменная $@ 'используется для хранения сообщения, генерируемого при последнем обращении к функциям eval () или do filename,

# файл "l.pl":

#!/usr/bin/perl \ do "2.pi"; \ print "ошибка: $@\n" if $@; do "3.pl"; ' . j print "системная ошибка: $!\n" if $!;

# файл "2.pi":

$x=l;

$y=0;

$z=$x/$y;

print "z= $z\n";

Peri-программа "i.pi", используя конструкцию do filename, пытается выполнить сценарии, содержащиеся в файлах "2.pi" и "З.р1". Первый из них содержит в третьей строке операцию деления на 0, вызывающую появление ошибки во время выполнения программы, а второй вообще не существует. В результате выполнения файла "i .pi" будут выведены следующие сообщения:

ошибка: Illegal division by zero at 2.pi line 3. системная ошибка: No such file or directory

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

(Директивы компилятора use и require рассмотрены в части 12 )


Глава 1

Вызов подпрограммы

Мы знаем, что принадлежность к тому или иному типу определяется префиксом имени: $х — скалярная переменная, @х — массив, %х — ассоциативный массив. Префиксом функции является символ "&". К любой подпрограмме можно обратиться, указав ее имя с префиксом &:

Sname args; Sname(args); Sname;

Здесь args обозначает список аргументов подпрограммы. Если список аргументов отсутствует, вместо него используется специальный массив @_.

Если после имени подпрограммы следуют скобки, префикс & можно опустить:

name (args); name();

Если до обращения к ней подпрограмма была объявлена или импортирована, то скобки также можно опустить:

sub name {. . .}; name args;

name ;

Если подпрограмма вызывается через ссылку на нее, префикс является обязательным:

$subref = sub (...}; Ssubref(args) ;

ssubref;

Подпрограмма может быть использована в выражении как функция, возвращающая значение. По умолчанию значением подпрограммы является последнее вычисленное в ней выражение. Его можно изменить, указав явно в качестве аргумента функцию return () в любой точке подпрограммы. Возвращаемое значение может быть скалярной величиной или массивом.


Глава 1

Локальные переменные в подпрограммах

Областью видимости или областью действия переменной мы будем называть часть программы, где данная переменная может быть использована. В языке Perl, как мы знаем, нет обязательного явного описания переменных. Точкой определения переменной является место, где она впервые встречается в программе. Область действия большинства переменных ограничена пакетом. Исключение составляют некоторые специальные предопределенные глобальные переменные интерпретатора peri. Пакет — это механизм, позволяющий создать свое пространство имен для некоторого отрезка программы (этот отрезок может включать всю программу). Каждый фрагмент кода Perl-лрограммы относится к соответствующему пакету.

(Пакеты рассматриваются в части 12 , а специальные переменные — в части 14 )

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

ПОМОЩИ ФУНКЦИЙ my() И local () .

Функция ту()

Функция ту о используется для объявления одной или нескольких переменных локальными:

my EXPR

и ограничивает их область действия:

  • подпрограммой;
  • заключенным в фигурные скобки блоком операторов;
  • выражением, переданным на выполнение функции eval ();
  • файлом, в зависимости от того, в каком месте вызвана для объявления переменных сама функция my ().

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

my ($myvar, @mylist, %myhash);

Одновременно с объявлением переменные могут быть инициализированы:

my $pi = 3.14159;

ту ($pi, $exp) = (3.14159, 2.71828);

Переменные, объявленные при помощи функции ту (), доступны в своей области действия только для подпрограмм, определенных в этой области. Для подпрограмм, определенных за ее пределами, они недоступны. Такие переменные называют лексическими, а саму область видимости — лексической или статической областью видимости.

Функция lосаl ()

Функция local () также используется для объявления и инициализации переменных:

local EXPR;

local ($myvar, @mylist, %myhash);

local $pi = 3.14159;

local ($pi, $exp) = (3.14159, 2.71828);

но, в отличие от функции ту о она создает не локальные переменные, а временные значения для глобальных переменных внутри:

  • подпрограммы;
  • заключенного в фигурные скобки блока операторов;
  • выражения, переданного на выполнение функции eval ();
  • файла;

в зависимости от того, в каком месте вызвана для объявления переменных сама функция local (). Если функция local () применяется для описания нескольких переменных, они должны быть заключены в скобки. Если глобальная переменная, объявленная при помощи этой функции, ранее встречалась до объявления и имела некоторое значение, то это значение сохраняется в скрытом стеке и восстанавливается после выхода соответственно из подпрограммы, блока, функции eval (} или файла. Переменная, объявленная при помощи функции local (), или, точнее, ее временное значение, доступна для' любой функции, вызванной внутри подпрограммы, блока, функции eval о или файла, в которых сделано объявление. Такую переменную называют динамической, а ее область видимости — динамической областью видимости. В • названии отражается тот факт, что область видимости переменной динамически изменяется с каждым вызовом функции, получающей доступ к этой переменной.

Функция ту о является относительно новой, она появилась в версии Perl 5. Для создания действительно локальных переменных рекомендуется использовать именно функцию ту о, а не функцию local о. Впрочем, есть несколько исключений. О них мы расскажем ниже.

В следующем примере показано, чем отличаются переменные, объявленные

При ПОМОЩИ функций mу () и local () .

sub fl{

local ($x) = "aaaa";

my($y) = "bbbb";

print("fl: x = $x\n");

print("fl: y='$y\n\n");

f 2 (} ;

print("fl: x = $x\n");

print("fl: у = $y\n\n");

} • ' ' ' sub f2{

print("f2: x = $x\n");

print("f2: y=$y\n\n");

$x = "cccc";

$y = "dddd";

print("f2: x = $x\n");

print("f2: y=$y\n\n");

I

Результатом выполнения данного примера будет следующий вывод:

II л = aaaa

f. у = bbbb ,

2: x — aaaa с2: у =

f2: x = cccc £2: у = dddd

fl: x = cccc fl: у = bbbb

Как видно из приведенного результата, функция f2 () не имеет доступа к переменной $у, объявленной при помощи функции ту о внутри функции fi () , и, напротив, имеет доступ к переменной $х, объявленной внутри fl ()

при ПОМОЩИ функции local () .


Глава 1

Передача параметров

Информация в подпрограмму и обратно передается через параметры (аргументы). Для передачи параметров в подпрограмму используется специальный массив @_. Все параметры запоминаются в элементах массива $_ [ 0 ], $_ [ 1 ] и т. д. Такой механизм позволяет передавать в подпрограмму произвольное количество параметров.

Массив @_ является локальным для данной подпрограммы, но его элементы — это псевдонимы действительных скалярных параметров. Изменение элемента массива @_ вызывает изменение соответствующего действительного параметра.

В языках программирования различают передачу параметров по ссылке и по значению. При передаче параметров по значению подпрограмма получает копию переменной. Изменение копии внутри подпрограммы не влияет на ее оригинал. При передаче параметров по ссылке подпрограмма получает доступ к самой переменной и может ее изменять.

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

#!/usr/bin/perl

# Передача в подпрограмму параметров по значению sub f {

my($x, $y) = @_; return (++$х * —$у); }

$val = f ^lib-print "Значение (9+1) * (11-1) равно $val.\n"; $х = 9; $У = 11;

$val = f($x,$y);

print "Значение ($х+1) * ($у-1) равно $val.\n"; print "Значение \$х остается равным $х, а \$у равным $у.\п";

Результат выполнения:

Значение (9+1) * (11-1) равно 100.

Значение (9+1) * (11-1) равно 100.

Значение $х остается равным 9, а $у равным 11.

Передача по ссылке параметров-массивов

Итак, подпрограмма получает и возвращает параметры через специальный массив @_. Если параметр является массивом или хеш-массивом, его элементы также сохраняются в массиве параметров @_. При передаче в подпрограмму нескольких параметров-массивов или хеш-массивов они утрачивают свою целостность. Иными словами, после записи параметров-массивов (хеш-массивов) в массив @_ из него невозможно выделить отдельный параметр-массив (хеш-массив): все параметры в массиве @_ хранятся единой "кучей". Для сохранения при передаче в подпрограмму целостности массива или хеш-массива существуют два основных подхода.

Использование типа typeglob

Первый подход, более старый, заключается в использовании внутреннего типа данных, называемого typeglob. Принадлежность к типу typeglob обозначается префиксом "*". Префикс "*" можно рассматривать как метасимвол, вместо которого может стоять любой из префиксов "$", "@", "%", "&", обозначающих тип данных "скаляр", "массив", "хеш-массив", "функция" соответственно. Интерпретатор преобразует переменную типа typeglob, например, *abc, в скалярную величину. Эта величина является ссылкой на гнездо в таблице символов, содержащее элементы, разных типов с одинаковым именем abc, и представляет любой из этих элементов. Например, запись *abc обозначает всю совокупность, а также любую из следующих переменных: скаляр $abc, массив @abc, хеш %abc, функция sabc.

(Таблицы символов обсуждаются в части 12 )

Передача в подпрограмму вместо параметра-массива или хеш-массива соответствующей переменной типа typeglob является имитацией передачи параметра-массива (хеш-массива) по ссылке с сохранением его целостности. Рассмотрим следующий пример.

sub doublargs {

local(*mylist, *myhash) = @_;

foreach $item (@mylist) { $item *= 2;

}

foreach $key (keys %myhash) { $myhash{$key} *= 2;

} }

@somelist= (1,2,3); /^~~- """"~\ %somehash=("one"=>5, "two"=>15, "three"=>20); print "начальные значения:\n\@somelist=@somelist\n"; foreach $key (keys %somehash) {

print "\$somehash{$key}=$somehash{$key} ";

}

print "\n";

doublargs(*somelist,*somehash);

print "итоговые значения:\n\@somelist=@somelist\n";

foreach $key (keys %somehash) {

print "\$somehash{$key}=$somehash{$key} "; } print "\n";

Подпрограмма doubiargs принимает на вход массив и хеш-массив и изменяет их элементы, умножая на 2. Вместо массива и хеш-массива в подпрограмму передаются соответствующие переменные типа typeglob, которые легко выделить из массива @_, так как фактически они являются скалярами. Обратите внимание на применение функции local. Использовать вместо нее функцию ту здесь нельзя. Переменная типа typeglob не может быть локальной, она представляет несколько одноименных переменных разных типов из таблицы символов. Далее возникает вопрос, каким образом изменение в подпрограмме массива @myiist влияет на изменение фактического параметра gsomeiist. Дело в том, что операция присваивания вида *х = *у создает синоним *х для гнезда таблицы символов *у, так что осуществление операции над $х, @х, %х эквивалентно осуществлению этой операции над $у, @у, %у. В результате присваивания

local(*mylist, *myhash) = @_;

создается псевдоним *myiist для *someiist, поэтому все изменения элементов массива @myiist внутри подпрограммы эквивалентны изменениям элементов массива @someiist. Все сказанное справедливо и для хеш-массивов %myhash и %somehash. Результат подтверждает корректность передачи массива и хеш-массива по ссылке:

начальные значения:

@somelist=l 2 3

$somehash{one}=5 $somehash{three}=20 $somehashftwo)=15

итоговые значения:

@somelist=2 4 6

$somehash{one}=10 $somehash(three}=40 $somehash{two}=30

Использование ссылок

Второй, более новый способ передачи массивов в подпрограмму заключается в том, чтобы вместо собственно массивов или хеш-массивов передавать ссылки на них. Ссылка является скалярной величиной и ее легко выделить в массиве параметров @_. Внутри подпрограммы остается только применить к ссылке операцию разыменования для того, чтобы получить доступ к фактическому параметру. Поскольку ссылки появились только в версии Perl 5, то этот способ является относительно новым. При помощи ссылок предыдущий пример можно записать в следующем виде,

sub doublparms {

ray ($listref, $hashref) = @_;

foreach $item (@$listref} { $item *= 2;

} .

foreach $key (keys %$hashref) { $$hashref{$key} *= 2;

} }

@somelist=(1,2,3) ;

%somehash=("one"=>5, "two"=>15, "three"=>20); print "начальные значения:\@somelist=@somelist\n"; foreach $key (keys %somehash) { .

print "\$somehash{$key}=$somehash{$key} "; }

print "\n";

doublparms(\@somelist,\%somehash); print "итоговые значения:\n\@somelist=@somelist\n"; foreach $key (keys %somehash) {

print "\$somehash{$key}=$somehash($key} "; } print "\n";

Здесь для описания локальных переменных использована функция ту. Как мы выяснили ранее в этой главе, применение функции ту в подобном случае реализует передачу параметров по значению. Другими словами, их изменение внутри подпрограммы не влияет на фактические параметры. Каким же образом в данном случае осуществляется передача массива и хеш-массива по ссылке? Дело в том, что по значению передаются только ссылки, указывающие на фактические параметры: массив @someiist и хеш-массив %somehash. Используя операции разыменования внутри подпрограммы, мы получаем доступ непосредственно к массиву @someiist и хеш-массиву %somehash, и изменяем их элементы. В результате выполнения данного сценария будет выведено:

начальные значения:

@somelist=1 2 3

$somehash{one}=5 $somehash{three}=20 $somehash{two}=15

итоговые значения:

@somelist=2 4 6

$somehash{one}=10 $somehash{three)=40 $somehash{two}=30


Глава 1

В каких случаях функцию local нельзя заменить функцией ту

В следующих случаях функция local () является незаменимой. П Присваивание временного значения глобальной переменной. В первую очередь это относится к некоторым предопределенным глобальным переменным, таким как $ARGV, $_ и т. д. Рассмотрим пример.

#!/usr/bin/perl $/ = under"; @ARGV = ("а"); $_ = <>;

print "Первое значение области ввода \$_= ", split,"\п"; {

local 0ARGV = ("аа"); local $_ = <>;

print "Второе значение области ввода \$_= ", split,"\п"; }

{

local 8ARGV = ("ааа"); local $_ = <>;

@fields = split;

print "Третье значение области ввода \$_= ", split, "\n";

}

print "Восстановленное значение области ввода \$_= ", split,"\n";

Пусть имеются три файла

"а": "аа": "ааа":

1111 1111 1111 2222 2222 2222 3333 3333 3333

аааа bbbb cccc dddd eeee ffff gggg hhhh iiii

В приведенной программе используются специальные глобальные переменные $/, $_ И OARGV.

Специальная переменная $/ содержит значение разделителя входных записей, которым по умолчанию является символ новой строки. Присваивание этой переменной неопределенного значения позволяет при помощи одной операции ввода <> считать весь файл, а не только первую строку.

Специальная переменная $_ используется по умолчанию для сохранения вводимых данных, если в операции ввода соответствующий параметр не указан. Она также используется по умолчанию в качестве аргумента функции split о, если в ней не задана строка, подлежащая разбиению на отдельные строки.

Массив @ARGV содержит аргументы командной строки самой программы. Если при вызове программы ей будет передано имя файла, то оно будет сохранено в массиве @ARGV. Операция ввода о применяется к файлам, переданным в программу в качестве аргументов командной строки, т. е. к файлам, имена которых хранятся в массиве @ARGV. В нашем примере программа вызывается без аргументов, поэтому имя входного файла "а" задается внутри программы прямой записью в массив @ARGV. Первая операция ввода о, следовательно, осуществляется из файла "а". Далее следуют два блока операторов, заключенных в фигурные скобки. В каждом из них при помощи функции local () создаются временные значения для глобальных переменных @ARGV и @_. В первом блоке данные считываются из файла "аа" и сохраняются в качестве временного значения глобальной переменной $_, во втором — из файла "ааа" и также сохраняются в качестве следующего временного значения переменной $_. По выходе из второго блока глобальная переменная $_ восстанавливает свое первоначальное значение. В результате выполнения данной программы будет напечатано:

Первое значение области ввода $_= 11111111llllaaaabbbbcccc Второе значение области ввода $_= 222222222222ddddeeeeffff Третье значение области ввода $_= 3333333333 ; 33gggghhhhiiii Восстановленное значение области ввода $_= lilllllllllllaaaabbbbcccc

  • Создание локального дескриптора файла, каталога или локального псевдонима для функции.

В следующем примере функция local о используется для создания локального дескриптора файла внутри блока операторов.

#!/usr/bin/perl

open(FILEHANDLE,">b");

print FILEHANDLE "Новая строка в файл 'b'\n";

{

local *FILEHANDLE;

open(FILEHANDLE,">bb");

print FILEHANDLE "Новая строка в файл 'bb'\n";

close FILEHANDLE; }

{

local *FILEHANDLE;

open(FILEHANDLE,">bbb");

print FILEHANDLE "Новая строка в файл 'bbb'\n" ;

close FILEHANDLE; > J

print FILEHANDLE "Еще одна строка в файл 'b'\n"; close FILEHANDLE;

В результате выполнения данного сценария в текущем каталоге будут созданы файлы:

"b":

Новая строка в файл 'b'

Еще одна строка в файл 'b'

"bb":

Новая строка в файл 'bb'

"bbb":

Новая строка в файл 'bbb'

Заметьте, что во время выполнения операций с файлами "bb" и "bbb" файл "Ь" остается открытым.

Аналогичным образом может быть определено локальное имя для функции.

#!/usr/bin/perl

# функция NumberOfArgs() возвращает число своих параметров sub NumberOfArgs {

return $#_ + 1;

) ' . . ' print "NumberOfArgs: число параметров=", NumberOfArgs(1,2,3,4),"\n"; {

local *Numbers = *NumberOf Args;

print "Numbers: число параметров=", Numbers (1, 2, 3} , "\n"; } {

local *N = \SNumberOfArgs;

print "N: число параметров=", N(1,2), "\n"; }

Результат выполнения:

NumberOfArgs: число параметров=4 Numbers: число параметров=3 N: число параметров=2

  • Временное изменение элемента массива или хеш-массива.

В следующем примере внутри блока операторов временно изменяется значение одного элемента глобального хеш-массива %ENV, содержащего значение переменной $РАТН, входящей в состав среды интерпретатора UNIX shell.

tt!/usr/bin/perl

print "значение переменной среды \$РАТН:\n$ENV{PATH}\n"; {

local $ENV{PATH} = "/home/mike/bin"; I -print "временное значение переменной среды \$РАТН: $ENV{PATH}\n";

}

print "прежнее значение переменной среды \$РАТН:\n$ENV{PATH}\n";

Результат будет выведен в следующем виде:

значение переменной среды $РАТН:

/sbin: /usr/sbin: /usr/bin: /bin: /usr/XHR6/bin: /usr/local/ bin: /opt/bin

временное значение переменной среды $РАТН: /home/mike/bin

прежнее значение переменной среды $РАТН:

/sbin: /usr/sbin: /usr/bin: /bin: /usr/XHR6/bin: /usr/local/bin: /opt/bin


Глава 1

Прототипы

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

sub func ($$) {

1

определяет функцию func о с двумя скалярными аргументами. Символы

для обозначения типа аргумента приведены в табл. 11.1.

Таблица 11.1. Символы, используемые в прототипах для задания типа аргумента

Символ | Тип данных
$ | Скаляр
@ | Массив
% | Ассоциативный массив
& | Анонимная подпрограмма
* | Тип typeglob

Запись вида \char, где char — один из символов табл. 11.1, обозначает что при вызове подпрограммы имя фактического параметра должно обязательно начинаться с символа char. В этом случае в подпрограмму через массив параметров @_ передается ссылка на фактический параметр, указанный при ее вызове. Обязательные параметры в прототипе отделяются от необязательных точкой с запятой.

В табл. 11.2 в качестве примера приведены объявления пользовательских функции nybud,itin(), синтаксис которых соответствует синтаксису встроенных функций buil tin ().

Таблица 11.2. Примеры прототипов

Объявление | Обращение к функции
sub mylink ($$) | mylink $old, $new
sub myvec ($$$) | myvec $var, $offset, 1
sub myindex ($$;$) | myindex Sgetstring, "substr"
sub mysyswrite ($$$;$) | mysyswrite $buf, 0, length ($buf) - $off, vOf f
sub myreverse (@) | myreverse $a, $b, $c
sub my join ($@j | myjoin ":", $a, $b, $c
sub mypop (\@) | mypop garray
sub mysplice (\@$$@) | mysplice Sarray, @array, 0, @pushme
sub mykeys (\%) | mykeys %{$hashref}
sub myopen (*;$) | myopen HANDLE, $name
sub mypipe (**) | mypipe READER, WRITER
sub mygrep (s@) | mygrep { /pattern/ } $a, $b, $c
sub myrand ($) | myrand 42
sub mytime () | mytime

Следует иметь в виду, что проверка синтаксиса, задаваемого при помощи прототипа, не осуществляется, если подпрограмма вызвана с использованием префикса &: ssubname.


Глава 1

Рекурсивные подпрограммы

Язык Perl допускает, чтобы подпрограмма вызывала саму себя. Такая подпрограмма называется рекурсивной. При написании рекурсивных подпрограмм следует иметь в виду, что все переменные, значения которых изменяются внутри подпрограммы, должны быть локальными, т. е. объявленными при помощи функций my () или local (). В этом случае при каждом вызове подпрограммы создается Новая копия переменной. Это позволяет избежать неопределенности и замещения текущего значения переменной ее значением из следующего вызова подпрограммы.

Рекурсивные подпрограммы следует применять осторожно. Многие алгоритмы, являющиеся по сути итеративными, можно реализовать при помощи рекурсивной подпрограммы. Однако такая подпрограмма окажется неэффективной по времени выполнения и потребляемым ресурсам памяти. Вместе с тем, существуют задачи, решить которые можно только при помощи рекурсивных алгоритмов. В этом случае применение рекурсивных подпрограмм является не только вполне оправданным, но и необходимым. Одной из таких задач, которую операционная система решает постоянно, является рекурсивный просмотр дерева каталогов. Рассмотрим пример рекурсивной Peri-подпрограммы tree (), которая делает то же самое: просматривает дерево каталогов, начиная с каталога, заданного параметром подпрограммы, и выводит список файлов, содержащихся в каждом подкаталоге.

sub tree {

local (*ROOT);

my ($root)=$_[01;

opendi.r ROOT, $root;

my (@filelist) = readdir ROOT;

closedir ROOT;

for $x (gfilelist) {

if ($x ne "." and $x ne ".."){ $x=$root."/".$x; print " $x\n" if (-f $x); if (-d $x) {

print "$x:\n";

tree($x); } } } }

Здесь использованы встроенные подпрограммы Perl opendiro, ciosediro, readdiro, применяемые соответственно для открытия каталога, его закрытия и чтения содержимого. Подпрограмма tree о рекурсивно просматривает каталог, переданный ей в качестве параметра, и выводит имена вложенных подкаталогов и содержащихся в них файлов в следующем виде:

/home/httpd/cgi-bin: /home/httpd/html:

/home/httpd/html/index.html /home/httpd/html/manual:

/home/httpd/html/manual/LICENSE

/home/httpd/html/manual/bind. html

/home/httpd/html/manual/cgi_path.html


Глава 1

Вопросы и Упражнения

Вопросы для самоконтроля

1. Какие формы обращения к подпрограмме вы знаете?

2. Что такое область видимости переменной?

3. Как ограничить область видимости переменной?

4. Чем отличаются переменные, объявленные при помощи функции ту о от переменных, объявленных при помощи функции local () ?

5. Каким образом данные передаются в подпрограмму и из подпрограммы?

6. Что такое передача параметров по ссылке и по значению?

7. Какой тип данных называется typegiob?

8. Как осуществить передачу по ссылке параметра-массива?

9. В каких случаях функция local о не может быть заменена функцией my ()?

10. Что такое прототип?

11. Какие значения будут иметь переменные $х, @list1, @list2 после выполнения программы

#!/usr/bin/perl

$х = 0;

@list1 = (1, 2, 3);

@list2 = funcO ; sub func {

local ($x);

$x = 1;

@list1 = (4, 5, 6); ___

} "X

\

Упражнения

1. Напишите подпрограмму, которая выводит пронумерованный список своих аргументов.

2. Напишите подпрограмму, которая выводит пронумерованный список своих аргументов в обратном порядке.

3. Напишите подпрограмму, которая подсчитывает число символов из стандартного ввода и выводит результат.

4. Напишите подпрограмму, которая выводит свои параметры-массивы в обратном порядке по элементам.

5. Напишите подпрограмму, которая для двух своих параметров-массивов осуществляет взаимный обмен элементов с одинаковыми индексами.

6. Одной из известных задач, для решения которых применяется рекурсия, является задача о Ханойских башнях.

7. На плоскости установлены три стержня: а, b, с (рис. 11.1).

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

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

Перенос диска со стержня а на стержень с.

Рис 11.1. Задача о Ханойских башнях


Глава 1

Часть 12. Пакеты, библиотеки, модули


Глава 1

Пакеты

В части 11 мы упомянули о том, что область действия переменных ограничена пакетом. Рассмотрим этот вопрос более подробно.

Итак, пакет — это способ создания собственного изолированного пространства имен для отдельного отрезка программы. Каждый фрагмент кода Peri-программы относится к некоторому пакету. Объявление

package NAMESPACE;

определяет пакет NAMESPACE. Ключевое слово package является именем встроенной функции, в результате обращения к которой компилятору предписывается использовать новое пространство имен. Область действия объявления пакета определяется аналогично области видимости локальных переменных, объявленных при помощи функций ту () или local (). Она распространяется либо до следующего объявления пакета, либо до конца одной из таких единиц программы:

  • Подпрограммы;
  • блока операторов, заключенного в фигурные скобки;
  • строки, переданной на выполнение функции eval ();
  • файла.

Область действия зависит от того, в каком месте вызвана для объявления пакета функция package о. Все идентификаторы, встретившиеся внутри этой области, принадлежат к пространству имен текущего пакета. Исключение составляют идентификаторы лексических переменных, созданных при помощи функции mу ().

Объявление пакета может быть сделано несколько раз в разных точках программы. Каждое объявление означает переключение на новое пространство имен. По умолчанию предполагается, что основная программа всегда начинается с объявления пакета

package main;

Таким образом, те переменные, которые мы называем в языке Perl глобальными, в действительности представляют собой переменные, чьи идентификаторы по умолчанию принадлежат пакету main.

К переменной из другого пакета можно обратиться, указав перед ее именем префикс, состоящий из имени этого пакета, за которым следуют два двоеточия: $PackageName: :name. Такие имена условимся называть квалифицированными именами. Если имя пакета отсутствует, предполагается имя main, т. е. записи $: :var и $main: :var обозначают одну и ту же переменную.

Специальная лексема _PACKAGE_ служит для обозначения имени текущего пакета.

#!/usr/bin/perl

$x=_PACKAGE_;

print "package $x:\n";

print "\$x= $x\n";

print "\$two::x= $two::x\n";

print "\$three::x= $three::x\n";

eval 'package two; $x=_PACKAGE_;
print " package $x:\n"; print "\$x= $x\n"; print "\$main::x=
$main::x\n"; print "\$three::x= $three::x\n";';

print "package $x:\n"; print "\$x= $x\n";

package three;

$x=_PACKAGE_;

print "package $x:\n";

print "\$x= $x\n";

print "\$main::x= $main::x\n";

print "\$two::x= $two::x\n";

package main;

print "package $x:\n";

print "\$x= $x\n";

print "\$two::x= $two::x\n";

print "\$three::x= $three::x\n";

В результате выполнения будут выведены следующие значения:

package main: $х= main

$two::x= $three::x=

package two: $x= two

$main::x= main $three::x=

package main: $x= main

package three: $x= three $main: :x= main $two::x= two

package main: $x= main $two::x= two $three::x= three

В данном примере используются три пакета, каждый со своим пространством имен: main, two, three. В каждом пакете определена переменная $х, значение которой совпадает с именем пакета. С пакетом main связаны следующие отрезки программы:

  • от начала программы до вызова функции evai ();
  • после вызова функции evai о до объявления пакета package three;
  • после явного объявления пакета package main до конца файла, содержащего данную программу.

Для выражения, выполняемого функцией evai (), определено собственное пространство имен two. Оно действует только в пределах этого выражения. Если бы внутри функции evai о не был определен собственный пакет two, все действия внутри нее были связаны с пакетом, в котором функция evai () была скомпилирована, т. е. с пакетом main.

С пакетом three связана часть программы от объявления package three до объявления package main.

В каждом пакете происходит обращение к переменным из двух других пакетов при помощи указания соответствующего префикса имени переменной.

Компилятор создает для каждого пакета отдельное пространство имен. Переменным $х из разных пакетов присваиваются их значения по мере выполнения соответствующего кода программы. Вот почему при первом обращении из пакета main к переменным two: :$x и $three: :x их значения еще не определены.

Таблицы символов.

С каждым пакетом связана таблица символов. Она представляет собой хеш-массив, имя которого образовано из имени пакета, за которым следуют два двоеточия. Например, таблица символов пакета main хранится в хеш-массиве %main::. Ключами этого хеш-массива являются идентификаторы переменных, определенных в пакете, значениями — значения типа typeglob, указывающие на гнездо, состоящее из одноименных переменных разных типов: скаляр, массив, хеш-массив, функция, дескриптор файла или каталога.

Тип typeglob, с которым мы уже сталкивались в главе И — это внутренний тип данных языка Perl, который используется для того, чтобы при помощи одной переменной типа typeglob сослаться на все одноименные переменные разных типов. Признаком типа typeglob является символ "*". Если переменной типа typeglob присвоить значение другой переменной типа

typeglob:

*у = *х;

то для всех переменных с именем х: $х, @х, %х, &х, будут созданы псевдонимы $у, @у, %у, &у соответственно. Можно создать псевдоним только для переменной определенного типа, например, для скалярной:

*у = \$х;

Ключами в хеш-массиве таблицы символов являются все идентификаторы, определенные в пакете. Поэтому можно получить данные о переменных всех типов, определенных в пакете, проверяя значения элементов этого хеш-массива. Например, чтобы вывести имена всех переменных, определенных в пакете main, можно использовать следующий код.

* !/usr/bin/perl

my ($key, $item) ;

print "Таблица символов пакета main:\n";

for $key (sort keys %main::) {

local *myglob = $main::{$key};

print "определен скаляр \$$key = $myglob\n" if defined $myglob; i^'.Med @myglob) {

опт "определен массив \@$key :\n"; for $item (0..$#myglob) {

p> ••••••• \$$key [$item] = $myglob[$item] \n";

} }

if (defined %myglob) {

print "определен хеш-массив \%$key :\n";

for $item (sort keys %myglob) {

print "\$$key {$item} = $myglob{$item}\n";

} } print "определена функция $key()\n" if defined Smyglob;

}

При помощи типа typegiob можно создавать скалярные псевдоконстанты. Например, после присваивания

*Р1 = \3.14159265358979;

выражение $PI обозначает операцию разыменования ссылки на константу. Его значением является значение самой константы 3.14159265358979. Значение $PI нельзя изменить, так как это означало бы попытку изменить константу.

В таблицу символов пакета, отличного от main, входят только идентификаторы, начинающиеся с буквы или символа подчеркивания. Все остальные идентификаторы относятся к пакету main. Кроме того, к нему относятся следующие начинающиеся с буквы идентификаторы: STDIN, STDOUT, STDERR, ARGV, ARGVOUT, ENV, INC, sic. Например, при обращении внутри некоторого пакета pack к хеш-массиву %ENV подразумевается специальный хеш-массив %ENV основного пакета main, даже если имя main не используется в качестве префикса для обозначения принадлежности идентификатора ENV.

Конструктор и деструктор пакета ВEGIN и END

Конструктором в объектно-ориентированном программировании называется специальная подпрограмма, предназначенная для создания объекта. Деструктором называется подпрограмма, вызываемая для выполнения завершающих действий, связанных с ликвидацией объекта: закрытие файлов, вывод сообщений и т. д.

Для создания пакета, как мы знаем, требуется только его объявление (в том числе, предполагаемое по умолчанию объявление package main). Вместе с тем, существуют специальные подпрограммы, выполняющие функции инициализации и завершения пакета. По аналогии их можно назвать конструкторами и деструкторами пакета, хотя никаких пакетов они не создают и не удаляют. Это подпрограммы BEGIN и END. При описании этих подпрограмм ключевое слово sub, необходимое при объявлении обычной подпрограммы, можно опустить. Таким образом, /синтаксис, подпрограмм BEGIN, END имеет вид:

BEGIN (block) END {block}

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

print "Привет!";

то он напечатает ее только после того, как обнаружит во входном потоке признак конца файла (например, комбинацию <Ctrl>+<D>). Если же в интерактивном режиме определить конструктор пакета

BEGIN {print "Привет!"};

то вывод строки "Привет!" будет осуществлен немедленно. Это свойство конструктора можно использовать, чтобы в начале пакета определять или импортировать имена из других пакетов. Затем эти имена будут влиять на процесс компиляции оставшейся части пакета.

Можно определить несколько блоков BEGIN внутри файла, они будут выполняться один за другим в порядке определения.

\ Подпрограмма END выполняется настолько поздно, насколько это возможно, т. е. при завершении работы интерпретатора. Можно указать несколько блоков END, при этом они будут выполняться в порядке, обратном определению.

END {

print "Завершаем работу, до свидания\п";

} . . BEGIN {

print "Привет, начинаем работу\n"; }

print "Это тело программы\n"; BEGIN {

print "Еще один блок BEGIN после блока END\n"; }

Здесь сознательно выбран не совсем естественный порядок следования конструкторов и деструкторов BEGIN и END в тексте программы, чтобы подчеркнуть, в каком порядке они будут вызываться. Вывод выглядит так:

Привет, начинаем работу

Еще один блок BEGIN после блока END

Это тело программы

Завершаем работу, до свидания

Автозагрузка

При попытке обратиться к функции из некоторого пакета, которая в нем не определена, интерпретатор завершает работу с выдачей сообщения об ошибке. Если же в этом пакете определить функцию с именем AUTOLOAD, то при вызове из пакета несуществующей функции вместо нее будет вызвана функция AUTOLOAD с параметрами, переданными при вызове несуществующей функции. При этом интерпретатор peri продолжает выполнение программы. Полное имя несуществующей функции с указанием имени пакета сохраняется в переменной $AUTOLOAD из того же пакета, что и функция AUTOLOAD. Например, для основного пакета main можно определить функцию AUTOLOAD, как в следующем примере.

#!/usr/bin/perl sub AUTOLOAD {

print "Функция $AUTOLOAD не определена\п"; } print "Начало работы\п";

f();

print "Конец работы\n";

Функция f (), в отличие от функции AUTOLOAD, не определена в пакете main, поэтому в результате выполнения данной программы будут выведены сообщения:

Начало работы

Функция main::f не определена

Конец работы

Этот пример достаточно тривиальный, но он дает представление об использовании функции AUTOLOAD. В состав дистрибутива Perl входят стандартные модули, многие из которых содержат собственные, достаточно сложные определения функции AUTOLOAD, которые можно рассмотреть в качестве более сложного примера.


Глава 1

Библиотеки

Подпрограммы, как мы отметили в цачале главы, служат для объединения группы операторов с целью их повторного использования. Следующим шагом является объединение группы подпрограмм и их сохранение в отдельном файле для последующего использования другими программами. Для реализации этой задачи в языке Perl имеются два механизма: библиотеки и модули.

Исторически первым средством являются библиотеки, появившиеся в версии Perl 4. Библиотека представляет собой пакет, областью действия которого является отдельный файл. Иными словами, библиотека — это файл, содержащий в качестве первой строки объявление пакета.

package package_name;

Имя файла библиотеки обычно имеет расширение pi.

Замечание

После появления модулей (смотрите здесь ) термин "библиотека" часто используют в широком смысле для обозначения всего множества модулей в составе Perl, содержащего разнообразные коллекции подпрограмм. Если не оговорено противное, мы будем использовать слово "библиотека" для обозначения файла библиотеки в соответствии с версией Perl 4.

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

Ключевое слово require служит для обозначения встроенной функции Perl. Фактически обращение к функции require () используется в качестве директивы компилятора. Поэтому дальше мы будем использовать применительно к ключевому слову require оба термина: "функция" и "директива-компилятора". Выясним, какой смысл имеет эта директива.

Функция require()

require [EXPR]

загружает внешние функции из библиотеки Perl во время выполнения. Она используется для того, чтобы сделать библиотеку подпрограмм доступной для любой Peri-программы.

Если параметр EXPR отсутствует, вместо него используется специальная переменная $_.

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

require 5.005;

Более ранние версии Perl вызовут немедленное завершение интерпретатора с выдачей сообщения об ошибке.

Если параметр является строкой, функция require включает в основную программу библиотечный файл, задаваемый параметром EXPR. Логика работы функции require соответствует следующему коду:

sub require {

my($filename) = @_; return 1 if $INC{$filename}; . my($realfilename,$result); ITER: {

foreach $prefix (@INC) {

$realfilename = "$prefix/$filename"; if (-f $realfilename) {

$result = do $realfilename; last ITER; } }

die "Can't find $filename in \@INC"; }

die $@ if $@;

die "$filename did not return true value" unless $result; $INC{$filename} = $realfilename; return $result; }

Замечание
Специальный встроенный массив OINC содержит имена каталогов, в которых следует искать сценарии Peri, подлежащие выполнению в конструкциях do filename, require или use. Первоначально содержит:

• имена каталогов, переданные при запуске интерпретатору peri в качестве параметра ключа -i;

• имена библиотечных каталогов по умолчанию (зависят от операционной системы);

• символическое обозначение текущего каталога ".".

Специальный встроенный хеш-массив % INC содержит по одному элементу для каждого файла, включенного при помощи do или require. Ключом является имя файла в том виде, как оно указано в качестве аргумента функций do или require, а значением — его полное маршрутное имя.

Встретив директиву require myfile, интерпретатор peri просматривает специальный хеш-массив %INC, проверяя, не был ли файл (rayfile уже включен ранее при помощи функций require или do. Если да, то выполнение функции require завершается. Таким образом файл под одним именем может быть включен только один раз. В противном случае интерпретатор просматривает каталоги, имена которых содержатся в специальном массиве @INC, в поисках файла ту file. Если файл найден, он выполняется, иначе директива require завершает выполнение с выдачей сообщения об ошибке:

Can't find myfile in @INC

Замечание

Замечание об использовании имени файла В Директиве require EXPR. Обычно имена библиотечных файлов имеют суффикс ".pl", например, myfile.pl. Интерпретатор peri воспринимает точку"." в качестве знака операции конкатенации двух строк "myfile" и "pi" и пытается найти файл myfiiepl. Во избежание подобных ошибок имя файла в директиве require следует заключать в кавычки:

require "myfile.pl";

Если аргумент EXPR является словом без суффиксов, не заключенным в кавычки, то директива require предполагает, что оно имеет суффикс ".рl", чтобы облегчить загрузку стандартных модулей, имеющих расширение " .рl".

Создание и подключение библиотечного файла

Для создания собственной библиотеки следует выполнить следующие шаги. П Создать каталог для хранения библиотечных файлов.

  • Сохранить наборы подпрограмм в отдельных файлах-библиотеках. Переместить библиотечные файлы в специально созданный для них каталог.
  • В конец каждого библиотечного файла поместить строку " i;". Смысл этого действия заключается в следующем. Как видно из приведенного текста, включение библиотечного файла в основную программу осуществляется через его выполнение функцией do:

$result = do $realfilename;

Значение $resuit, возвращаемое функцией require, должно быть ненулевым, что является признаком успешного выполнения кода инициализации. Простейший способ получить ненулевое значение — добавление в конец каждого библиотечного файла строки "1,-".

  • В основной программе использовать директиву require, указав в качестве ее аргументов имена требуемых библиотечных файлов.
  • Добавить в массив @INC имя каталога, содержащего библиотечные файлы, либо при запуске основной программы передать это имя интерпретатору peri при помощи ключа -i. \ '

Создадим библиотечный файл myiib.pl и поместим его в каталог /usr/temp/periiib. Файл myiib.pl содержит единственную подпрограмму Numof Args (), которая выводит число аргументов, переданных ей при вызове.

# библиотечный файл /usr/temp/perllib/mylib.pl sub NumOfArgs {

return $#_+!; }

I;

Создадим файл основной программы mymain.pi:

#!/usr/bin/perl

unshift (@INC, "/usr/temp/perllib");

require "mylib.pl";

print "Число аргументов=", NumOfArgs(1,2,3,4), "\n";

В результате выполнения файла mymain.pi будет выведена строка

Число аргументов=4

Обратите внимание на выполнение всех шагов, необходимых для создания и подключения библиотеки.


Глава 1

Модули

Дальнейшим развитием понятия библиотеки явилось понятие модуля, возникшее в версии Perl 5. Модуль представляет собой библиотеку подпрограмм, обладающую дополнительными свойствами по сравнению с библиотеками Perl 4. Он позволяет управлять экспортом своих имен в другие программы, объявляя, какие из них экспортируются по умолчанию, а какие должны быть явно указаны в соответствующем операторе вызывающей программы.

Под экспортом мы здесь понимаем предоставление возможности другим модулям импортировать символы из пространства имен данного модуля. Соответственно под импортом мы понимаем включение в собственное пространство имен символов, экспортируемых другим модулем.

Для целей управления экспортом каждый модуль должен располагать методом import о и определить специальные массивы @EXPORT и @EXPORT_OK.

(Понятие "метод" используется в объектно-ориентированном программировании, которое обсуждается в части 13 )

Вызывающая программа обращается для импорта символов^ к методу import () экспортирующего модуля.

Специальный массив @ EXPORT содержит идентификаторы, экспортируемые по умолчанию.

Специальный массив @EXPORT_OK содержит идентификаторы, которые будут экспортироваться только в том случае, если они явно указаны в списке импорта вызывающей программы.

С появлением модулей появилась новая директива для их подключения к основной программе. Эта директива реализуется функцией use ().

Функция use ()

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

ФУНКЦИЯ use () use Module [LIST] use VERSION

служит для загрузки модуля во время компиляции.

Директива use автоматически экспортирует имена функций и переменных в основное пространство имен текущего пакета. Для этого она вызывает метод import () импортируемого модуля. Механизм экспорта имен устроен таким образом, что каждый экспортирующий модуль должен иметь свой метод import (), который используется программой, импортирующей имена. Метод import () должен быть определен в самом экспортирующем модуле или наследован у модуля Exporter. Большинство модулей не имеют своих собственных методов import (), вместо этого они экспортируют его из модуля

Exporter.

Логику работы директивы use можно описать одной строкой:

BEGIN { require Module; import Module LIST; }

Здесь значением параметра Module должно быть слово без суффиксов, не заключенное в кавычки.

Если первый аргумент директивы use является числом, он обозначает номер версии интерпретатора peri. Если номер версии текущего интерпретатора peri меньше, чем значение VERSION, интерпретатор выводит сообщение об ошибке и завершает работу.

Конструктор пакета BEGIN вызывает немедленное выполнение подпрограммы require {) и метода import о до завершения компиляции оставшейся части файла.

Выше мы рассмотрели логику работы функции require(). Она загружаете память файл Module.pm, выполняя его при помощи функции dot). Затем метод import() модуля Module.pm импортирует в вызывающую программу имена, определенные в Module.pm, в соответствии со списком LIST.

Если список импорта LIST отсутствует, из Module будут импортированы те имена, которые перечислены в специальном массиве @EXPORT, определенном В самом Module.

Если список импорта задан, то в вызывающую программу из модуля Module будут импортированы только имена, содержащиеся в списке LIST.

Создание и подключение модуля

Для создания модуля MyModuie следует создать пакет и сохранить его в файле MyModuie. pm. Расширение .pm является признаком того, что данный файл является модулем Perl.

В следующем примере мы создадим собственный модуль MyModuie, содержащий одну функцию MyArgs о , одну скалярную переменную $MyArgs, один массив емуАгдз и один хеш-массив $MуАrgs. Затем создадим файл основной программы MyMain.pi, экспортирующий модуль MyModuie, используя директиву use.

Файл МОДУЛЯ MyModuie. pm:

package MyModuie;

require Exporter;

@ISA = 'qw(Exporter) ;

SEXPORT = qw(MyArgs) ;

@EXPORT_OK = qw($MyArgs @MyArgs %MyArgs);

sub MyArgs {

my ($x, $i);

SMyArgs = @_;

$MyArgs = $#MyArgs + 1;

foreach $x (@MyArgs) {

$MyArgs{$x}=++$i ; }

}

Файл основной вызывающей программы MyMain.pi: ^

tt!/usr/bin/perl

use MyModuie qw(:DEFAULT $MyArgs @MyArgs %MyArgs);

MyArgs one, two, three, four;

print "число аргументов=$МуАгдз\n";

print "массив аргументов: @MyArgs\n";

print "хеш-массив аргументов:\n";

foreach $k (keys %MyArgs) {

print "\$MyArgs{$k}=$MyArgs{$k} "; }

Первые пять строк файла MyModuie.pm являются стандартными для определения модуля Perl. Их можно использовать в качестве образца при создании собственных модулей.

Первая строка служит определением пакета.

Вторая строка осуществляет включение встроенного модуля Exporter. Так предоставляется возможность наследовать метод import, реализованный в этом модуле, и использовать стандартные соглашения для задания списка импорта в вызывающей программе.

Третья строка определяет массив @ISA, состоящий из одного элемента, содержащего название пакета Exporter. С каждым пакетом ассоциируется свой массив @ISA, включающий имена других пакетов, представляющих классы. Иногда интерпретатор встречает обращение к методу, не определенному в текущем пакете. Он ищет этот метод, просматривая пакеты, определенные в массиве @ISA текущего пакета. Таким образом в языке Perl реализован механизм наследования.

В четвертой и пятой строках определяются имена, которые будут экспортироваться за пределы модуля. Специальный массив OEXPORT содержит имена, экспортируемые по умолчанию. В четвертой строке указывается, что из данного МОДУЛЯ ПО умолчанию будет Экспортировано ИМЯ фуНКЦИИ MyArgs.

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

$MyArgs, Массива @MyArgs из ассоциативного массива %MyArgs.

Функция MyArgs подсчитывает число своих аргументов и запоминает его в переменной $MyASrgs. Затем она помещает аргументы в массив @MyArgs и формирует ассоциативный массив %MyArgs, в котором ключами являются имена аргументов функции MyArgs, а значениями — их порядковые номера.

К основной программе MyMain.pl модуль MyModuie подключается при помощи директивы use. Директива use содержит список импорта

qw(:DEFAULT $MyArgs @MyArgs %MyArgs)

Обычно список импорта включает в себя имена переменных и функций. Кроме того, он может содержать некоторые управляющие им спецификации. Спецификация : DEFAULT означает включение в список импорта всех элементов специального массива @EXPORT. В нашем случае это значит, что в список импорта будет добавлено имя функции MyArgs, содержащееся в списке @EXPORT. Кроме того, список импорта явно содержит имена $MyArgs, @MyArgs и %MyArgs. Экспорт этих имен по явному запросу вызывающей программы разрешен модулем MyModuie путем их включения в список

@EXPORT_OK.

В результате выполнения основной программы MyMain.pi будет получен вывод:

число аргументов=4

массив аргументов: one two three four хеш-массив аргументов: . $MyArgs{one}=1 $MyArgs{three}=3 $MyArgs{two}=2 $MyArgs{four}=4

Функция no ()

Существует функция no (), противоположная по своему назначению функции use о и также выполняющая роль директивы компилятора

no Module LIST

Директива по отменяет действия, связанные с импортом, осуществленные ранее директивой use, вызывая метод unimport () Module LIST.

Стандартные модули Perl

В данном разделе мы рассмотрели вопросы, связанные с созданием модулей. В состав дистрибутива Perl входит большой набор стандартных модулей, предназначенных для выполнения определенных функций) Помимо них, существует огромная коллекция модулей, разработанных программистами всего мира, известная как коллекция модулей CPAN (Cbmprehensive Perl Archive Network). Ее копия поддерживается в сети Internet на многих анонимных ftp-серверах. В качестве отправной точки можно обратиться по адресу http://www.perl.com/CPAN/modtiles/ . Список стандартных модулей и категорий модулей CPAN приведен в приложении 2. Здесь же мы в заключение рассмотрим специальный вид стандартных модулей — прагма-библиотеки.

Прагма-библиотеки

Многие языки программирования позволяют управлять процессом компиляции посредством директив компилятора. В языке Perl эта возможность реализована при помощи так называемых прагма-библиотек. В современной терминологии, связанной с программированием, слово "pragma" используется для обозначения понятия, смысл которого в русском языке выражается сочетанием "директива компилятора". В языке Perl термин "pragma" обозначает модуль, содержащий коллекцию подпрограмм, используемых на этапе компиляции. Его назначение — передать компилятору информацию о том, как модифицировать процесс компиляции. Поскольку сочетание "библиотека директив компилятора" звучит несколько тяжеловато, мы используем для обозначения таких модулей название "прагма-библиотека".

Как и остальные модули, прагма-библиотека подключается к основной программе при помощи директивы use и выполняет функцию директивы компилятора. Область действия большинства таких директив ограничена, как правило, блоком, в котором они встречаются. Для отмены соответствующей директивы используется функция по.

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

#!/usr/bin/perl

print "Арифметика с плавающей точкой: 2/3= ", 2/3, "\n";

use integer;

print "Целочисленная арифметика: 2/3= ",2/3, "\n";

no integer;

print "Возврат к арифметике с плавающей точкой: 2/3= ", 2/3, "\n";

В результате выполнения данного примера будет получен вывод

Арифметика с плавающей точкой: 2/3= 0.666666666666667

Целочисленная арифметика: 2/3=0

Возврат к арифметике с плавающей точкой: 2/3= 0.666666666666667

В дистрибутивный комплект Perl входит стандартный набор прагма-библиотек. Некоторые из них представлены в табл. 12.1.

Таблица 12.1. Некоторые прагма-библиотеки

Прагма-библиотека | Назначение
diagnostics | Включает режим диагностики с выдачей подробных сообщений
integer | Применение целочисленной арифметики вместо арифметики с плавающей точкой
lib | Позволяет добавлять элементы в специальный массив @INC во время компиляции
overload | Режим переопределения операций Perl, например, директива package Number; use overload "+" => \&add; определяет функцию Number : : add ( ) в качестве операции сложения
sigtrap | Директива, позволяющая управлять обработкой сигналов в UNIX
strict | Режим ограниченного использования "опасных" конструкций Perl use strict "refs"; генерирует ошибку выполнения при использовании символических ссылок use strict "vars"; генерирует ошибку компиляции при попытке обращения к переменной, которая не была объявлена при помощи директивы use vars, локализована при помощи функции ту() или не является квалифицированным именем use strict "subs"; генерирует ошибку компиляции при попытке использовать идентификатор, который не заключен в кавычки, не имеет префикса типа и не является именем подпрограммы, за исключением тех случаев, когда он заключен в фигурные скобки, или стоит слева от символа => use strict; эквивалентно заданию всех трех рассмотренных выше ограничений
subs | Служит для предварительного объявления подпрограмм, указанных в списке: use subs qw(subl sub2 sub3);
vars | Служит для предварительного объявления переменных, указанных в списке use vars qw($scal @list %hash) ; после чего их можно использовать при включенной директиве use strict, не опасаясь возникновения ошибки компиляции


Глава 1

Вопросы и Упражнения

Вопросы для самоконтроля

1. Что такое пакет?

2. Верно ли, что пакет должен всегда занимать отдельный файл?

3. Что такое таблица символов?

4. Сколько таблиц символов могут быть связаны с одним файлом, функцией, блоком операторов, заключенным в фигурные скобки?

5. Какие функции выполняют конструктор и деструктор пакета BEGIN и END?

6. Как определить имя текущего пакета?

7. Для чего нужна функция AUTOLOAD?

8. Что такое библиотека?

9. Назовите действия, необходимые для создания библиотечного файла.

10. Что такое модуль? В чем разница между модулем и библиотекой?

11. Объясните назначение массивов SEXPORT и @EXPORT_OK.

12. Чем похожи и чем отличаются функции use () и require () ?

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

14. Объясните назначение функции по ().

15. Что такое прагма-библиотека?

Упражнения

1. Напишите программу, которая выводит таблицу символов для пакета, заданного ее аргументом.

2. Создайте два модуля. Модуль Modi должен содержать подпрограмму reverselist о, которая переставляет в обратном порядке элементы в массиве, переданном ей в качестве параметра. Модуль Mod2 должен содержать массив @listt. Используйте оба модуля в основной программе, которая при помощи подпрограммы reverseiist о переставляет элементы в массиве @list.


Глава 1

Часть 13. Объектно-ориентированное программирование в языке Perl


Глава 1

Классы и объекты

Эта глава не предназначена для того, чтобы изучать по ней основы объектно-ориентированного программирования (ООП). Мы лишь хотим дать представление о том, как основные идеи ООП реализованы в языке Perl. Начнем с краткого обзора этих идей. В основе ООП лежат понятия класса и объекта. Эти понятия тесно связаны друг с другом.

Класс представляет собой сочетание структуры данных и тех действий, которые можно выполнить над этими данными. Данные называют свойствами, а действия — методами. Совмещение в классе структуры данных и действий над ними называют инкапсуляцией.

Объект является экземпляром класса. Свойства объекта обусловлены его принадлежностью к определенному классу. Понятия "объект" и "класс" отражают два различных вида иерархий, которые можно обнаружить в любой достаточно сложной системе. Рассмотрим пример.

Персональный компьютер является сложной системой. Жесткий диск — составная часть этой системы. Другими ее частями являются центральный процессор, память и т. д. Можно сказать, что жесткий диск — это часть структурной иерархии под названием "персональный компьютер". С другой стороны, жесткий диск является абстракцией, обобщением свойств, присущих всем жестким дискам, и этим он отличается, например, от гибкого диска, рассматриваемого как абстракция, обобщающая свойства всех гибких дисков. В типовой иерархии жесткие диски Quantum — это особый тип жестких дисков, жесткие диски Quantum Fireball EL — особый тип жестких дисков Quantum, жесткие диски Quantum Fireball EL объемом 5,1 Гбайт — особый тип жестких дисков Quantum Fireball EL и т. д.

В данной аналогии абстрактный жесткий диск является базовым классом, жесткие диски фирмы Quantum образуют подкласс класса жестких дисков, жесткие диски Quantum Fireball EL — подкласс класса жестких дисков фирмы Quantum и т. д. Подкласс называют также производным классом или классом-потомком. Для него класс, расположенный выше в иерархии, является базовым, подклассом или родительским классом. В нашем примере объект (конкретный жесткий диск) является составной частью структуры под названием "персональный компьютер", и обладает своими свойствами в силу принадлежности к определенному типу дисков Quantum Fireball EL 5,1 Гбайт, отличающемуся по своим характеристикам от других типов жестких дисков.

Действие, которое можно выполнить над данными, определяет метод. Он представляет собой подпрограмму. Различают методы класса и методы объекта. Последние могут применяться к любому объекту класса. Методы класса, называемые также статическими методами, не зависят от отдельного экземпляра, они применяются к целому классу как к отдельному объекту.

Для создания объектов применяются специальные статические методы, называемые конструкторами.

Для корректного удаления объектов используются специальные методы, называемые деструкторами.

Термин наследование в ООП обозначает способность классов наследовать свойства и методы у своих родительских классов.

Термин полиморфизм в ООП обозначает свойство класса-потомка переопределять методы родительского класса.

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

В этой главе мы рассмотрим, каким образом основные концепции объектно-ориентированного программирования реализованы в языке Perl.

В языке Perl нет специального синтаксиса для описания классов, объектов или методов. Для их реализации используются уже знакомые нам синтаксические конструкции. Класс в Perl представляет собой пакет, объект является ссылкой, а метод — обычной подпрограммой.

В качестве пакета каждый класс имеет собственное изолированное пространство имен и таблицу символов, реализованную в виде хеш-массива. К переменным класса можно обращаться, используя их квалифицированные имена, содержащие в качестве префикса имя класса, за которым следуют два двоеточия, например, $CLASSNAME: :var. Для того чтобы пакет стал классом, в нем нужно определить специальную подпрограмму — конструктор, которая используется для создания отдельных экземпляров класса — объектов (см. здесь).

Объект в Perl представляет собой просто ссылку, но не любую, а связанную с определенным классом. Тип ссылки можно определить при помощи функции ref EXPR, которая, рассматривая свой аргумент как ссылку, возвращает символическое обозначение ее типа. Для встроенных типов Perl используются следующие символические обозначения:

  • REF — ссылка на ссылку;
  • SCALAR — ссылка на скаляр;
  • ARRAY — ссылка на массив;
  • HASH — ссылка на ассоциативный массив;
  • CODE — ссылка на подпрограмму;
  • GLOB — ссылка на переменную типа typegiob.

Для ссылки-объекта функция ref () возвращает имя класса, к которому этот объект принадлежит. Обычная ссылка становится объектом после ее "посвящения" в члены класса при помощи функции

bless REF [, CLASSNAME]

Слово "bless" в английском языке имеет значение "освящать, благословлять". В данном контексте его можно перевести как "посвящать" или "санкционировать". Функция bless REF санкционирует принадлежность субъекта ссылки REF к классу CLASSNAME. Она возвращает ссылку на этот субъект, но уже другого типа — CLASSNAME (напомним, что в главе 9 субъектом ссылки мы условились называть то, на что она указывает, т. е. собственно структуру данных некоторого типа). Она связывает обычную ссылку с именем класса. Если имя класса не задано, то используется имя текущего класса. После выполнения функции bless ок созданному ей объекту можно обращаться, используя его квалифицированное имя $CLASSNAME: :REF. Сказанное иллюстрируется следующим примером.

$h = { }; •

print("тип переменной \$h - ". ref($h), "\n");

bless($h, "MyClass");

print("тип переменной \$h - ". ref($h), "\n");

В результате будет выведен тип переменной $ref до и после вызова функции bless ():

тип переменной $h — HASH тип переменной $h — MyClass

Наследование в Perl отличается от наследования в других объектно-ориентированных языках программирования тем, что наследуются только методы. Наследование данных реализуется программистом самостоятельно. Наследование методов реализовано следующим образом. С каждым пакетом ассоциирован свой специальный массив @ISA, в котором хранится список базовых классов данного пакета. Таким образом, подкласс располагает информацией о своих базовых классах. Если внутри текущего класса встречается обращение к методу, не определенному в самом классе, то интерпретатор в поисках отсутствующего метода просматривает классы в том порядке, в котором они встречаются в массиве @ISA. Затем просматривается предопределенный класс UNIVERSAL. В нем изначально нет никаких явных определений, но автоматически содержатся некоторые общие методы, которые неявно наследуются всеми классами. В этом смысле класс UNIVERSAL можно считать базовым классом всех остальных классов.

Если метод не найден ни в одном из просмотренных классов, они вновь просматриваются в том же порядке в поисках подпрограммы AUTOLOAD (см. здесь). Если таковая обнаружена, она выполняется вместо вызванного несуществующего метода с теми же параметрами. Квалифицированное имя несуществующего метода при этом доступно через переменную

$AUTOLOAD.


Глава 1

Методы

Методы в Perl являются обычными подпрограммами. Начнем их изучение с методов, которые обязательно должны быть определены в каждом классе. Такими методами являются конструкторы. Знакомство с ними позволит лучше понять, каким способом в языке Perl представляются объекты.

Конструкторы

Конструктор — это просто подпрограмма, возвращающая ссылку. Обычно (но не обязательно) конструктор имеет имя new. Выше мы сказали, что объект является ссылкой. Конструктор, создающий объект, то есть ссылку, тоже возвращает ссылку. О каких ссылках идет речь, на что они указывают? Рассмотрим простой пример.

package MyClass; sub new {

my $class = shift;

my $self = {} ;

bless $self, $class;

return $self; -""'

} ^

В операторе my $seif = {} создается ссылка на 7 анонимный хеш-массив, первоначально пустой, которая сохраняется в локальной переменной $seif. Функция bless о "сообщает" субъекту ссылки sseif, то есть анонимному хеш-массиву, что он отныне является объектом класса MyClass, и возвращает ссылку на этот объект. Затем ссылка на новый объект класса MyClass возвращается в качестве значения конструктора new (). Обратите внимание на следующие обстоятельства.

Во-первых, объект класса MyClass представлен анонимным хеш-массивом. Это обычный, хотя и не обязательный, способ представления объекта. Преимущество использования для этой цели хеш-массива заключается в том, что он может содержать произвольное число элементов, к которым можно обращаться по произвольно заданному ключу. Таким образом, все данные объекта сохраняются в указанном хеш-массиве. В некоторых случаях для представления объекта может использоваться другой тип данных, например массив, скаляр.

Во-вторых, обязательным для конструктора является обращение к функции bless о. Внутри подпрограммы-конструктора функцию bless о следует применять с двумя аргументами, явно указывая имя класса. В этом случае конструктор может быть наследован классом-потомком. Конструктор определяет, объект какого именно класса он создает, используя значение своего первого аргумента. Первым аргументом конструктора должно быть имя класса.

В-третьих, конструктору, создающему объект, в качестве первого аргумента передается имя класса этого объекта.

f Модуль класса Staff.pm: package Staff; require Exporter; 8ISA = qw(Exporter); SEXPORT = qw(new); sub new {

my ($class,@items) = shift;

my $self = {};

bless $self, $class;

return $self; }

# Основная программа:

#!/usr/bin/perl use Staff;

$someone=new(Staff) ; ${$someone}{"имя"}="Александр";
${$someone}{"фамилия"}="Александров"; ${$someone}{"возраст"}="37";
for $i (sort keys %{$someone}) { print "$i=>$$someone{$i}\n"; }

В данном примере класс staff служит для представления анкетных данных. В качестве внутренней структуры для представления объекта наилучшим образом подходит хеш-массив, так как в него при необходимости можно добавлять новые элементы с произвольно заданными ключами, например, "имя", "фамилия", "образование" и т. д. Класс оформлен в виде отдельного модуля, способного управлять экспортом своих методов. Чтобы конструктор new () можно было вызвать в основной программе, он включен в файл экспорта @EXPORT. В результате вызова конструктора возвращается ссылка на объект класса. Значения элементов хеш-массива выводятся:

возраст => 37

имя => Александр

фамилия => Александров

Методы класса и методы объекта

Различают методы класса и методы объекта, которые называют также статическими и виртуальными, соответственно. Первым аргументом метода должна быть ссылка, представляющая объект, или имя пакета. То, каким образом каждый метод обрабатывает свой первый аргумент, определяет, является ли он методом класса или методом объекта.

Статические методы применяются к целому классу, а не к отдельным его объектам. В качестве первого аргумента статическому методу передается имя класса. Типичным примером статических методов являются конструкторы. В Perl методы выполняются в пространстве имен того пакета, в котором они были определены, а не в пространстве имен пакета, в котором они вызваны. Поэтому статические методы часто свой первый аргумент игнорируют, так как и без него известно, к какому пакету метод принадлежит. Последнее не относится к конструкторам, передающим свой первый аргумент функции Ыезз () в качестве имени класса.

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

# Модуль класса Staff.pm:

package Staff;

require Exporter;

@ISA = qw(Exporter);

8EXPORT = qw(new showdata setdata);

sub new {

my ($class, $data) = @_;

my $self = $data;

bless $self, $class;

return $self; } sub showdata {

my $self = shift;

my @keys = @_ ? @_ : sort keys %$self;

foreach $key (@keys) {

print "\t$key => $self->{$key}\n";

}

return $self;

}

sub setdata {

my ($self,$data) = @_; for $i (keys %$data) {

$self->{$i)=$data->{$i);

}

return $self;

}

В данном примере по сравнению с предыдущим изменен конструктор new (). Теперь второй параметр, представленный локальной переменной $data, содержит ссылку. Эту ссылку функция bless () свяжет с классом staff, превратив в его объект. Таким образом, при помощи этого параметра можно управлять типом внутренней структуры данных, которая и представляет объект. Это может быть ссылка на хеш-массив, массив, скаляр и т. д. Параметры, передаваемые конструктору, называют переменными объекта. Они используются для того, чтобы установить начальные значения данных каждого вновь создаваемого объекта.

Если обратиться к основной программе:

#!/usr/bin/peri

use Staff;

$someone=new(Staff, ("имя"=>"","фамилия"=>""});

setdata($someone,{"имя"=>"Максим","фамилия"=>"Исаев",

"возраст"=>42,"занятия спортом"=>"теннис"}); showdata($someone);

то будут выведены следующие данные:

возраст => 42

занятия спортом => теннис

имя => Максим

фамилия => Исаев

В разных ситуациях один и тот же метод может выступать как метод класса или как метод объекта. Для этого он должен "уметь" определить тип своего первого аргумента: если аргумент является ссылкой, то метод действует как метод объекта, если именем пакета, то есть строкой, то как метод класса. Подобную информацию можно получить при помощи функции ref (). Она возвращает значение ЛОЖЬ (пустая строка), если ее аргумент не является ссылкой, то есть объектом. В противном случае функция ref о возвращает имя пакета, принадлежность к которому была для данного объекта санкционирована функцией bless ().

Вызов метода

Существуют две синтаксические формы вызова как методов класса, так и методов объекта.

Первая форма имеет вид:

method class_or_object, parameters

например,

$somebody = new Staff, {"имя"=>"Анна"}; # метод класса

showdata $somebody, "имя","фамилия"; # метод объекта

showdata {"имя"=>"Мария","возраст"=>18}; # метод объекта

showdata new Staff "возраст"; # метод объекта

showdata setdata hew Staff, {"имя"=>"Глеб"}, "имя"; # метод объекта

Данная форма представляет собой обычный вызов функции, который может быть вложенным в другой вызов. Первым аргументом функции является ссылка (для методов объекта) или/им^Гпакета (для методов класса).

В приведенном примере первая строка содержит вызов конструктора new, в котором первым (и единственным) аргументом является имя пакета.

Вторая строка содержит вызов метода объекта, в котором первым аргументом является объект-ссылка.

В третьей строке первый аргумент задается при помощи блока {}, возвращающего ссылку на анонимный хеш-массив. Данный хеш-массив не будет объектом, так как он не объявлен объектом класса staff при помощи функции bless о, но синтаксически такая конструкция возможна.

В четвертой строке метод объекта вызывается с двумя аргументами. Первым аргументом является ссылка, возвращаемая конструктором new (), вторым — строка "возраст".

В пятой строке конструктор new создает объект, который передается в качестве первого аргумента методу setdata. Вторым аргументом метода setdata является ссылка на анонимный хеш-массив {"имя"=>"Глеб"). Метод showdata в качестве первого аргумента использует ссылку, возвращаемую методом setdata, а в качестве второго аргумента — строку "имя".

Вторая форма обращения к методу имеет вид

class_or_obj ect ->method(parameters)

Например, предыдущие вызовы могут быть записаны также в виде:

$somebody = Staff->new(("имя"=>"Анна"});

$somebody->showdata("имя","фамилия");

new Staff->showdata("возраст");

new Staff->setdata({"имя"=>"Глеб"})->showdata("имя");

Вторая форма требует обязательного заключения аргументов в скобки.

Замечание

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

При использовании первой формы на том месте, где должен стоять объект или имя класса, синтаксис позволяет использовать либо имя класса, либо скалярную переменную, содержащую ссылку, либо блок {...}, возвращающий значение ссылки. Вторая форма для Представления объекта позволяет использовать более сложные конструкции, например, элемент хеш-массива:

$obj->{keyname}->method().

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

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

$someone = new Staff;

Staff::showdata($someone, "имя");

В данном случае просто вызывается метод showdata из пакета staff. Ему передается в качестве аргумента объект $ some one и прочие аргументы. Если вызвать метод следующим образом:

$someone=new Staff; $someone->Staff::showdata("имя");

то это будет означать, что для объекта $ someone следует сначала найти метод showdata, начав поиск с пакета staff, а затем вызвать найденный метод с объектом $ someone в качестве первого аргумента. Напомним, что с каждым пакетом ассоциируется свой массив @ISA, содержащий имена других пакетов, представляющих классы. Если интерпретатор встречает обращение к методу, не определенному в текущем пакете, он ищет этот метод, рекурсивно (то есть включая производные классы) просматривая пакеты, определенные в массиве @ISA текущего пакета. Если подобный вызов делается внутри пакета, являющегося классом, то для того, чтобы указать в качестве начала поиска базовый класс, не указывая его имя явно, можно использовать имя псевдокласса SUPER:

$someone->SUPER::showdata("имя");

Такая запись имеет смысл только внутри пакета, являющегося классом.

Деструкторы

В главе 9 было сказано, что для каждого субъекта ссылки поддерживается счетчик ссылок. Область памяти, занимаемая субъектом ссылки, освобождается,, когда значение счетчика ссылок становится равным нулю. Объект, как мы знаем, является просто ссылкой, поэтому с ним происходит то же самое: когда значение счетчика ссылок становится равным нулю, внутренняя структура данных, представляющая объект (обычно хеш-массив), освобождает память. Интерпретатор сам отслеживает значение счетчика ссылок и автоматически удаляет объект. Пользователь может определить собственные действия, завершающие работу объекта, при помощи специального метода — деструктора. Деструктор нужен для того, чтобы корректно завершить жизненный цикл объекта, например, закрыть открытые объектом файлы или просто вывести нужное сообщение. В соответствующее время деструктор будет автоматически вызван интерпретатором.

Деструктор должен быть определен внутри своего класса. Он всегда имеет имя DESTROY, а в качестве единственного аргумента — ссылку на объект, подлежащий удалению. Создавая подпрограмму-деструктор, следует обратить внимание на то, чтобы значение ссылки на удаляемый объект, передаваемое в качестве первого элемента массива параметров $_[0], не изменялось внутри подпрограммы.

Подчеркнем, что создавать деструктор не обязательно. Он лишь предоставляет возможность выполнить некоторые дополнительные завершающие действия. Основная работа по удалению объекта выполняется автоматически. Все объекты-ссылки, содержащиеся внутри удаляемого объекта как его данные, также удаляются автоматически.

Метод DESTROY не вызывает другие деструкторы автоматически. Рассмотрим следующую ситуацию. Конструктор класса, вызывая конструктор своего базового класса, создает объект базового класса. Затем при помощи функции bless о делает последний объектом собственного класса. Оба класса, текущий и базовый, имеют собственные деструкторы. Поскольку конструктор базового класса, вызванный конструктором текущего класса, создал собственный объект, то при его удалении должен вызываться деструктор базового класса. Но этот объект уже перестал быть объектом базового класса, так как одновременно объект может принадлежать только одному классу. Поэтому при его удалении будет вызван только деструктор текущего класса. При необходимости деструктор текущего класса должен вызвать деструктор своего базового класса самостоятельно.


Глава 1

Обобщающий пример

В заключение рассмотрим небольшой пример, поясняющий некоторые вопросы, рассмотренные в этой части.

#!/usr/bin/perl package Staff; sub new {

my ($class, $data) = @_;

my $self = $data;

bless $self, $class;

return $self; } sub setdata {

my ($self,$data) = @_;

for $i (keys %$data) {

$self->{$i}=$data->{$i};

}

return $self; } sub showdata {

my $self = shift;

my @keys = @_ ? @_ : sort keys %$self;

fоreach $key (@keys) {

print "\t$key => $self->{$key}\n";

}

return $self; } sub AUTOLOAD {

print "пакет Staff: отсутствует функция $AUTOLOAD\n"; } sub DESTROY {

print "Удаляется объект класса Staff\n"; }

######################################

package Graduate; @ISA = (Staff); sub new {

my ($class, $data) = @_;

# наследование переменной объекта

my $self = Staff->new($data);

$self->{"образование"}="высшее";

bless $self, $class;

return $self; } sub showdata {

my $self = shift;

return $self if ($self->{"образование"} no "высшее");

my @keys = sort keys %$self;

foreach $key (@keys) {

print "\t$key => $self->{$key}\n";

}

return $self; } sub DESTROY {

my $self= shift;

$self->SUPER::DESTROY;

print "Удаляется объект класса Graduate\n";

######################################

package main;

$someone=Graduate->new({ "фамилия" => "Кузнецов", "имя" => "Николай" });

$somebody=Staff->new({"фамилия" => "Петрова", "имя" => "Анна"});

$someone->showdata;

$somebody->Graduate::showdata;

$someone->getdata;

Для простоты все классы расположены в одном файле. Если класс занимает отдельный модуль, необходимЬчхрзабхшпъся об управлении экспортом имен при помощи списков @EXPORT и @EXPORT_OK, а также о подключении соответствующих модулей к вызывающей программе (см. здесь).

В данном примере определен пакет main и два класса: staff и Graduate, staff является базовым классом Graduate.

Наследование методов задается при помощи массива @ISA, ассоциированного с производным классом Graduate. Помимо наследования методов, которое обеспечивает Perl, организовано наследование переменных, объекта через вызов в конструкторе класса Graduate конструктора базового класса staff. В результате объект класса Graduate получает при создании переменную объекта staff (параметр, переданный конструктору new), которую изменяет, добавляя в соответствующий хеш-массив новый элемент с ключом "образование" и значением "высшее".

Ситуация, когда конструктор производного класса вызывает конструктор базового, также обсуждалась в разделе 12 . В подобном случае при удалении объекта автоматически вызывается только деструктор производного класса, хотя при вызове конструктора последовательно создавались объекты двух классов. В примере деструктор DESTROY класса Graduate вызывает деструктор базового класса. В данном случае это совершенно не обязательно, так как оба деструктора просто выводят сообщения об удалении своих объектов, но ведь это только модель. В иной ситуации такое решение может быть необходимым.

Следующая тема связана с поиском несуществующего метода, к которому происходит обращение внутри класса, и применением в этом случае функции AUTOLOAD. В пакете main вызывается метод getdata. для объекта $someone. $someone является объектом Класса Graduate, в котором метод getdata не определен. Обратите внимание на форму вызова метода getdata. Если бы он был вызван в виде getdata ($someone), это означало, что вызывается функция getdata из пакета main с параметром $someone. Поскольку в пакете main такая функция не определена, выполнение программы было бы завершено с выдачей сообщения вида:

Undefined subroutine Smain::getdata called at ...

Форма обращения $someone->getdata однозначно определяет, что вызывается не функция, а метод объекта. Следовательно, его надо искать сначала в собственном классе Graduate, а затем в классе staff, который определен в качестве базового для Graduate. В классе staff метод getdata также не определен, но определена функция AUTOLOAD, которая и вызывается вместо этого метода.

Полиморфизм, т. е. переопределение методов базового объекта, показан на примере метода showdata. Класс staff служит для порождения анкетных форм учета персонала. Его подкласс Graduate описывает подмножество таких форм только для лиц с высшим образованием. Конструктор класса Graduate автоматически добавляет в форму запись о наличии высшего образования. Метод showdata, наследованный у базового класса, изменен таким образом, чтобы игнорировать анкеты без такой записи. Поэтому информация об объекте $ somebody, принадлежащем к базовому классу, напечатана не будет.

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

имя => Николай образование => высшее фамилия => Кузнецов

пакет Staff: отсутствует функция Graduate::getdata Удаляется объект класса Staff Удаляется объект класса Graduate Удаляется объект класса Staff


Глава 1

Вопросы и Упражнения

Вопросы для самоконтроля

1. Как связаны между собой понятия "объект" и "класс"?

2. Что такое инкапсуляция, наследование, полиморфизм в объектно-ориентированном программировании?

3. Как в языке Perl реализовано понятие "класс"?

4. Что представляет собой объект в языке Perl?

5. Для чего предназначена функция bless () ?

6. Как, по вашему мнению, связаны между собой понятия "класс", "пакет", "модуль"?

7. Как в Perl осуществляется наследование методов? Данных?

8. Какой синтаксис используется для объявления метода?

9. Чем конструкторы в Perl отличаются от других методов?

10. В чем разница между методами класса и методами объекта?

11. Какие формы вызова объекта вы знаете? В чем, по-вашему, заключаются их преимущества и недостатки?

12. Какие имена может иметь подпрограмма-деструктор?

13. Как можно реализовать переопределение метода базового класса (полиморфизм)?

14. Как осуществить наследование переменных объекта?

Упражнение

Создайте класс, объект которого представляет дерево файловой системы для заданного каталога и позволяет вывести это дерево с указанием всех вложенных каталогов и содержащихся в них файлов.


Глава 1

Часть 14. Запуск интерпретатора и режим отладки


Глава 1

Опции командной строки

При запуске интерпретатора peri из командной строки можно задать разнообразные режимы его работы. Это достигается передачей ему специальных опций, называемых еще переключателями или просто ключами, включающих или выключающих разные режимы работы интерпретатора. Знание всех возможностей, предоставляемых опциями, позволяет более эффективно использовать интерпретатор для решения возникающих задач. Например, опция -е позволяет задать строку кода Perl непосредственно в командной строке. Эта возможность удобна для быстрого выполнения небольшой задачи, не прибегая к созданию файла с исходным текстом программы. Другие опции могут сэкономить время системного администратора, позволив решить некоторые задачи непосредственным вызовом peri из командной строки без написания кода достаточно большого объема.

Внутренний отладчик, которым снабжен интерпретатор peri, неоценим при отладке сложных сценариев Perl, особенно при разработке собственных модулей. Конечно, по общему признанию апологетов Perl, возможности его отладчика не так изощренны, как в С, C++ или Visual Basic, однако они могут оказаться мощным средством выявления ошибок, не обнаруживаемых самим компилятором даже в режиме отображения предупреждений.

В этой главе мы познакомимся с некоторыми опциями командной строки интерпретатора и их использованием для решения повседневных задач системного администрирования. Узнаем, как можно воспользоваться встроенным отладчиком в системе UNIX, а также изучим необходимый набор команд отладчика для выполнения основных действий в процессе обнаружения ошибок программ Perl.

Наиболее общая форма синтаксиса строки запуска интерпретатора peri имеет следующий вид:

peri [опции] [—] [файл_программы] [параметры_программы]

Опции peri задаются сразу же после имени интерпретатора и представляют собой двухсимвольные последовательности, начинающиеся с дефиса "-":

peri -а -р prog.pl filel file2

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

peri -w -I/usr/include -0055 prog.pl filel file2

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

peri -wd prog.pl filel file2 peri -w -d prog.pl filel file2

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

peri -wdl/usr/include -0055 prog.pl filel file2

Передать опции для установки соответствующего режима работы интерпретатора можно непосредственно и из самой программы Perl. Первая строка сценария со специальным комментарием #! предназначена именно для этой цели:

#!/usr/bin/perl -w -d -I/usr/include

Теперь настало время узнать, какие опции существуют и как они влияют на режим работы интерпретатора. Мы решили сначала перечислить все возможные опции peri, а потом объяснить использование некоторых из них для выполнения задач системного администрирования UNIX. В табл. 14.1 представлены все опции с их кратким описанием. Советуем читателю внимательно ознакомиться с содержанием этой таблицы, так как для некоторых опций приведенное краткое описание в то же время является и исчерпывающим для понимания существа данной опции.

Таблица 14.1. Опции командной строки

Опция | Назначение
-0 [nnn] | Задает разделитель записей файла ввода, устанавливая значение специальной переменной $/. Необязательный параметр nnn — восьмеричный код символа; если отсутствует, принимается равным 0 (код символа \0). Эта опция удобна, если при каждом запускесценария Perl в него необходимо передавать файлы с разными символами завершения записи
-а | Включает режим авторазбиения строки ввода при совместном использовании с опцией -n или -р (строка передается функции split ( } , а результат ее выполнения помещается в специальный массив @г). Умалчиваемый разделитель — пробел; опция -F позволяет задать регулярное выражение для иного разделителя
-с | Проверка синтаксиса программы без ее выполнения (блоки BEGIN, END и use выполняются, так как это необходимо для процесса компиляции)
-d[ : модуль] | Запускает сценарий в режиме отладки под управлением модуля отладки или трассировки, установленного как Devel: : модуль
-D [число/список] | Устанавливает флаги отладки
-е ' строка кода ' | Выполнение строки кода Perl, заданной параметром этой опции, а не файла сценария файл_программы, указанного в командной строке вызова интерпретатора. В строке кода можно задать несколько операторов, разделенных символом ';'. Допускается задание нескольких опций -е. (В Windows строка кода задается в двойных кавычках и для использования самих кавычек в коде Perl необходимо применять управляющую последовательность \".)
-F/per выраж/ | Задает регулярное выражение для разделителя при автоматической разбивке строки файла ввода в случае задания опции -а. Например, -F/-.+/ определяет в качестве разделителя одно или более двоеточий. По умолчанию используется пробел / /. Символы "/" при задании per выраж не обязательны
-i {расширение] | Задает режим редактирования по месту файла, переданного как параметр в сценарий Perl. Используется совместно с опциями -n или -р. Редактирование выполняется по следующей схеме: файл переименовывается (если задано расширение), открывается файл вывода с именем исходного файла и все операции print ( ) сценария осуществляют вьшод в этот новый файл. Параметр расширение используется для задания имени копии файла по следующей схеме: если в нем нет символов "*", то оно добавляется в конец имени исходного файла; каждый символ "*" этого параметра заменяется именем исходного файла
-Ткаталог | Задает каталог поиска сценариев Perl, выполняемых операциями do, require и use (сохраняется как элемент специального массива @INC). Также задает каталоги размещения файлов, включаемых в сценарий Perl директивой finclude препроцессора С. Можно использовать несколько опций для задания необходимого количества каталогов поиска
- 1 [ nnn ] | Включает автоматическую обработку концов строк ввода. Работа в этом режиме преследует две цели: 1. Автоматическое удаление завершающего символа записи, хранящегося в специальной переменной $/, при включенном режиме -n или -р; 2. Присваивание специальной переменной $\, в которой хранится символ завершения записи при выводе операторами print, восьмеричного значения nnn, представляющего код символа завершения; если этот параметр не задан, то переменной $\ присваивается текущее значение $ /
-m[-] мо дуль -М [ - ] модуль -[гаМ][-] модуль= пар! [ , пар2 ] . . | Выполняет оператор use module О (опция -т) или use module (опция -м) перед выполнением сценария Perl. Если параметр модуль задан с дефисом, то use заменяется на по. Третья форма этих опций позволяет передать параметры в модуль при его загрузке
-n | Заданный сценарий Perl выполняется в цикле while (0) { сценарий Perl } Это позволяет обработать программой Perl в цикле каждую строку всех файлов, имена которых переданы в качестве параметров сценария Perl. Эта опция эмулирует функциональность sed с ключом -n и awk
-Р | Аналогичен опции -п, но добавляется печать каждой обрабатываемой строки, как в редакторе sed: while (<>) { сценарий Perl } continue { print or die "-p destination: $!\n"; }
_Р | Перед компиляцией сценарий Perl обрабатывается препроцессором С, что позволяет использовать в нем команды препроцессора #define, # include и все команды условной компиляции С (#if, felse и т. д.). В опции -I можно задать каталоги расположения файлов, включаемых командой t include
-s | Включает режим синтаксического анализа строки вызова сценария Perl на наличие после имени файла сценария, но до параметров имен файлов "пользовательских опций" (параметров, начинающихся с дефиса). Такие параметры извлекаются из массива @ARGV и в программе объявляются и определяются переменные с именами введенных опций. Следующий сценарий напечатает "Задана опция optl" тогда и только тогда, когда он будет вызван с опцией -optl: # ! /usr/bin/perl -s if ($optl) { print " Задана опция optl\n"; }
-S | Поиск файла программы Perl файл программы осуществляется с использованием значения переменной среды PATH
-т | Включает режим проверки на безопасность полученных извне данных в операциях с файловой системой. Полезен при реализации CGI-сценариев. Эта опция должна быть первой в списке опций интерпретатора peri, если она применяется к сценарию
-u | Создание дампа ядра после компиляции сценария Perl. Последующее использование программы undump позволяет создать двоичный выполняемый файл сценария
_u | Разрешает выполнение небезопасных операций. При использовании этой опции в UNIX программа Perl выполняется в незащищенном режиме и ей предоставляется полный доступ к файловой системе
-V | Отображает номер версии, а также другую важную информацию об интерпретаторе peri
_V [ : переменная ] | Отображает информацию конфигурации интерпретатора peri (расположение необходимых библиотек, значения переменных среды и т. д.). Если задано имя переменной среды, отображает ее установленное значение
-W | Включает режим отображения предупреждений во время компиляции программы Perl. Рекомендуется всегда применять эту опцию
-х [каталог] | Извлекает и выполняет сценарий Perl, расположенный в текстовом файле. Эта возможность удобна, если сценарий прислан по электронной почте. Интерпретатор сканирует файл, пока не найдет строку, начинающуюся с символов "# ! " и содержащую слово "peri". После этого выполняется сценарий до лексемы END . Если задан параметр каталог, то перед выполнением сценария он становится текущим

Как видно из табл. 14.1, интерпретатор peri располагает достаточно большим набором опций, разработанных специально в помощь системному администратору UNIX. Эти опции позволяют решать задачи, даже не прибегая к созданию текстового файла сценария Perl.

Одной из наиболее часто встречающихся задач системного администрирования является задача обработки содержимого большого числа файлов и данных (например, изменение значений некоторых переменных среды в конфигурационных файлах). Можно написать сценарий Perl, в цикле обрабатывающий строки переданных ему файлов, а можно воспользоваться опциями -п или -р, которые организуют цикл по файлам, и задать необходимые действия для каждой строки файла в опции -е. Таким образом, быстрота решения задачи гарантирована!

Итак, мы хотим в конфигурационных файлах configl, config2 и configS, содержащих строки вида ключ = ЗНАЧЕНИЕ, изменить значение ключа "кеу!" на величину 5. Эта задача решается следующим вызовом peri из командной строки:

peri -p -i.bak -e"m/ (\w+)\s*=\s*(.+)/i;

if($l eq V'KeylV){$_ = \"$1 = 5\n\";};" configl config2 configS

Несколько комментариев к строке вызова интерпретатора peri. В ней использованы опции -р и -i для обработки содержимого конфигурационных файлов по месту (опция -i, причем задано расширение .bak для копий исходных файлов) в неявном цикле с печатью] (опция -р). Код модификации содержимого файлов задается с помощью о/щии -е. Он достаточно прост. Строка файла, прочитанная в специальнуюЛтеременную Perl $_, проверяется на содержание подстроки вида ключ = ЗНАЧЕНИЕ. В регулярном выражении определены две группы: первая соответствует ключу, а вторая — его значению. Таким способом мы сохраняем имя ключа и его значение в специальных переменных $1 и $2, соответственно. Второй оператор кода, в случае совпадения имени ключа с заданным, заносит в переменную $_ строку с новым значением ключа (кеу! = 5).

Как выполняется этот сценарий? Создается копия исходного конфигурационного файла configl (файл configl.bak) и новый пустой файл с именем configl. Последовательно в переменную $_ читаются строки из файла-копии, а в переменную $1 заносится имя ключа, если таковой обнаружен оператором т/.../. Если имя ключа равно "Keyi", то в переменной $_ формируется строка задания нового значения ключа. Перед чтением следующей строки файла configl.bak в файл configl записывается содержимое переменной $_. Эта процедура выполняется со всеми заданными конфигурационными файлами. В результате выполнения этой программы формируются конфигурационные файлы, в которых значение ключа Keyi изменено на 5.

Опция -р достаточна полезна для анализа содержимого файлов. Например, следующая команда отобразит на экране содержимое файла prog.pl:

peri -p -е"1" prog.pl

Замечание

Задание кода программы Perl при работе с интерпретатором peri является необходимым действием. Его можно задать, указав имя файла программы после всех опций, либо непосредственно в командной строке в опции -е. При печати содержимого файла мы задали в командной строке программу из одного оператора, который, по существу, ничего не делает.

При работе с интерпретатором из командной строки очень часто используются регулярные выражения для отображения какой-либо части содержимого файла. Следующий вызов peri отображает на экране монитора последний столбец данных из файла test.dat (элементы столбцов в строке файла разделены пробелами):

peri -p -e "s/\s*.+\s(.+)\s*/$l\n/;" test.dat

Замечание

При использовании опции -р следует помнить, что она печатает содержимое специальной переменной $_. Поэтому информацию, подлежащую отображению, следует помещать именно в эту переменную, как в последнем примере.

Можно было бы привести еще массу полезных примеров вызова интерпретатора peri с различными опциями для решения разнообразных задач, но мы ограничимся приведенными примерами. При написании этой главы мы ставили целью всего лишь показать, на что способен интерпретатор peri. Думайте, размышляйте, и вы сможете решать многие задачи, не прибегая к написанию больших файлов с кодом Perl.


Глава 1

Отладчик Perl

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

Для вызова встроенного отладчика интерпретатора peri его необходимо запустить с опцией -d (задав ее либо в командной строке, либо в строке специального комментария I! самой программы). Отладчик инициируется, когда начинает выполняться сценарий Perl. При этом отображается версия отладчика, первая выполняемая строка кода программы и его приглашение DB<I> на ввод команд пользователя:

Loading DB routines from perl5db.pl version 1.0402 Emacs support available.

Enter h or *h h' for help.

main::(examplel.pl:3): print "\000\001\002\003\004\005\006\007\n"; DB<1>

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

Замечание

Все, что здесь говорится о встроенном отладчике, относится к пользователям системы UNIX. Тот, кто работает в системах семейства Windows и пользуется интерпретатором peri фирмы ActiveState, может совсем не читать этот раздел, так как интерпретатор этой фирмы снабжен отладчиком, работа с которым осуществляется через графический интерфейс пользователя, а не с помощью командной строки. Хоти ничто не мешает таким пользователям воспользоваться встроенным отладчиком, аналогичным отладчику интерпретатора peri для UNIX, и работать с ним из командной строки. Для этого следует воспользоваться командным файлом cmddb.bat, расположенным в каталоге \perl520\debugger.

Просмотр текста программы

При загрузке отладчика отображается первая строка кода программы Perl. Но для выполнения определенных действий (установки точки останова, задания определенных действий при выполнении конкретного оператора Perl и т. д.) нам необходимо видеть текст программы. Как это осуществить? В отладчике для подобных действий предусмотрен ряд команд.

Команда i отображает на экране монитора 10 строк текста программы, расположенных непосредственно за последней отображенной строкой. Последовательное выполнение этой команды позволяет быстро пролистать текст программы. Вызов команды i с указанием номера строки отобразит ее содержимое. Несколько последовательных строк программы можно увидеть, задав в команде i через дефис номер начальной и конечной строки. Например, команда 1 1-5 отобразит пять строк программы:

DB<5> 1 1-5 1 #! peri -w

2==> open FILE, "books" or die $!; 3: open REPORT, ">report"; 4 5: select REPORT;

Обратите внимание, что отладчик отображает не только содержимое строк, но и их номера, причем после номера строки с первым выполняемым оператором следует стрелка, а номер каждой последующей выполняемой строки завершается символом двоеточие ":". В угловых скобках приглашения отладчиков <5> отображается порядковый номер выполненной команды от начала сеанса отладки.

Команда w отобразит блок, или окно строк — три строки до текущей, текущая строка и шесть после текущей. Текущей в отладчике считается строка программы после последней отображенной. Если команде w передать номер строки, то отобразится окно строк относительно заданной строки текста — три строки до заданной, сама заданная строка и шесть после нее.

Последняя команда отображения текста программы — команда - (дефис), которая отображает 10 строк текста, предшествующих текущей строке.

Выполнение кода

Команда s предназначена для последовательного пошагового выполнения программы: каждый ее вызов выполняет следующую строку кода. Эта строка должна выполняться в программе без изменения потока выполнения операторов. После выполнения очередной строки кода отображается строка, которая должна быть выполнена следующей:

DB<1> s main: : (exaitple2.pl:3) : @s = split;

При следующем выполнении команды s будет выполнена отображенная строка программы: @s = split;.

Если следующая выполняемая строка кода расположена в модуле, код которого доступен (например, вызов подпрограммы), то осуществляется переход в него, соответствующая строка кода выполняется и отладчик приостанавливает выполнение программы до ввода следующей программы. Последующие команды s будут построчно выполнять код подпрограммы, пока не выполнится последняя строка ее кода. Таким образом, команда s позволяет выполнять подпрограмму с заходом в нее.

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

Совет
Если мы осуществляем пошаговое выполнение программы (командами s или n), то нажатие клавиши <Enter> эквивалентно вызову последней команды s или n.

Если мы в процессе отладки попали в тело подпрограммы (после, например, выполнения очередной команды s), то командой г можно немедленно завершить ее выполнение. Выполнение программы приостанавливается на первом после вызова подпрограммы операторе в ожидании очередной команды пользователя.

Пошаговое выполнение программы командами s и п может оказаться утомительным делом при отладке кода большого объема. В отладчике Perl предусмотрена команда с, которая выполняет программу от текущего оператора до первой встретившейся точки останова или до завершения программы, если точки останова на соответствующем участке кода не определены.

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

Просмотр значений переменных

В процессе отладки можно посмотреть значение любой переменной программы в любой момент времени. Команда

V [пакет [переменная]]

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

При работе с этой командой следует иметь в виду, что при задании переменной, значение которой необходимо посмотреть, не надо задавать никакого префикса, а только идентификатор переменной. Отладчик отобразит значения всех переменных указанного пакета с заданным идентификатором. Например, если в программе определена скалярная переменная $ref, маесив скаляров @ref и хеш-массив %ref, то выполнение команды v main ref приведет к следующему результату:

DB<1> V main ref $ref = 24 @ref = ( 1

01

1 2 | ) %ref = (

'One' => 1

'Two' => 2 )

Команда х аналогична команде v, но она отображает значения переменных текущего пакета. Ее параметром является идентификатор переменной, имя пакета указывать не надо. Вызванная без параметров, она отображает значения всех переменных текущего пакета. Например, команда х ref отобразит значения переменной $ref, массива @ref и хеш-массива %ref текущего пакета, внутри которого приостановлено выполнение программы. По умолчанию программа выполняется в пакете main.

Команда t работает как переключатель, включая режим отображения строк выполняемого кода (режим трассировки) или выключая его:

DB<11> t Trace = on

DB<11> с 5

main::(example2.pl:3): @ref = (1,2,3,4);
main::(example2.pl:4): %ref = ("One",l, "Two",2); main::(example2.pl:5):
$_ = " qwerty \t\tqwerty";

DB<12> t Trace = off

DB<12> с 7 main::(example2.pi:7): mySub();

DB<13>

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

Точки останова и действия

В процессе отладки программы возникает необходимость приостановить ее выполнение в определенных подозрительных иестах, посмотреть значения

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

Для этих целей служит команда ь (сокращение от английского глагола break — прервать) отладчика. Ее параметром является номер строки кода, в которой устанавливается точка останова: отладчик приостановит выполнение программы Perl перед заданной строкой. Если команда ь вызывается без параметра, то точка останова определяется в текущей строке.

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

DB<11> b mySub

I устанавливает точку останова в первой строке кода подпрограммы mySub.

Иногда необходимо, чтобы выполнение программы приостанавливалось в некоторой точке программы только при выполнении каких-либо условий (например, равенства заданному числу значения какой-нибудь переменной, или совпадения значений двух других переменных и т. п.). Команда ь позволяет задавать подобные условные точки останова. Для этого ей можно передать в качестве второго параметра условие, при истинности которого точка останова будет восприниматься отладчиком как действительная точка останова. Если условие перед выполнением строки кода не будет истинно, то останова программы в этой точке не произойдет. Например, следующая команда

DB<1> b 4 $r==l I

определяет условную точку останова в строке 4. Отладчик приостановит выполнение программы перед этой строкой по команде с только, если значение переменной $г будет равно 1.

Команда L отображает список всех установленных точек останова, как безусловных, так и условных:

DB<1> b 4 $г==1 DB<2> b 6 DB<3> L

Iexample2.pl: . •:.,-. , 4: %ref = ("One",!, "Two",2);

break if ($r==l) 6: @s = split;

break if (1) •/;•;'• : • -

Отображаемая информация о точке останова представляет номер строки и код Perl, а также условие, при котором действует точка останова (break if (УСЛОВИЕ)). Для безусловной точки останова условие всегда истинно и равно 1.

Для удаления точки останова достаточно выполнить команду d с параметром, равным номеру строки, в которой определена точка останова. Команда о удаляет все точки останова, определенные в сеансе работы с отладчиком.

Полезно при отладке программы задать действия, которые будут предприняты перед выполнением операторов определенной строки. Например, напечатать значения каких-либо переменных или изменить их при выполнении некоторого цикла. Подобное поведение программы можно реализовать, задав действия командой а. Два ее параметра определяют строку кода и сами действия (обычный оператор Perl) перед началом выполнения операторов заданной строки:

а 75 print "*** $ref\n";

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

Любое действие можно выполнить немедленно, набрав код в строке приглашения отладчика DBO. Подобные действия не изменяют текст программы (операторы действий не записываются в ее файл), но позволяют создавать новые переменные и использовать их в вычислениях. Правда, по завершении сеанса отладки подобная информация пропадает.

Мы познакомили читателя лишь с основными командами отладчика, наиболее важными и полезными, с нашей точки зрения, для процесса поиска ошибок. Их полный набор с краткими описаниями представлен в табл. 14.2. Более подробную информацию можно всегда найти в документации, с которой распространяется Perl, или из различных ресурсов Internet.

Таблица 14.2. Основные команды отладчика

Команда | Описание
m | Отображается содержимое стека вызванных подпрограмм
s | Пошаговое выполнение программы (с заходом в подпрограммы)
n | Пошаговое выполнение программы (без захода в подпрограммы)
<Enter> | Повтор последней команды s или п
r | Завершение текущей подпрограммы и возврат из нее
с [строка] с [подпрогр] | Непрерывное выполнение кода программы до первой точки останова или указанной строки, или подпрограммы
1 строка+число | Отображает число плюс одну строку кода, начиная с заданной строки —
1 строка!-строка2 | Отображает диапазон строк: от строки с номером строка! до строки с номером строка2
1 строка | Отображает заданную строку
1 подпрогр | Отображает первый блок строк кода подпрограммы
1 | Отображает следующий блок из 10 строк
- | Отображает предыдущий блок из 10 строк
w [строка] | Отображает блок строк вокруг заданной строки
• | Возврат к выполненной строке
f файл | Переключение на просмотр файла. Файл должен быть загружен
/образец/ | / Поиск строки по образцу; направление вперед от текущей строки. Завершающая косая черта не обязательна
?образец? | Поиск строки по образцу; направление назад от текущей строки. Завершающий символ "?" не обязателен
L | Отображение всех установленных точек останова
S [ [ ! ] образец] | Отображение имен подпрограмм, [не] соответствующих образцу
t | Включение/выключение режима трассировки
b [строка] [условие] | Установка точки останова в заданной строке и условия ее действия
b подпрогр [условие] | Установка точки останова в первой строке подпрограммы и условия ее действия
b load файл | Установка точки останова на операторе require файл
b postpone подпрогр [условие] | Установка точки останова в первой строке подпрограммы после ее компилирования
b compile подпрогр | Остановка после компилирования подпрограммы

В этой, можно сказать, завершающей главе мы познакомились с возможностями интерпретатора peri для решения некоторых задач системного администрирования в UNIX. Установка некоторых опций интерпретатора при его запуске из командной строки меняет режим работы, позволяя практически без написания кода изменять, проверять, копировать и отображать содержимое файлов.

Для удобства и ускорения отладки больших программ в интерпретаторе per! предусмотрен встроенный отладчик. Его команды позволяют приостанавливать выполнение сценария Perl в подозрительных точках, задавать определенные действия при выполнении кода программы, просматривать стек вызова подпрограмм, менять в цикле значения переменных программы и многие другие полезные при поиске ошибок действия. Использование отладчика ускоряет процесс разработки программ Perl.


Глава 1

Вопросы для самоконтроля

1. Какую функцию выполняют опции интерпретатора peri?

2. Какие существуют способы задания опций интерпретатора?

3. Перечислите наиболее полезные опции для выполнения задач, связанных с системным администрированием в UNIX.

4. Зачем нужен отладчик и как его инициировать?

5. Перечислите основные действия, которые позволяет выполнять отладчик в процессе отладки программы Perl.


Глава 1

Часть 15. Язык Perl и CGI-программирование


Глава 1

Основные понятия

Основу "всемирной паутины" WWW составляют Web-узлы. Это компьютеры, на которых выполняется специальная программа — Web-сервер, ожидающая запроса со стороны клиента на выдачу документа. Документы сохраняются на Web-узле, как правило, в формате HTML. Клиентом Web-сервера является программа-браузер, выполняющаяся на удаленном компьютере, которая осуществляет запрос к Web-серверу, принимает запрошенный документ и отображает его на экране.

Аббревиатура CGI (Common Gateway Interface) обозначает часть Web-сервера, которая может взаимодействовать с другими программами, выполняющимися на этом же Web-узле. В этом смысле она является шлюзом (gateway — шлюз) для передачи данных, полученных от клиента, программам обработки — таким, как СУБД, электронные таблицы и др. CGI включает общую среду (набор переменных) и протоколы для взаимодействия с этими программами.

Общая схема работы CGI состоит из следующих элементов.

1. Получение Web-сервером информации от клиента-браузера. Для передачи данных Web-серверу в языке HTML имеется средство, называемое форма. Форма задается в HTML-документе при помощи тэгов <FORM>. . .</FORM> и состоит из набора полей ввода, отображаемых браузером в виде графических элементов управления: селекторных кнопок, опций, строк ввода текста, управляющих кнопок и т. д. (рис. 15.1).

2. Анализ и обработка полученной информации. Данные, извлеченные из HTML-формы, передаются для обработки CGI-программе. Они не всегда могут быть обработаны CGI-программой самостоятельно. Например, они могут содержать запрос к некоторой базе данных, которую CGI-программа читать "не умеет". В этом случае CGI-программа на основании полученной информации формирует запрос к компетентной программе, выполняющейся на том же компьютере. CGI-программа может быть написана на любом языке программирования, имеющем средства обмена данными между программами. В среде UNIX для этой цели наиболее часто используется язык Perl. Так как UNIX является наиболее популяр-

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

  • Создание нового HTML-документа и пересылка его браузеру. После обработки полученной информации CGI-программа создает динамический или, как говорят, виртуальный HTML-документ, или формирует ссылку на уже существующий документ и передает результат браузеру.

Рис 15.1. Пример отображения HTML-формы браузером


Глава 1

HTML-формы

HTML-формы предназначены для пересылки данных от удаленного пользователя к Web-серверу. С их помощью можно организовать простейший диалог между пользователем и сервером (например, регистрацию пользователя на сервере или выбор нужного документа из представленного списка). Формы поддерживаются всеми популярными браузерами.

(Различные аспекты передачи данных Web-серверу будут рассмотрены здесь )

Мы предполагаем, что читатель знаком с основами языка HTML и структурой HTML-документа. В этом разделе рассмотрены средства HTML, используемые для создания форм. В описании тэгов приведены только наиболее употребительные атрибуты и опущены атрибуты, специфические для отдельных браузеров.

Тэг <FORM>

<FORM ACTION="URL" МЕТНОО=метод_передачи ЕНСТУРЕ=М1МЕ-!гип>

содержание_формы

</FORM>

В HTML-документе для задания формы используются тэги <FORM>. . .</FORM>, отмечающие, соответственно, начало и конец формы. Документ может содержать несколько форм, но они не могут быть вложены одна в другую. Тэг <FORM> имеет атрибуты ACTION, METHOD и ENCTYPE. Отдельные браузеры (Netscape, Internet Explorer) поддерживают дополнительные атрибуты помимо стандартных, например, CLASS, NAME, STYLE и др.

Атрибут ACTION — единственный обязательный. Его значением является адрес (URL) CGI-программы, которая будет обрабатывать информацию, извлеченную из данной формы.

Атрибут METHOD определяет метод пересылки данйых, содержащихся в форме, от браузера к Web-серверу. Он может принимать два значения; GET (по умолчанию) и POST.

Замечание

Взаимодействие между клиентом-браузером и Web-сервером осуществляется по правилам, заданным протоколом HTTP, и состоит из запросов клиента и ответов сервера. Запрос разбивается на три части. В первой строке запроса содержится HTTP-команда, называемая методом, URL запрашиваемого файла и номер версии протокола HTTP. Вторая часть— заголовок запроса. Третья часть — тело запроса, собственно данные, посылаемые серверу. Метод сообщает серверу о цели запроса. В протоколе HTTP определены несколько методов. Для передачи данных формы в CGI-программу используются два метода: GET И POST.

При использовании метода GET данные формы пересылаются в составе URL запроса, к которому присоединяются после символа "?" в виде совокупности пар переменная=значение, разделенных символом "&". В этом случае первая строка запроса может иметь следующий вид:

После выделения данных из URL сервер присваивает их переменной среды QUERY_STRING, которая может быть использована CGI-программой.

При использовании метода POST данные формы пересылаются Web-серверу в теле запроса, после чего передаются сервером в CGI-программу через стандартный ввод.

Значением атрибута ENCTYPE является медиа-тип, определяющий формат кодирования данных при передаче их от браузера к серверу. Браузер кодирует данные, чтобы избежать их искажения в процессе передачи. Возможны два значения этого атрибута: appiication/x-wuw-form-uriencoded, используемое ПО умолчанию, И multipart/form-data.

Замечание

Одним из первых применений Internet была электронная почта, ориентированная на пересылку текстовых сообщений. Часто возникает необходимость вместе с текстом переслать данные в нетекстовом формате, например, упакованный zip-файл, рисунок в формате GIF, JPEG и т. д. Для того чтобы пересылать средствами электронной почты такие файлы без искажения, они кодируются в соответствии с некоторым стандартом. Стандарт MIME (Multipurpose Internet Mail Extensions, многоцелевые расширения электронной почты для Internet) определяет набор MIME-типов, соответствующих различным типам данных, и правила их пересылки по электронной почте. Для обозначения MIME-типа используется запись вида тип/подтип. Тип определяет общий тип данных, например, text, image, application (тип application обозначает специфический внутренний формат данных, используемый некоторой программой), а подтип — конкретный формат внутри типа данных, например, application/zip, image/gif, text/html. MIME-типы нашли применение в Web, где они называются также ме-dua-типами, для идентификации формата документов, передаваемых по протоколу HTTP. В HTML-форме атрибут ENCTYPE определяет медиа-тип, который используется для кодирования и пересылки специального типа данных — содержимого формы.

Как видно из примера на рис. 15.1, форма отображается в окне браузера в виде набора стандартных элементов управления, используемых для заполнения полей формы значениями, которые затем передаются Web-серверу. Значение вводится в поле ввода пользователем или назначается по умолчанию. Для создания полей средствами языка HTML существуют специальные тэги:

<INPUT>, <SELECT>, <TEXTAREA>, которые могут употребляться только внутри

тэга <FORM>.

Тэг <INPUT>

<INPUT ТУРЕ=тип_лоля_ввода ЫАМЕ=имя_лоля_ввода другие_атрибуты>

Наиболее употребительный тэг, с помощью которого можно генерировать внутри формы поля для ввода строки текста, пароля, имени файла, различные кнопки. Он имеет два обязательных атрибута: TYPE и NAME. Атрибут TYPE определяет тип поля: селекторная кнопка, кнопка передачи и др. Атрибут NAME определяет имя, присваиваемое полю. Оно не отображается браузером, а используется в качестве идентификатора значения, передаваемого Web-серверу. Остальные атрибуты меняются в зависимости от тира поля. Ниже приведено описание типов полей, создаваемых при помощи тэга <INPUT>, и порождаемых ими элементов ввода.

  • TYPE=TEXT Создает элемент для ввода строки текста. Дополнительные атрибуты:
  • MAXLENGTH=n Задает максимальное количество символов, разрешенных в текстовом поле. По умолчанию не ограничено,
  • SIZE=n Максимальное количество отображаемых символов.
  • УАЫ1Е=начальное_значение

Первоначальное значение текстового поля.

  • TYPE=PASSWORD

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

Замечание

Поле PASSWORD не обеспечивает безопасности введенного текста, так KaKjna сервер он передается в незашифрованном виде. /-"'"
  • TYPE=FILE

Создает поле для ввода имени локального файла, сопровождаемое кнопкой Browse. Выбранный файл присоединяется к содержимому формы при пересылке на сервер. Имя файла можно ввести непосредственно, или, воспользовавшись кнопкой Browse, выбрать его из диалогового окна, отображающего список локальных файлов. Для корректной передачи присоединенного файла следует установить значения атрибутов формы равными ENCTYPE="multipart/form-data" И METHOD=POST. В Противном случае будет передана введенная строка, то есть маршрутное имя файла, а не его содержимое. Дополнительные атрибуты MAXLENGTH и SIZE имеют тот же смысл, что и для элементов типа TEXT и PASSWORD.

  • TYPE=CHECKBOX Создает элемент-переключатель, принимающий всего два значения (on/off, вкл./выкл., истина/ложь) и отображаемый в виде квадратной кнопки. Элементы-переключатели CHECKBOX можно объединить в группу, установив одинаковое значение атрибута NAME для всех ее элементов. Дополнительные атрибуты:
  • УАШЕ=строка Значение, которое будет передано серверу, если данная кнопка выбрана. Если кнопка не выбрана, значение не передается. Обязательный атрибут.
  • CHECKED Если указан атрибут CHECKED, элемент является выбранным по умолчанию.

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

  • TYPE=RADIO Создает элемент "радиокнопка", существующий только в составе группы подобных элементов, из которых может быть выбран только один. Все элементы группы должны иметь одинаковое значение атрибута NAME. Отображается в виде круглой кнопки. Дополнительные атрибуты:
  • VALUE=Cтpoкa Обязательный атрибут, значение которого передается серверу при выборе данной кнопки. Должен иметь уникальное значение для каждого члена группы.
  • CHECKED Устанавливает элемент выбранным по умолчанию. Один и только один элемент в группе должен иметь этот атрибут.
  • TYPE=SUBMIT Создает кнопку передачи, нажатие которой вызывает пересылку на сервер всего содержимого формы. По умолчанию отображается в виде прямоугольной кнопки с надписью Submit. Дополнительный атрибут
  • УАШЕ=яазвание_кнолки позволяет изменить надпись на кнопке. Атрибут NAME для данного элемента может быть опущен. В этом случае значение кнопки не включается в список параметров формы и не передается на сервер. Если атрибуты
  • NAME И VALUE Присутствуют, Например,

<INPUT TYPE=SUBMIT NAME="submit_button" VALUE="OK">,

то в список параметров формы, передаваемых на сервер, включается параметр submit_button="OK". Внутри формы могут существовать несколько кнопок передачи.

  • TYPE=RESET Создает кнопку сброса, нажатие которой отменяет все сделанные изменения, восстанавливая значения полей формы на тот момент, когда она была загружена. По умолчанию отображается в виде прямоугольной кнопки с надписью Reset. Надпись можно изменить при помощи дополнительного атрибута
  • ЧКШЕ.=название_кнопки Значение кнопки Reset никогда не пересылается на сервер, поэтому у нее отсутствует атрибут NAME.
  • TYPE=IMAGE Создает элемент в виде графического изображения, действующий аналогично кнопке Submit. Дополнительные атрибуты:
  • ЗКС=иг!_изображения Задает ссылку (url) на файл с графическим изображением элемента.
  • КЬТ.СП=тип_въ1равнивания Задает тип выравнивания изображения относительно текущей строки текста точно так же, как одноименный атрибут тэга <IMG>.

Если на изображении элемента щелкнуть мышью, то координаты указателя мыши в виде NAME.x=n&NAME.y=m включаются браузером в список параметров формы, посылаемых на сервер.

  • TYPE=HIDDEN Создает скрытый элемент, не отображаемый пользователю. Информация, хранящаяся в скрытом поле, всегда пересылается на сервер и не может быть изменена ни пользователем, ни браузером. Скрытое поле можно использовать, например, в следующем случае. Пользователь заполняет форму и отправляет ее серверу. Сервер посылает пользователю для заполнения вторую форму, которая частично использует информацию, содержащуюся в первой форме. Сервер не хранит историю диалога с пользователем. Он обрабатывает каждый запрос независимо и при получении второй формы не будет знать, как она связана с первой. Чтобы повторно не вводить уже введенную информацию, можно заставить CGI-програм-му, обрабатывающую первую форму, переносить необходимые данные в скрытые поля второй формы. Они не будут видимы пользователем и, в то же время, доступны серверу. Значение скрытого поля определяется атрибутом VALUE.

Тэг <SELECT>

<SELECT NAME=HM*_n<afl# SIZE=n MULTIPLE>

элементы OPTION </SELECT>

Тэг <SELECT> предназначен для того, чтобы организовать внутри формы выбор из нескольких вариантов без применения элементов ввода типа CHECKBOX и RADIO. Дело в том, что если элементов выбора много, то представление их в виде переключателей и радиокнопок увеличивает размеры формы, делая ее труднообозримой. С помощью тэга <SELECT> варианты выбора более компактно представляются в окне браузера в виде элементов ниспадающего меню или списка прокрутки. Тэг имеет следующие атрибуты.

  • NAME= строка Обязательный атрибут. При выборе одного или нескольких элементов формируется список выбранных значений, который передается на сервер под именем NAME.
  • SIZE=n Устанавливает число одновременно видимых элементов выбора. Если п=1, то отображается ниспадающее менй, если п>1, то список прокрутки с п одновременно видимыми элементами.
  • MULTIPLE Означает, что из меню или списка можно выбрать одновременно несколько элементов. Если этот атрибут задан, то список выбора ведет себя как группа переключателей CHECKBOX, если не задан — как группа радиокнопок RADIO.

Элементы меню задаются внутри тэга <SELECT> при помощи тэга <OPTION>:

<OPTION SELECTED УАШЕ=строка>содержимое_тэга</ОРТION>

Закрывающий тэг </OPTION> не используется. Атрибут VALUE содержит значение, которое пересылается серверу, если данный элемент выбран из меню или списка. Если значение этого атрибута не задано, то по умолчанию оно устанавливается равным содержимому тэга <OPTION>. Например, элементы

<OPTION VALUE=Red>Red <OPTION>Red

имеют одно значение Red. В первом случае оно установлено явно при помощи атрибута VALUE, во втором — по умолчанию. Атрибут SELECTED изначально отображает элемент как выбранный.

Тэг <TEXTAREA>

<TEXTAREA НАМЕ=имя ROWS=m COLS=n>

текст

</TEXTAREA>

Создает внутри формы поле для ввода многострочного текста, отображаемое в окне браузера в виде прямоугольной области с горизонтальной и вертикальной полосами прокрутки. Для пересылки на сервер каждая введенная строка дополняется символами %OD%OA (ASCII-символы "Возврат каретки" и "Перевод строки" с предшествующим символом %), полученные строки объединяются в одну строку, которая и отправляется на сервер под именем, задаваемым атрибутом NAME. Атрибуты:

  • NAME ' __ Необходимый атрибут, используемый для идентификации данных при пересылке на сервер.
  • COLS=n Задает число столбцов видимого текста.
  • ROWS=n Задает число строк видимого текста.

Между тэгами <textarea> и </textarea> можно поместить текст, который будет отображаться по умолчанию.

Пример формы

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

<htmlxheadxtitle>пpимep формы</^д^1ех/пеаЗ>

<body>

<b2>Регистрационная страница Клуба любителей фантастики</b2>

Заполнив анкету, вы сможете пользоваться нашей электронной библиотекой.

<br>

<form method="get" action="/cgi-bin/registrar.cgi">

<pre>

Введите регистрационное имя: <input type="text" name="regnarae">

Введите пароль: <input type="password" name="passwordl" max-length=8>

Подтвердите пароль: <input type="password" name="password2" max-length=8>

</pre>

Ваш возраст:

<input type="radio" name="age" value="lt20" checked >До 20

<input type="radio" name="age" value="20_30">20-30

<input type="radio" name="age" value="30_50">30-50

<input type="radio" name="age" value="gt50">CTapiue 50

<brxbr>

На каких языках читаете:

<input type="checkbox" name="language" value="russian" checked>pycciorii

<input type="checkbox" name="language" ^а1ие="english">английский

<input type="checkbox" name="language" Уа1ие="£гепсЬ">французский

<input type="checkbox" name="language" value="germnan">немецкий

<brxbr>

Какой формат данных является для Вас предпочтительным

<br>

<select name="format" size=2 >

<option selected value="HTML">HTML

<option value="Plain text">Plain text

<option value="PostScript">PostScript

<option value="PDF">PDF </select> <brxbr>

Ваши любимые авторы: <br> <textarea name="wish" cols=40 rows=3>

</textarea> <brxbr>

<input type="submit" value="OK"> <input type="reset" уа!ие="0тменить">

</form>

</body> \

</html>

Данная форма содержит:

  • текстовое поле для ввода регистрационного имени пользователя;
  • текстовое поле для ввода пароля, отображаемого в окне символами *;
  • текстовое поле для подтверждения пароля, также отображаемого символами *;
  • группу радиокнопок для указания возраста пользователя (единственный выбор);
  • группу переключателей для указания языков, которыми владеет пользователь (множественный выбор);
  • список прокрутки для указания предпочтительного формата данных (выбор из ограниченного списка);
  • блок ввода многострочного текста для перечисления любимых авторов (неизвестное заранее количество строк);
  • кнопку передачи с меткой ОК (у этого элемента отсутствует атрибут NAME, он не нужен, так как в данном примере всего одна кнопка передачи, а, значит, CGI-программе нет необходимости определять, от какой именно кнопки поступила команда передачи данных);
  • кнопку сброса с меткой Отменить.

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

1. Информация кодируется и пересылается на Web-сервер, который передает ее для обработки CGI-программе.

2. CGI-программа обрабатывает полученные данные, возможно, обращаясь за помощью к другим программам, выполняющимся на том же компьютере, и генерирует новый "виртуальный" HTML-документ, либо определяет ссылку на уже имеющийся.

3. Новый HTML-документ или ссылка передаются CGI-Программой Web-серверу для возврата клиенту.

Рассмотрим эти шаги более подробно.


Глава 1

Передача информации CGI-программе

Как мы уже знаем, существуют два метода кодирования информации, содержащейся в форме: стандартный метод application/x-www-form-urlencoded, используемый по умолчанию, и дополнительный multipart/form-data. Второй метод нужен только в том случае, если к содержимому формы присоединяется локальный файл, выбранный при помощи элемента формы < INPUT TYPE=FILE>. В остальных случаях следует использовать метод кодирования по умолчанию.

Схема кодирования application/x-www-form-urlencoded одинакова для обоих методов пересылки GET и POST и заключается в следующем.

Для каждого элемента формы, имеющего имя, заданное атрибутом МИМЕ, формируется пара "name=value", где value — значение элемента, введенное пользователем или назначенное по умолчанию. Если значение отсутствует, соответствующая пара имеет вид "пате—'. Для радиокнопок и переключателей используются значения только выбранных элементов. Если элемент выбран, а значение атрибута VALUE не определено, по умолчанию используется значение "ON".

Все пары объединяются в строку, в качестве разделителя служит символ &. Так как имена и значения представляют собой обычный текст, то они могут содержать символы, недопустимые в составе URL (метод GET пересылает данные как часть URL). Такие символы заменяются последовательностью, состоящей из символа % и их шестнадцатеричного ASCII-кода. Символ пробела может заменяться не только кодом %20, но и знаком + (плюс). Признак конца строки, встречающийся в поле TEXTAREA, заменяется кодом %OD%OA. Такое кодирование называется URL-кодированием.

Закодированная информация пересылается серверу одним из методов GET или POST. Основное отличие заключается в том, как метод передает информацию CGI-программе.

При использовании метода GET данные формы пересылаются серверу в составе URL запроса, к которому добавляются после символа ? (вспомним, что запрос — это формализованный способ обращения браузера к Web-серверу). Тело запроса в этом случае является пустым. Для формы из примера 15.1 запрос выглядит следующим образом:

GET /cgi-bin/registrar.cgi? regname = bobSpasswordl = rumataSpassword2 = rumata&age = lt20&language = russian&format = HTML&wish = %F6%C5%CC%Dl%DA%CE%D9 HTTP/1.0

(заголовки запроса, сообщающие серверу, информацию о клиенте) <пусто> (тело запроса). Часть URL после символа "?" называется строкой запроса. Web-сервер, получив запрос, присвоит переменной среды QUERY_STRING значение строки запроса и вызовет CGI-программу, обозначенную в первой части URL до символа "?": /cgi-bin/registrar.cgi. CGI-Программа registrar.cgi сможет затем обратиться к переменной QUERY_STRING для обработки закодированных в ней данных.

Обратите внимание на то, что данные, введенные в поле типа PASSWORD, передаются открытым текстом без шифрования. При передаче данных методом GET они в составе URL помещаются в файл регистрации доступа access.log, обычно открытый для чтения всем пользователям. Таким образом "секретные" данные, введенные в поле типа PASSWORD, оказываются доступными посторонним.

Замечание

Метод GET позволяет передавать данные CGI-программе вообще без использования форм. Информацию, содержащуюся в приведенном выше URL, можно передать при помощи следующей гиперссылки, помещенной в HTML-документ: <А HREF=" http://www.domain/cgi-bin/registrar.cgi?regname= 4>bobspasswordl=rumata&password2=rimata&age=lt20slanguage=russian& 4>format=HTML&wish=%F6%C5%CC%Dl%DA%CE%D9 ">СС1-программа</А>, заменив в этом фрагменте символ "&" его символьным примитивом &#38 или &amp для правильной интерпретации браузером.

К сожалению, эта информацдагявдяется статической. Форма же позволяет менять данные.

Строка запроса — не единственный способ передачи данных через URL. Другим способом является дополнительная информация о пути (extra path information), представляющая собой часть URL, расположенную после имени CGt-программы. Сервер выделяет эту часть и сохраняет ее в переменной среды PATH_INFO. CGI-программа может затем использовать эту переменную для извлечения данных. Например, URL

http://www.domain/cgi-bin/registrar.cgi/

4>regname = bobspasswordl = rumataspassword2 = r\amata&age = lt20&language = 4>russian&format = HTML&wish = %F6%C5%CC%Dl%DA%CE%D9

содержит уже знакомые нам данные, но не в виде строки запроса, а в виде дополнительной информации о пути. При получении запроса с таким URL сервер сохранит данные в переменной среды

PATH_INFO = /regname = bobspasswordl = rumataspassword2 = rumata&age = l blt20&language = russian&format = HTML&wish = %F6%C5%CC%Dl%DA%CE%D9"

Название объясняется тем, что обычно этим способом передается информация о местоположении какого-либо файла (extra path information). Например, URL

http://www.domain/cgi-bin/registrar.cgi/texts/jdk_doc.txt

содержит дополнительную информацию PATH_iNFO=/texts/jdk_doc.txt" о местонахождении файла jdk_doc.txt относительно корневого каталога дерева документов. Другая переменная среды PATHJTRANSLATED содержит информацию об абсолютном местоположении файла в файловой системе, например,

PATH_TRANSLATED="/hcme/httpd/docs/texts/jdk_doc.txt"

а переменная DOCUMENT_ROOT содержит путь к корневому каталогу дерева документов, В нашем случае DOCUMENT_ROOT="/home/httpd/docs/".

При использовании метода POST данные формы пересылаются серверу в теле запроса. Если в примере 15.1 вместо метода GET использовать метод POST

<form method="post" action="/cgi-bin/registrar.cgi">,

то запрос клиента будет иметь следующий вид:

POST /cgi-bin/registrar.cgi HTTP/1.1

(заголовки запроса, сообщающие серверу информацию о клиенте)

Content-length: 126

regname=bob&passwordl=rumata&password2=ruinata&age=lt20&language=russian& 4>format=HTML&wish=%F6%C5%CC%Dl%DA%CE%D9

В этом фрагменте среди прочих заголовков выделен заголовок content-length, сообщающий серверу количество байт, переданных в теле запроса. Это значение сервер присваивает переменной среды CONTENT_LENGTH, а данные посылает в стандартный ввод CGI-программы.

Методы GET и POST имеют свои достоинства и недостатки. Метод GET обеспечивает лучшую производительность при пересылке форм, состоящих из небольшого набора коротких полей. При пересылке большого объема данных следует использовать метод POST, так как браузер или сервер могут накладывать ограничения на размер данных, передаваемых в составе URL, и отбрасывать часть данных, выходящую за границу. Метод POST, к тому же, является более надежным при пересылке конфиденциальной информации.


Глава 1

CGI-сценарии

Назначение CGI-программы — создать новый HTML-документ, используя данные, содержащиеся в запросе, и передать его обратно клиенту. Если такой документ уже существует, то передать ссылку на него. Какой язык можно использовать для написания CGI-программ? Сам интерфейс CGI не накладывает ограничений на выбор языка программирования. Зная, какую задачу решает CGl-программа и каким образом она получает входную информацию, мы можем назвать свойства, которыми должен обладать язык CGI-программирования.

  • Средства обработки текста. Необходимы для декодирования входной информации, поступающей в виде строки, состоящей из отдельных полей, разделенных символами-ограничителями.
  • Средства доступа к переменным среды. Необходимы, так как с помощью переменных среды данные передаются на вход CG.I-программы.
  • Возможность взаимодействовать с другими программами. Необходима для обращения к СУБД, программам обработки графики и другим специальным программам.

Выбор языка зависит и от операционной системы Web-сервера. Большая часть имеющихся серверов предназначена для работы под управлением операционной системы UNIX. Учитывая эти соображения, мы можем заключить, что язык Perl, обладающий развитыми средствами обработки текста и создания сценариев, первоначально созданный для работы в ОС UNIX и перенесенный на множество других платформ, является наиболее подходящим средством создания сценариев CGI. Кроме того, CGI-програм-мирование на языке Perl имеет поддержку в виде готовых модулей CPAN, свободно доступных в сети Internet.

CGI-сценарий на языке Perl — это программа, имеющая свою специфику. Она, как правило, генерирует HTML-документ, посылаемый клиенту в виде ответа сервера. Ответ сервера, так же, как и запрос клиента, имеет определенную структуру. Он состоит из следующих трех частей:

1. Строка состояния, содержащая три поля: номер версии протокола HTTP, код состояния и краткое описание состояния, например:

НТТР/1.0 200 OK f запрос клиента обработан успешно

НТТР/1.0 404 Not Found # Документ по указанному адресу не существует

2. Заголовки ответа, содержащие информацию о сервере и о возвращаемом HTML-документе, например:

Date: Mon, 26 Jul 1999 18:37:07 GMT # Текущая дата и время

Server: Apache/1.3.6 ::,, •.# Имя и номер версии сервера

Content-type: text/html tt Описывает медиа-тип содержимого

3. Содержимое ответа — HTML-документ, являющийся результатом выполнения CGI-программы.

CGI-программа передает результат своей работы (HTML-документ) серверу, который возвращает его клиенту. При этом сервер не анализирует и не изменяет полученные данные, он может только дополнять их некоторыми заголовками, содержащими общую информацию (например, текущая дата и время) и информацию о самом себе (например, имя и версия сервера). Информация о содержимом ответа формируется CGI-программой и должна содержать как минимум один заголовок, сообщающий браузеру формат возвращаемых данных:

Content-type: text/html

Замечание

Информацию о заголовках можно найти в спецификации протокола HTTP. Мы же ограничимся еще одним примером. Если в качестве ответа клиенту посылается статический документ, например, подтверждение о получении заполненной формы, то неэффективно каждый раз создавать его заново. Лучше создать один раз и сохранить в файле. В этом случае CGI-сценарий вместо заголовка Content-type: media-type, описывающего формат данных, формирует заголовок Location: URL, указывающий серверу местонахождение документа, который следует передать клиенту.

Заголовки отделяются от содержимого документа пустой строкой.

Напишем простейший CGI-сценарий, посылающий пользователю HTML-страницу с приветствием

#!/usr/bin/perl

print "Content-type:text/html\n\n";

print "<html><head><title>HELLO</titlex/head>\n";

print "<body>\n";

print "<h2>Bac приветствует издательство ВХВ - Санкт-Петербург</п2>\п"; print "</bodyx/html>\n";

Если поместить файл hello.cgi в каталог CGI-программ Web-сервера, а затем обратиться к нему из браузера, то браузер отобразит HTML-документ, созданный программой hello.cgi (рис. 15.2).

Замечание

Большинство Web-серверов по умолчанию предполагают, что файлы GGi-сце-нариев находятся в специальном каталоге, обычно называемом cgi-bin. Можно настроить сервер таким образом, чтобы все файлы, находящиеся в определенном каталоге, он воспринимал не как обычные документы, а как выполняемые сценарии. Можно также указать серверу, что все файлы с определенным расширением (например, .cgi) должны рассматриваться как CGI-сценарий. Когда пользователь открывает URL, ассоциированный с CGI-программой. клиент посылает запрос серверу, запрашивая файл. Сервер распознает, что запрошенный адрес является адресом CGI-программы, и пытается выполнить эту программу. Подробности конфигурирования Web-серверов можно найти в соответствующей литературе и документации на конкретный сервер.

Рис 15.2. Web-страница, сформированная программой hello.cgi

Переменные среды CGI

В зависимости от метода данные формы передаются в CGI-программу или через стандартный ввод (POST), или через переменную среды QUERY_STRING (GET). Помимо этих данных CGI-программе доступна и другая информация, поступившая от клиента в заголовках запроса или предоставленная Web-сервером. Эта информация сохраняется в переменных среды UNIX. С некоторыми из них мы уже познакомились ранее. В табл. 15.1 перечислены переменные, обычно используемые в CGI.

Таблица 15.1. Переменные среды CGI

Переменная среды | Описание
GATEWAYJNTERFACE | Версия CGI, которую использует сервер
SERVER_NAME | Доменное имя или IP-адрес сервера
SERVER_SOFTWARE | Имя и версия программы-сервера, отвечающей на запрос клиента (например, Apache 1 .3)
SERVER_PROTOCOL | Имя и версия информационного протокола, который был использован для запроса (например, HTTP 1 .0)
SERVER_PORT | Номер порта компьютера, на котором работает сервер (по умолчанию 80)
REQUEST_METHOD | Метод, использованный для выдачи запроса (GET, POST)
PATHJNFO | Дополнительная информация о пути
PATHJRANSLATED | Та же информация, что и в переменной PATHJNFO с префиксом, задающим путь к корневому каталогу дерева Web-документов
SCRIPT_NAME | Относительное маршрутное имя CGI-сценария (например, /cgi-bin/program.pl)
DOCUMENT_ROOT | Корневой каталог дерева Web-документов
QUERY_STRING | Строка запроса— информация, переданная в составе URi запроса после символа "?"
REMOTE_HOST | Имя удаленной машины, с которой сделан запрос
REMOTE_ADDR | IP-адрес удаленной машины, с которой сделан запрос
REMOTE_USER | Идентификационное имя пользователя, посылающего запрос
CONTENT_TYPE | Медиа-тип данных запроса, например, "text/html".
CONTENT_LENGTH | Количество байт в теле запроса, переданных в CGI-программу через стандартный ввод
HTTP_HOST | Хост-имя компьютера, на котором работает сервер
HTTP_FROM | Адрес электронной почты пользователя, направившего запрос
HTTP_ACCEPT | Список медиа-типов, которые может принимать клиент
HTTP_USER_AQENT | Браузер, которым клиент пользуется для выдачи запроса
HTTP_REFERER | URL документа, на который клиент указывал перед обращением к CGI-программе

Имена переменных среды CGI на разных Web-серверах могут различаться. Следует обратиться к документации на соответствующий сервер.

CGI-программа на языке Peri имеет доступ к переменным среды через специальный предопределенный хеш-массив %ENV, к элементам которого можно обратиться по ключу, совпадающему с именем переменной среды. Ниже приведены пример CGI-сценария, формирующего HTML-документ с информацией о всех установленных переменных среды, и отображение этого документа в окне браузера.

#!/usr/bin/perl

print "Content-type:text/html\n\n";

print "<html>\n";

print "<head><title>Преременные cpeды</titlex/head>\n";

print "<bоdу><h2>Переменные среды</h2>\n";

print "<hrxpre>\n";

foreach $name (sort(keys %ENV))

{

print "$name: $ENV($name}\n";

}

print "<hrx/pre>\n";

print "</bodyx/html>\n";

Рис 15.3. Информация о переменных среды CGI

Обработка данных формы

Данные формы поступают в CGI-программу в закодированном виде, поэтому в качестве первого шага CGI-сценарий должен выполнить декодирование полученной информации. При пересылке данных методом GET данные формы присваиваются переменной среды QUERY_STRING, при передаче методом POST — передаются в программу через стандартный ввод и тоже могут быть присвоены некоторой внутренней переменной. Таким образом, декодирование данных сводится к следующей последовательности манипуляций со строкой:

  • замена каждой группы %hh, состоящей из шестнадцатеричного ASCII-кода hh с префиксом %, на соответствующий ASCII-символ;
  • замена символов "+" пробелами;
  • выделение отдельных пар имя=знанение> разделенных ограничителем &;
  • выделение из каждой пары имя=значение имени и значения соответствующего поля формы.

Программа декодирования HTML-формы может выглядеть, например, так:

#!/usr/bin/perl

# Декодирование данных формы, переданных методом GET $form_data = $ENV{'QUERY_STRING'};

# преобразование цепочек %hh в соответствующие символы $form_data-=- s/%(..)/pack ("С", hex ($1))/eg; i преобразование плюсов в пробелы $form_data =~ tr/+/ /;

# разбиение на пары имя=значение @pairs = split (/&/, $form_data);

# выделение из каждой пары имени и значения поля формы и сохранение

# их в ассоциативном массиве $fom_fields

foreach $pair (@pairs)

{

($name, $value)=split(/=/,$pair);

$ form_fields{$name}=$value; }

Если данные формы переданы методом POST, то в приведенном тексте следует заменить оператор присваивания

$form_data = $ENV{'QUERY_STRING'};

оператором

read(STDrN,$fom_data,$ENV{'CONTENT_LENGTH' }} ;

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

В приведенном примере используются две новые функции: packQ и hex(). Поясним их назначение прежде, чем перейти к обсуждению текста программы.

Функция

pack template, list

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

а/A Текстовая строка, заполненная нулями/пробелами

b/B Двоичная строка, значения расположены в порядке возрастания/ убывания

с/с Обычное символьное значение/ Символьное значение без знака

f/d Значение в формате с плавающей точкой одинарной/двойной точности

b/n Шестнадцатеричная строка, младший/старший полубайт первый

i/i Целое со знаком/ без знака

I/L Значение типа long со знаком/без знака

П/N Значение типа short/long с "сетевым" порядком байтов ("старший в старшем")

P/U Указатель на строку/Ш-кодированная строка s/s Значение типа short c$> знаком/без знака

v/v Значение типа short/long с VAX-порядком байтов ("старший в младшем")

х/х Нулевой байт/резервная копия байта

@ Заполнение нулевыми байтами (до абсолютной позиции)

За каждым символом может следовать число, обозначающее счетчик применений данного символа в качестве формата. Символ * в качестве счетчика означает применение данного формата для оставшейся части списка.

$х = pack "cccc", 80, .101, 114, 108; $х = pack "c4", 80, 101, 114, 108;

$х = pack "B32", "01010000011001010111001001101100";

$х = pack "H8", "5065726С";

$х = pack "H*", "5065726C"; ' •

$х = pack "сВ8Н2с",80,"01100101", 12, 108;

Значение переменной $х во всех случаях равно "peri". Функция

hex expr

Интерпретирует аргумент ехрг как шестнадцатеричную строку и возвращает ее десятичное значение.

В тексте программы примера 15.4 все представляется очевидным. Разберем только наиболее насыщенную строку

$fonn_data =~ s/%(..)/pack ("С", hex ($l))/eg;

Образец для поиска задан в виде регулярного выражения %(..). Этому образцу удовлетворяет произвольная последовательность вида %ху, где х, у — любые символы. В результате кодирования данных в качестве х, у могут появиться только шестнадцатеричные цифры, поэтому можно не задавать более точный, но менее компактный шаблон %([0-9A-Fa-f][0-9A-Fa-f]j. Часть выражения заключена в скобки (..). При нахождении подходящего фрагмента %hh его часть, содержащая шестнадцатеричное число hh, сохраняется в переменной, которая затем будет использована в качестве аргумента функции hex($ij для преобразования в десятичное значение. Функция pack упакует это десятичное значение в двоичную структуру, которая в соответствии с шаблоном "с" будет интерпретироваться как символ. Этот символ заменяет в тексте найденную цепочку %hh.

После выделения и декодирования данных можно приступить к их обработке. Попробуем написать CGI-сценарий, обрабатывающий данные формы из примера 15.1.

Пример создания собственного CGI-сценария

Программа должна декодировать полученные данные, проверять заполнение обязательных полей формы и правильность подтверждения пароля, в зависимости от результатов проверки формировать документ для отсылки клиенту. Сохраним сценарий в файле /cgi-bin/registrar, cgi. Полный маршрут к данному файлу определяется параметрами конфигурации Web-сервера. Местоположение каталога cgi-bin обычно указывается относительно корня дерева документов Web-сервера, а не корневого каталога файловой системы. Например, если корнем является каталог /home/httpd/htmi/, то файл сценария будет иметь маршрутное ИМЯ /home/httpd/html/cgi-bin

/registrar.cgi, которое в запросе клиента будет указано как /cgi-bin /registrar.cgi. В первом приближении текст сценария может выглядеть следующим образом.

#!/usr/bin/perl

print "Content-type:text/html\n\n"; $method = $ENV{'REQUEST_METHOD'}; if ($method eq "GET") { ^

$form_data = $ENV{'QUERY_STRING'}; } else {

read (STDIN, $form_data, $ENV{'CONTENT_LENGTH'}); }

$form_data =~ s/%(..)/pack ("C", hex ($l))/eg; $form_data =~ tr/+/ /; @pairs = split (/&/, $form_data); foreach $pair (Spairs) {

($name, $value)=split(/=/,$pair);

$FORM{$name}=$value;

Проверка заполнения обязательных полей

if (!$FORM{'regname'} I I !$FORM{'passwordl'}) { print «goback

<html>

<head><title>Невведенные данные </title></head>

<body><h2>Извините, Вы пропустили обязательные данные</п2>

<br>

<а href=" http://www.klf.ru/welcome.Мл11 ">Попробуйте еше раз, пожалуйста</а>

</body>

</html> goback ;}

#Проверка правильности ввода пароля elsif ($FORM{'passwordl 1 } eq $FORM{'password2'}){ print«confirmation <htrml>

<head><title>no3flpaBnHeM!</titlex/head> <Ьойу><h2>Поздравляем! </h2><br>

Ваша регистрация прошла успешно. Вы можете пользоваться нашей библиотекой. Спасибо за внимание.

</body>

</html>

confirmation

;}

else {

print«new_form

<htmlxhead><title>Oira6Ka при вводе napc«w</titlex/head>

<ЬойуХпЗ>Введенные Вами значения пароля не совпадают

<br><form method="get" action="/cgi-bin/registrar.cgi">

<pre>

Введите пароль: <input type="password" name="passwordl">

Подтвердите пароль: <input type="password" name="password2">

</pre>

new_form

foreach $key ( keys %FORM) {

if ($key ne "passwordl" && $key ne "password2") {

print "<input type=\"hidden\" name=$key value=$FORM{$key}>\n";

} } print «EndOfHTML

<br><br>

<input type="submit" value="OK"> <input type="reset" value="Отменить">

<i / form></body></html> EndOfHTML ;)

После вывода строки заголовка осуществляется считывание переданной серверу информации в переменную $form_data. В зависимости от метода передачи, эта информация считывается из переменной среды QUERY_STRING (метод GET) или из стандартного ввода программы (метод POST).

Считанная информация декодируется и помещается в ассоциативный массив %FORM.

Отсутствие обязательных данных — регистрационного имени и пароля — проверяется С ПОМОЩЬЮ УСЛОВИЯ if (! $FORM{' regname'} I I ! $FORM{ 'passwordl'}).

В случае отсутствия необходимых данных формируется виртуальный HTML-документ, предлагающий повторить попытку, который и посылается клиенту (рис. 15.4).

Рис 15.4. Ответ сервера в случае отсутствия обязательной информации

При выводе этого документа в операции print использована конструкция "документ здесь". Она позволяет использовать внутри себя символы, которые при заключении в обычные двойные кавычки необходимо маскировать символом "\", например, сами, двойные кавычки ", символы "@", ••$", "%".

Условие elsif ($FORM{ 'passwordl 1 } eq $FORM{ 'password2 ' }} предназначено для проверки совпадения двух копий введенного пользователем пароля. Если значения совпадают, то пользователю посылается сообщение, подтверждающее успешную регистрацию (рис. 15.5).

Рис 15.5. Подтверждение регистрации

В противном случае формируется HTML-документ, предлагающий ввести пароль повторно (рис. 15.6). Этот новый документ содержит форму, в состав которой входят два видимых поля типа "password" — для ввода и подтверждения пароля, и скрытые поля типа "hidden" — для сохранения остальных данных, введенных при заполнении исходной формы. Каждое скрытое поле новой формы наследует у соответствующего поля исходной формы атрибуты name и value. Если эти данные не сохранить, то их придется вводить заново, принуждая пользователя повторно выполнять уже сделанную работу. Информация, сохраненная в скрытых полях, невидима пользователю и недоступна для изменения.

Рис 15.6. Повторное приглашение для ввода пароля

Культура Perl допускает различные уровни владения языком. В рассмотренном варианте использован минимальный набор средств. Очевидно, что часть кода, например, декодирование, требуется при обработке не только данной, но и любой другой формы. Естественным шагом в развитии исходного варианта сценария является выделение этой части в отдельную подпрограмму и предоставление доступа к ней другим сценариям. Для этого преобразуем исходный текст в соответствии с планом, изложенным в примере 15.7.

1. Часть исходного кода может быть использована другими CGI-программами. Преобразуем ее в отдельный модуль, сохраняемый в файле CGI__UTILS.pm.

package CGI_UTILS; require Exporter;

@ISA = qw(Exporter);

^EXPORT = qw(print_header process_input);

# Подпрограмма вывода заголовка ответа sub print_header {

print "Content-type: text/html\n\n"; } .

# Подпрограмма декодирования данных форьы sub process_input {

my ($form_ref)=(?_.;

my ($ form_data,@pai rs);

my ($temp)="";

if ($ENV{'REQUEST_METHOD'} eq 'POST') {

read(STDIN,$form_data,$ENV{'CONTENT_LENGTH'}); } else {

$form_data=$ENV{'QUERY_STRING'}; }

$form_data=~s/%(..)/pack("c",hex($1))/ge;
$form_data=~tr/+/ /; $form_data=~s/\n/\0/g; @pairs=split(/&/,$form_data);
foreach $item(@pairs) {

($name,$value)=spl£t (/=/,$item) ; if (!defined($forih_ref->{$name})) <

$ form_ref->{Sname}=$value; } else {

$form_ref->{$name} .= "\0$value"; } }

foreach $item (sort keys %$form_ref) { $temp.=$item."=".$form_ref->{$item}."&";} return($temp); } 1;

2. Текст основного сценария обработки формы registrar.cgi преобразуем следующим образом:

#!/usr/bin/perl use cgi_utils;

my 8FORM, $file_rec; $file_rec=&process_input(\%FORM); ^Проверка заполнения обязательных полей

#if {$FORM{'regname'} eq "" || $FORM{'passwordl'} eq "") { if (!$FORM{'regname'} || !$FORMTpasswordl'}) {

print "Location: /goback.html\n\n"; }

#Проверка правильности ввода пароля

elsif ($FORM{'passwordl'} eq $FORM{'password2'}}{

print "Location: /confirmation.html\n\n";

open (OUTF, "»users");

print OUTF $file_rec, H \n";

close OUTF }

else {

&print_header; print«new_form

<html>

<head><title>Ошибка при вводе пароля</titlex/head>

<ЬойухпЗ>Введенные Вами значения пароля не совпадают

<br><forro method="get" action="/cgi-bin/registrar.cgi">

<pre>

Введите пароль: <input type="password" name="passwordl">

Подтвердите пароль: <input type="password" name="password2">

</pre> new_form / foreach $key ( keys %FORM) {

if ($key ne "passwordl" && $key ne "password2") {

print "<input type=\"hidden\' J name=$key value=$FORM{$key}>\n";

} } print«EndOfHTML

<br><br>

<input type="submit" value="OK">

<input type="reset" value="Отменить">

</form>

</body>

</html> EndOfHTML

} exit

3. В исходном варианте сценария в качестве ответов сервера при получении неполных данных и для подтверждения регистрации пользователя формируются виртуальные HTML-документы. В этом нет необходимости, так как они содержат только статическую информацию. Соответствующие фрагменты сценария преобразуем в HTML-код ротовых документов, которые сохраним в отдельных файлах. В основном сценарии в качестве ответа сервера возвращаются ссылки на эти документы.

Файл confirmation.html содержит документ, посылаемый клиенту в качестве сообщения об успешной регистрации:

<html>

<head><title>Пoздpaвляeм!</title></head> <body><h2>Пoздpaвляeм! </h2><br>

Ваша регистрация прошла успешно. Вы можете пользоваться нашей библиотекой. ..- -

<br>

Спасибо за внимание.

</body>

</html>

Файл goback.html содержит документ, посылаемый клиенту при получении неполных данных:

<html>

<head><title>Heпoлныe данные</^^1ех/пеай>

<body><h2>HsBHHMTe, Вы пропустили обязательные данные</h2>

<br>

<а href=" http://www.klf.ru/welcome.Ылп1 ">Попробуйте еще раз, пожалуйста </а>

</body>

</htral>

В приведенном тексте появились некоторые новые элементы, которые необходимо пояснить.

Подпрограмма process_input модуля cgi_utiis.pm передает декодированные данные через вызываемый по ссылке параметр — ассоциативный массив. [Кроме того, она возвращает при помощи функции return () те же данные, но в виде строки, состоящей из пар имя=значение, разделенных символом "&". Обратите внимание на то, как подпрограмма вызывается в основной программе:

$file_rec=sprocess_input(\%FORM);

В качестве аргумента ей передается ссылка на ассоциативный массив. В тексте подпрограммы появилась проверка наличия полей формы с совпадающими именами и разными значениями:

if (!defined($form_ref->{$name})) {

$form_ref->{$name}=$value; }

else { }}

Этот фрагмент необходим для того, чтобы правильно обработать следующую ситуацию из нашего примера. Выбраны несколько переключателей, определяющих языки, которыми владеет пользователь: русский, английский, французский. Так как соответствующие элементы формы имеют одинаковые имена name=language, то без проверки в ассоциативный массив %fcrm_ref, куда помещаются обработанные данные, попадет только информация от последнего обработанного Элемента name=language value=french. В Подобном случае обычное присваивание заменяется операцией присваивания с конкатенацией

$form_ref->{$name} .= "\0$value",

которая к переменной $fom_ref->{$name) добавляет нулевой символ и значение $value.

В основной программе registrar.cgi обратим внимание на то, как передается ссылка на готовый HTML-документ. Для этого вместо заголовка content-type: text/html выводится заголовок Location: URL, сообщающий серверу адрес документа.

Еще один новый элемент в основной программе — сохранение данных в файле с именем users.

Модуль CGI.pm

Пример, рассмотренный выше, демонстрирует наивный подход, когда кажется, что все необходимые программы надо писать самостоятельно с самого начала. Но программирование CGI — это такая область, в которой Per] давно и активно применяется, и многое из того, что может потребоваться, уже давно кем-то написано. Надо только найти и использовать. В. данном разделе мы сделаем краткий обзор одного из таких готовых средств, предназначенных для поддержки разработки CGI-приложений.

Модуль CGI.pm, созданный Линкольном Штейном, входит в состав дистрибутивного комплекта Perl, начиная с версии 5.004, и его даже не нужно специально инсталлировать.

Этот модуль содержит большой набор функций для создания и обработки HTML-форм. Мы посвятили значительную часть предыдущего раздела изучению многочисленных тэгов, чтобы затем написать HTML-код для создания формы в примере 15.1. Модуль CGI позволяет сделать то же самое, но без использования HTML. С его помощью можно описать форму на языке Perl, используя вместо тэгов обращения к функциям модуля. В результате получится не документ HTML, а сценарий на языке Perl, который при вызове будет "на лету" генерировать HTML-форму и передавать серверу для отправки клиенту.

Модуль CGI является не просто модулем, а классом, что позволяет использовать преимущества объектно-ориентированного подхода. Модуль предоставляет пользователю на выбор два вида интерфейса с самим собой: процедурно-ориентированный и объектно-ориентированный.

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

#!/usr/bin/perl ; use CGI qw/:standard/; \ print header(), \

start_html('Пример формы'),

hi('Пример формы'),

Директива use импортирует в пространство имен вызывающей программы некоторый стандартный набор функций. Помимо него, существуют другие наборы функций модуля CGI. Их можно импортировать, указав имя соответствующего набора в списке импорта директивы use. Имена всех наборов можно просмотреть в файле CGI.pm, где они содержатся в хеш-массиве

%EXPORT_TAGS,

Функции header (), start_html О , hi () ЯВЛЯЮТСЯ функциями модуля CGI. Они будут рассмотрены ниже.

При использовании объектно-ориентированного интерфейса в директиве use вызывающей программы не нужно указывать список импортируемых имен функций. В этом случае взаимодействие с модулем CGI осуществляется через объект класса CGI, который нужно создать в вызывающей программе при помощи конструктора new (). Объектно-ориентированный вариант приведенного выше фрагмента выглядит следующим образом:

#!/usr/bin/perl

use CGI;

Squery = new CGI;

print $query->header(),

$query->start_html {'Пример формы'),

$query->hl ('Пример формы' ) ,

Замечание

Функции модуля CGI.pm являются методами класса CGI. Для того чтобы их можно было вызывать и как функции, и как методы, синтаксис не требует в качестве обязательного первого параметра указывать объект класса CGI. Поэтому в качестве функций к ним можно обращаться обычным образом, а как к объектам — только используя форму $object->method ().

Модуль CGI, как мы отметили выше, содержит большой набор методов, и в наши планы не входит их подробное изучение. Документация, входящая в состав самого модуля, достаточно подробно описывает его компоненты. Чтобы получить представление о работе модуля CGI, создадим с его помощью небольшой сценарий. Для этого вернемся к рассмотрению формы из примера 15.1.

Будем для определенности использовать традиционный процедурно-ориентированный интерфейс. Рассмотрим следующий сценарий.

#!/usr/bin/perl

use CGI qw(:standard);

print header;

print start_html('Пример формы'),

h2('Регистрационная страница Клуба любителей фантастики'),

'Заполнив анкету, вы сможете пользоваться нашей электронной

"^библиотекой.',

br,

start_form,

"Введите регистрационное имя:",textfield('regname'),

P.

"Введите пароль: ", password_field(-name=>'passwordl',

-maxlength=>'8'),

Р,

"Подтвердите пароль: ", password_field(-name=>'password2',

-maxlength=>'8'),

Р/-

"Ваш возраст",

Р'

radio_group(-name=>'age',

-value=>['lt20','20_30', '30_50', 'gt50'],

-default=>'Lt20',

-labels=>{'It20'=>'flo 20', I 20_30'=>'20-30', 4> '30_50'=>'30-50','gt50'=>'старше 50'}),

br,br,

"На каких языках читаете:",

checkbox_group(-name=>'language', 4> -values=>

4>

['русский', 'английский' ,'французский', 'немецкий'],

^ -defaults=>['русский']), br,br,

"Какой формат данных является для Вас предпочтительным ", Ьг, popup_menu(-name=>'type',

-values=>['Plain text','PostScript','РОГ']), br,br, \

N

"Ваши любимые авторы:", x -~-_.

br,

textarea(-name=>'wish', -cols=>40, -rpws=>3),

br,

subrai t{-name=>'OK'), reset{-name=>'Отменить'),

end_form,

hr;

if (paramO) { print

"Ваше имя: ",em(param('regname')),

P,

"Ваш возраст: ", em(param('age')),

P, '

J

"Вычитаете на языках: ",em(join(", ",param('language'))),

P,

"Предпочтительный формат данных для Вас: ",em(param ('type')),

P,

"Ваши любимые авторы: ", em(join(", ",param('wish 1 ))),

hr; } print end_html;

Обсудим приведенный текст. Директива use, как мы отметили выше, осуществляет импорт стандартного набора функций модуля CGI.pm в пространство имен вызывающего пакета. В самом сценарии на месте тэгов исходного HTML-кода стоят обращения к функциям модуля: каждому тэгу соответствует вызов функции. Вызов функции модуля CGI можно осуществлять двумя способами: с использованием позиционных параметров print textfield('regname','начальное значение',50,80); с использованием именованных параметров

print textfield(-name=>'regname',

-default=>'начальное значение",

-size=>50,

-maxlength=>80);

Обработка позиционного параметра внутри функции зависит от его места в списке параметров. Обработка именованного параметра не зависит от его места в списке параметров. Функции модуля CGI могут иметь большое число параметров, порядок следования которых трудно запомнить, поэтому в этом модуле была реализована возможность вызова функций с именованными параметрами. Кроме того, применение именованных параметров делает текст программы более понятным. В тексте примера функции вызываются с именованными параметрами, если параметров больше одного. Познакомимся с функциями, использованными в примере.

  • Функция header о без параметров создает для виртуального ответа сервера стандартный HTTP-заголовок Content-Type: text/html и вставляет после него необходимую пустую строку. Параметры позволяют задать дополнительную информацию для заголовка, например, указать другой медиа-тип содержимого или код ответа, посылаемый браузеру:
    print header(-type=>'image/gif',
    -status=>'404 Not Found 1 );
  • Функция start_htmi () создает HTML-заголовок и начальную часть документа, включая открывающий тэг <вооу>. При помощи параметров функции можно задать дополнительные тэги внутри тэга <HEAD>, а также значения атрибутов. Все параметры являются необязательными. В примере функция start_htmio вызвана с одним позиционным параметром, определяющим название документа.

Модуль CGI содержит методы (функции) для поддержки многих тэгов HTML2, HTML3, HTML4 и расширений, используемых в браузерах Netscape. Тэгам соответствуют одноименные методы модуля CGI.pm, имена которых записываются при помощи символов нижнего регистра. Если при этом возникают конфликты имен, в названия методов следует вводить символы верхнего регистра, как, например, в следующих случаях.

1. Название тэга <TR> совпадает с именем встроенной функции tr(). Имя соответствующего метода записывать в виде TR () или тг ().

2. Название тэга <PARAM> совпадает с именем собственного метода модуля CGI paramo. Для обозначения метода, соответствующего тэгу, использовать ИМЯ РАКАМ ().

3. Название тэга <SELECT> совпадает с именем встроенной функции select (). Для обозначения метода использовать имя Select ().

4. Название тэга <зив> совпадает с именем ключевого слова объявления функции sub. Для обозначения метода использовать имя sub ().

Тэгам, имеющим атрибуты, соответствуют методы, имеющие в качестве первого аргумента ссылку на анонимный хеш-массив. Ключами этого хеш-массива являются имена атрибутов тэга, а значениями — значения атрибутов.

Методы, соответствующие тэгам, и методы, предназначенные длячгенерирова-ния других элементов HTML-документа, возвращают строки, содержащие соответствующие элементы. Чтобы эти строки попали в создаваемый документ, их нужно вывести, как это делается в примере при помощи функции print.

В примере использованы следующие методы, соответствующие тэгам HTML.

  • Функция Н2 соответствует тэгу <Н2>. Она определяет, что ее аргумент является в документе заголовком второго уровня.
  • Функция Ьг соответствует тэгу <BR> и обозначает, что последующий текст размещается с начала новой строки.
  • Функция р соответствует тэгу <р> и обозначает начало абзаца.
  • Функция hr соответствует тэгу <т> и обозначает горизонтальную линию, разделяющую документ на части.
  • Функция em соответствует тэгу <ет> и обозначает, что ее аргумент в документе должен быть выделен курсивом.

Следующие функции используются для создания формы и ее элементов.

  • Функция start_form соответствует тэгу <FORM>. Она может иметь три параметра start_form(-method=>$method,
    -action=>$action,
    -encoding=>$encoding);
    при помощи которых можно задать метод передачи формы Web-серверу (-method), программу, предназначенную для обработки формы (-action), и способ кодирования данных (-encoding). Все параметры являются необязательными. По умолчанию используются значения
    method: POST;
    action: данный сценарий;
    encoding: application/x-www-form-urlencoded. П Функция end_form создает закрывающий тэг </FORM>.
  • Функция textfieid соответствует тэгу <INPUTE TYPE=TEXT>. Она имеет следующий синтаксис
    textfieid (-name=>' field__name',
    -default=>'starting value',
    -size=>50,
    -maxlength=>80);
    Параметры соответствую^ атрибутам тэга. Обязательным является первый параметр. /
    х. __ _ _^/
  • Функция password_fieid соответствует тэгу <INPUTE TYPE=PASSWORD>. Ее синтаксис:
    password_field(-name=>'secret',
    -value=>'starting value',
    -size=>8,
    -maxlength=>12);
    Параметры имеют тот же смысл, что и одноименные атрибуты соответствующего тэга. Обязательным является первый параметр.
  • Функция radio_group служит для создания группы "радиокнопок" — элементов, задаваемых тэгом <INPUTE TYPE=RADIO>. Ее синтаксис имеет следующую форму
    radio_group(-name=>'group_name',
    -values=>['bim',Jbam','bom'],
    -default=>'bom',
    -linebreak=>'true',
    -lab.els=>\%labels) ;
    Первый аргумент является обязательным, соответствует одноименному атрибуту тэга. Второй аргумент тоже обязательный и задает значения элементов. Эти значения отображаются в качестве названий кнопок. Он должен быть ссылкой на массив. Остальные аргументы являются необязательными. Третий аргумент задает кнопку, которая выбрана по умолчанию. Если значение четвертого аргумента 'true 1 , каждая следующая кнопка группы размещается в начале новой строки. Пятым аргументом является ссылка на хеш-массив, который связывает значения, присвоенные кнопкам, с метками, которые отображаются в виде названий кнопок. Если аргумент не задан, то в качестве названий отображаются сами значения.
  • Функция checkbox_group служит для создания группы элементов-переключателей, задаваемых тэгом <INPUTE TYPE= CHECKBOX>.
    checkbox_group(-name=>'group_name',
    -values=>['bim','bam','bom'],
    -default=>['bim','bom'],
    -linebreak=>'true',
    -labels=>\%labels);
    Аргументы имеют тот же смысл, что и одноименные аргументы функции radio_group. Поскольку в группе переключателей можно одновременно выбрать несколько элементов, третий аргумент может быть или одиночным элементом, или ссылкой на массив, содержащий список значений, выбранных по умолчанию. Обязательными являются первый и второй аргументы.
  • Функция popup_menu служит для создания меню, задаваемого при помощи тэга <SELECT>. Имеет следующий синтаксис:
    popup_menu(-name=>'menu_name',
    -values=>['bim', 'bam','bom'],
    -default=>'bom r ,
    -labels=>\%labels);
    Первый аргумент задает имя меню. Второй аргумент является ссылкой на массив, содержащий список значений, присвоенных элементам меню. Первый и второй аргументы обязательны, остальные — нет. Третий аргумент задает элемент меню, выбранный по умолчанию. Четвертый 1 аргумент является ссылкой на хеш-массив. Хеш-массив значению каждого элемента меню ставит в соответствие строку, которая будет отображаться в меню для этого элемента. Если четвертый аргумент отсутствует, то для каждого элемента меню отображается его значение, заданное вторым аргументом.
  • Функция textarea соответствует тэгу <TEXTAREA>, задающему в документе текстовое поле для ввода многострочного текста. Имеет следующий синтаксис
    textarea(-name=>'region',
    -default=>'starting value',
    -rows=>10,
    -columns=>50);
    Первый параметр, задающий имя элемента формы <ТЕХТДКЕА>, является обязательным, остальные — нет. Второй параметр задает строку, отображаемую по умолчанию. Третий и четвертый параметры задают соответственно число строк и столбцов, отображаемых в текстовом поле.
  • Функция submit соответствует тэгу <INPUT TYPE=SUBMIT>, задающему кнопку передачи. Ее синтаксис:
    print $query->submit(-name=>'button_name', -value=>'value');
    Первый параметр является необязательным. Он задает имя кнопки, которое отображается в качестве ее названия. Нужен только для переопределения названия Submit и в тех случаях, когда надо различать несколько имеющихся кнопок передачи. Второй параметр тоже необязательный. Он задает значение, которое посылается в строке запроса при щелчке на этой кнопке.
  • Функция reset соответствует тэгу <INPUT TYPE=RESET>, задающему кнопку сброса. Может иметь параметр, переопределяющий название Reset, отображаемое по умолчанию.
  • Функция end_htmi завершает HTML-документ, добавляя тэги </BODY>
    </HTML>.

Пример 15.8 содержит также код, который не связан с созданием формы Он состоит из одного условного оператора, в котором в качестве условия используется значение, возвращаемое функцией paramo. Эта функция используется также внутри блока условного оператора. Разберем для чего она применяется. При помощи функции paramo модуля CGI можно выполнить следующие действия.

  • Получение списка имен параметров, переданных сценарию.
    Если сценарию переданы параметры в виде списка пар "имя=значение" функция paramo без аргументов возвращает список имен параметров сценария:
    @names = param;
  • Получение значений именованных параметров.
    Функция paramo с единственным аргументом — именем параметра, возвращает значение этого параметра. Если параметру соответствует несколько значений, функция param о возвращает список этих значений:'
    @values = param('language');
    в противном случае — одно значение:
    $value = param('regname');
  • Задание значений параметров.
    param(-name => 'language', -values => ['russian', 'english', 'french']);
    Можно задавать значения параметров, используя вызов функции param о в форме с позиционными параметрами, но тогда нужно знать порядок следования этих параметров:
    param ('language', 'russian', 'english', 'french');

При помощи функции param о можно устанавливать начальные значения элементов формы или изменять ранее установленные значения.

Часть сценария, предшествующая условному оператору, предназначена для создания формы из примера 15.1. Заключительная часть, состоящая из условного оператора, обрабатывает заполненную и отправленную Web-серверу форму. Это происходит потому, что по умолчанию приложением, обрабатывающим форму, является данный сценарий (см. описание start__form). Таким образом, в одном сценарии содержится код, и создающий форму, и ее обрабатывающий.

Сохраним код, приведенный в примере 15.8, в файле welcome.cgi. Этот файл можно поместить на Web-сервере в стандартный каталог cgi-bin, предназначенный для хранения CGI-сценариев. Предположим, что Web-сервер имеет Internet-адрес www.klf.ru. Если из удаленного браузера послать запрос по адресу http://www.klf.ru/cgi-bin/welcome.cgi , то Web-сервер, получив запрос, выполнит сценарий welcome, cgi. Сценарий "на лету" создаст HTML-документ, содержащий форму, и передаст его Web-серверу, который отправит документ браузеру. Браузер, получив документ, отобразит его.

После заполнения формы и нажатия кнопки ОК данные формы будут вновь отправлены Web-серверу, который передаст их для обработки все тому же сценарию welcome.cgi. Сценарий "на лету" создаст новый HTML-документ с учетом полученных данных и через сервер направит его браузеру. Браузер отобразит новый документ.

Сценарий welcome.cgi можно передать для выполнения интерпретатору peri, а результат вывести в файл, чтобы посмотреть, как вызовы функций модуля CGI преобразуются в тэги HTML-документа. Документ HTML, созданный сценарием welcome.cgi, имеет следующий вид.

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <HTMLXHEADXTITLE>npMMep <J>opMbi</TITLE> </HEADXBODY>

<Н2>Регистрационная страница Клуба любителей фантастики</Н2>

Заполнив анкету, вы сможете пользоваться нашей электронной библиотекой,

<BR>

<FORM METHOD="POST" ENCTYPE="application/x-www-form-urlencoded">

Введите регистрационное имя:<1ЫРит TYPE="text" NAME="regname" ><P>

Введите пароль: <INPUT TYPE="password" NAME="passwordl" MAXLENGTH=8><P>

Подтвердите пароль: <INPUT TYPE="password" NAME="password2" MAXLENGTH=8>

<Р>Ваш возраст<Р>

<INPUT TYPE="radio" NAME="age" VALUE="lt20" СНЕСКЕО>до 20

<INPUT TYPE="radio" NAME="age" VALUE="20_30">20-30

<INPUT TYPE="radio" NAME="age" VALUE="30_50">30-50

<INPUT TYPE="radio" NAME="age" VALUE="gt50">CTapnie 50

<BRXBR>Ha каких язьпсах читаете:

<INPUT TYPE="checkbox" NAME="language" УАШЕ="русский" СНЕСКЕП>русский

<INPUT TYPE="checkbox" NAME="language" УАШЕ="английский">английский

<INPUT TYPE="checkbox" NAME="language" УАьиЕ="французский">ф р анцузский

<INPUT TYPE="checkbox" NAME="language" VALDE="немецкий"Жемецкий

<BRXBR>

Какой формат данных является для Вас предпочтительным

<BRXSELECT NAME="type">

<OPTION VALUE="Plain text">Plain text

<OPTION VALUE="PostScript">PostScript

<OPTION VALUE="PDF">PDF

</SELECT>

<BRXBR>

Ваши любимые авторы:

<BRXTEXTAREA NAME="wish" ROWS=3 COLS=40X/TEXTAREA>

<BR>

<INPUT TYPE="sxobmit" NAME="OK" VALUE="OK">

<INPUT TYPE="reset" VALUE="Отменить">

<INPUT TYPE="hidden" NAME=".cgifields" VALUE="language">

<INPUT TYPE="hidden" NAME=".cgifields" VALUE="age">

</FORM>

<HRX/BODYX/HTML>

В действительности документ, созданный сценарием welcome. cgi, состоит из небольшого количества длинных строк, что связано с тем, как они формируются методами модуля CGI. Поэтому реально сформированный текст для удобства представлен в более структурированном виде. Но это единственное изменение, не влияющее на смысл автоматически созданного документа.


Глава 1

Вопросы и Упражнения

Вопросы для самоконтроля

1. Что обозначает аббревиатура CGI?

2. Что такое Web-сервер?

3. Что такое клиент Web-сервера?

4. Для чего нужна HTML-форма?

5. Какую первую строку должна выводить CGI-программа?

6. Какие методы передачи данных формы Web-серверу вы знаете? В чем их особенности?

7. Перечислите элементы управления, которые может содержать форма.

8. Как кодируется информация, передаваемая CGI-программе?

9. В чем заключается специфика CGI-сценариев?

10. Каким образом CGI-программа имеет доступ к переменным среды CGI?

11. Какая информация содержится в переменной среды QUERY_STRING?

12. Для чего предназначен модуль CGI.pm?

Упражнения

1. Запишите текст примера 15.8 в обозначениях, использующих объектно-ориентированный интерфейс с модулем CGI.

2. Используя модуль CGI, дополните пример 15.8 кодом, осуществляющим проверку введенных данных, как это сделано в примере 15.76.

3. Ниже приведены тексты трех примеров, входящих в отдельный дистрибутив модуля CGI.pm. Разберите, что делают эти сценарии.

a) clickable_image.cgi

#!/usr/bin/perl

use CGI;

$query = new CGI;

print $query->header;

print $query->start__html("A Clickable Image");

print «END;

<H1>A Clickable Image</H1>

</A> /

END |

print "Sorry, this isn't very exciting!\n";

print $query->startform;

print $query->image_button('picture',"./wilogo.gif"); print "Give me a: ", $query->popup_rnenu ('letter',

['A', 'B', 'C', 'D', 'E', 'W]),"\n"; print "<P>Magnification: ", $query->radio_group('magnification',

['IX' ,'2X',MX','20X']),"\n"; print "<HR>\n"; if ($query->param) {

print "<P>Magnification, <EM>",$query->param('magnification'),

"</EM>\n";

print "<P>Selected Letter,
<EM>",$query->param( 'letter'),"</EM>\n"; ($x,$y)
= ($query->param('picture.x'),$query->param('picture.y')) ; print "<P>Selected
Position <EM>($x,$y)</EM>\n";

}

print $query->end_html;

6) quadraphobia.cgi #!/usr/bin/perl use CGI qw/:standard/; print header,

start_html('QuadraPhobia'),

hi('QuadraPhobia'),

start_form(),

image_button(-name=>'square',

-src=>'red_square.gif,

-width=>200,

-height=>200,

-align=>MIDDLE),

end_form(); if (paramO) {

($x,$y) = (param('square.x'),param('square.у'));

$pos = 'top-left' if $x < 100 && $y < 100;

$pos = 'top-right' if $x >= 100 && $y < 100;

$pos = 'bottom-left' if $x < 100 && $y >= 100;

$pos = 'bottom-right' if $x >= 100 && $y >= 100;

print b("You clicked on the $pos part of the square."); }

print p,a({href=>'../source.html'},"Code examples"); print end_html ();

в) popup.cgi

#!/usr/local/bin/perl

use CGI;

$query = new CGI;

print $query->header;

print $query->start_html('Popup Window');

if (!$query->param) {

print "<Hl>Ask your Question</Hl>\n";

print $query->startform(-target=>'_new');

print "What's your name? ",$query->textfield('name'); .

print "<P>What's the corobination?<P>",

$query->checkbox_group(-name=>'words',

-values=>['eenie','meenie','minie','moe'],

-defaults=>['eenie','moe']);

print "<P>What's your favorite color? ", $query->popup_menu(-name=>'color',

-values=>['red','green','blue','chartreuse']), "<P>";

print $query->submit; print $query->endform;

} else {

print "<H1>And the Answer is...</H1>\n";

print "Your name is <EM>",$query->param(name),"</EM>\n";

print "<P>The keywords are: <EM>",

join(", ",$query->param(words)),"</EM>\n"; print "<P>Your favorite color is <EM>",

$query->param{color),"</EM>\n";

}

print qq{<P><A HREF="cgi_docs.html">Go to the documentation</A>};

print $query->end_html;


Глава 1

Часть 16. Ресурсы Perl


Глава 1

Конференции

Язык Perl распространяется и развивается в глобальной сети Internet. Самые последние новости из мира Perl можно найти на многочисленных Web-узлах и просто обычных страницах HTML, авторы которых являются апологетами языка Perl и входят в так называемое сообщество Perl — свободное объединение людей, использующих и развивающих язык Perl там, где требуется что-нибудь и как-нибудь автоматизировать. Задав в любой поисковой машине (например, AltaVista, Yahoo) поиск по ключевому слову "Perl", вы получите тысячи ссылок на различные ресурсы Internet, так или иначе связанные с языком Perl и программированием на этом языке. Чтобы читатель не запутался и не растерялся в этом обилии информации, мы решили в последней главе нашего самоучителя отметить основные ресурсы Internet — конференции, Web-узлы и FTP-серверы, с которых можно и стоит начинать вхождение в многообразный и интересный мир Perl.

Конференции, или группы новостей Usenet, — это такие ресурсы Internet, куда люди могут посылать статьи со своими мыслями или вопросами на те мы, обсуждаемые в конференции. Если поднятый вопрос заинтересует кого-либо из участников конференции, то могут разгореться жаркие дебаты.

Не советуем сразу же посылать в конференции Perl свои вопросы. Прежде всего ознакомьтесь с их содержанием, так как вполне возможно, что подобные (или близкие) темы в них уже обсуждались, и ваш аналогичный вопрос может вызвать раздражение участников.

В Internet существуют пять известных конференций, в которых обсуждаются вопросы Peri-программирования:

  • news://comp.lang.perl — основная группа новостей, с которой рекомендуется начинать при поиске необходимой информации;
  • news://comp.Iang.perl.announce — публикуется информация о новых модулях Perl;
  • news://comp.lang.perl.modules — дискуссии об использовании модулей, именно здесь можно найти информацию о новых версиях модулей;
  • news://comp.lang.perl.misc — здесь обсуждается информация, не вошедшая в другие группы новостей;
  • news://comp.lang.perl.tk — обсуждаются проблемы использования модуля тк для создания интерфейса пользователя.

Регулярное чтение указанных конференций может дать вам больше знаний, чем чтение книг по языку Perl (мы не имеем в виду получение базовых знаний о нем), так как обсуждаемые в конференциях вопросы возникают в результате повседневной практической деятельности большинства их участников. В книгах обычно отражается накопленный к определенному моменту времени практический опыт автора, но новые задачи-то продолжают возникать.

Здесь же мы хотим дать один небольшой, но полезный совет: прежде чем начинать поиск ответа на возникший вопрос в конференциях, обратитесь к файлу, содержащему часто задаваемые вопросы, или, как его еще называют, файл Perl FAQ (Frequently Asked Questions). Его можно найти по адресу: http://www.perl.com/perl/faq/


Глава 1

Специализированные Web-узлы Perl

Группы новостей — интересные и полезные места обмена информацией, но доступ к ним зависит от вашего поставщика услуг Internet. He все они предоставляют доступ к конференциям с помощью своего сервера новостей, а если и предоставляют, то может оказаться, что именно той конференции, о которой вы мечтали, в списке доступных нет. В таких случаях следует обратить свое внимание на Web-узлы, полностью или частично посвященные языку Perl. И первым в этом ряду следует назвать Web-узел, расположенный по адресу http://www.perl.com

Этот узел является, если можно так сказать, официальным "представителем" информации по языку Perl. Здесь всегда можно узнать последние новости из мира Perl: какая версия интерпретатора считается устойчивой, а какая находится в разработке, где найти и как можно загрузить их на свой компьютер. Узел содержит обширную информацию о реализациях языка на различных платформах (UNIX, Linux, Win32, Mac, OS2 и др.), документацию по последней версии языка и многое другое. Здесь же имеются ссылки на интересные статьи и разработки.

На других многочисленных Web-узлах можно найти учебные пособия и книги по языку Perl, материалы конференций, разнообразные советы по программированию на языке Perl и многое другое. Мы включили в приводимый ниже список только небольшую часть узлов Internet, имеющих отношение к Perl и наиболее интересных с нашей точки зрения:

  • http://www.perl.oreffly.com — узел издательства O'Reilly, на котором можно найти и приобрести книги по языку Perl, ссылки на другие узлы, а также последние новости;
  • http://conference.perl.com/pace/conf/ — здесь расположены материалы третьей конференции по языку Perl, проходившей с 21 по 24 августа 1999 года;
  • http://perl.lco.net/ — дискуссионный форум по программированию на языке Perl;
  • http://www.perl.org — узел некоммерческой организации, поддерживающей сообщество Perl;
  • http://www.perhnonks.org — можно найти разнообразные советы по программированию, электронные учебники как для начинающих, так и для тех, кто желает углубить свои знания по языку Perl;
  • http://www.perifoo.org — здесь можно найти реализацию с помощью Perl интересных задач;
  • http://feenix.metronet.com/lh/perlinfo/ — архив статей о Perl, начиная с 1996 года;
  • http://www.peri.com/pub/langiiage/versus/Didex.htmI — статьи, посвященные сравнению языка Peri с языками программирования Tel, Python, Java, С, csh и др.
  • http://www.peiffiiq.com — интерактивная база данных по часто задаваемым вопросам, позволяет осуществлять поиск на основе запроса пользователя;
  • http://www.stars.com — интересный узел, посвященный проблемам создания Интернет-приложений, в том числе и на базе языка Perl;
  • http://www.activestate.coni — узел фирмы ActiveState Tool Co., на котором имеются свободно распространяемые двоичные версии интерпретаторов для различных платформ: UNIX, Linux, Win32 и Mac.

Мы перечислили только очень небольшую часть адресов Web-узлов, на которых можно найти информацию по Perl. На узлах многих университетов существует описание курсов, посвященных изучению языка Perl и его применению для решения задач Web-программирования и системного администрирования.


Глава 1

Архив СРАМ

CPAN (Comprehensive Perl Archive Network) представляет гигантское храншщ-ще практически всех документов и исходных текстов Perl, а также информацию и исходные тексты огромного количества модулей, расширяющих возможности языка Perl и упрощающих программирование на нем. Если у вас возникла проблема, требующая решения, загляните на CPAN и внимательно посмотрите среди предлагаемых там модулей — может быть, в каком-нибудь из них эта или схожая задачи уже решены. Все модули на CPAN 1 свободно распространяемые, и вы можете бесплатно загрузить и установить на собственном компьютере любой из них. Попасть в архив CPAN можно по адресу:

http://www.cpan.com

На домашней странице этого Web-узла можно найти ссылки на документацию в разных форматах, архив конференции news:comp.lang.perl.annouiice, начиная с 1995 года, ссылки на страницу модулей и программы Perl для работы с аудиофайлами, администрирования, обработки почты, организации поиска, автоматизации в Web, организации сетевого взаимодействия и написания CGI-еценариев. Здесь же находится ссылка на двоичные дистрибути-i вы (порты) Perl для разных платформ и на файл с часто задаваемыми вопросами.

В сети Internet, кроме Web-узлов, существуют ресурсы, на которых хранятся файлы с разнообразной информацией. Это так называемые FTP-серверы. Многие из них предоставляют возможность свободной загрузки на компьютер пользователя хранящихся на них файлов по протоколу FTP. Все, что находится на основном узле архива CPAN, доступно через многочисленные FTP-серверы, которые являются обычными копиями (узел-зеркало) содержимого основного узла. На всех узлах-зеркалах содержится совершенно идентичная информация. Организация FTP-сервера архива CPAN представлена в табл. 16.1.

Таблица 16.1. Организация архива CPAN

Файл или каталог | Описание
README.html | Основной документ
CPAN.html | Описание организации архива CPAN, ссылок на все модули и внесения в CPAN собственного модуля
ENDINGS | Описание содержимого файлов в соответствии с их расширением
SITES.html | Список FTP-серверов и Web-узлов архивов CPAN
RECENT . html | Последние поступления
RQADMAP.html | Организация архива CPAN
authors/ | Каталог модулей и расширений Perl, классифицированных по авторам
clpa/ | Каталог архивов групп новостей comp.lang.perl.announce
doc/ | Каталог, содержащий документацию Perl
indeces/ | Каталог, содержащий файлы с указателями на ресурсы CPAN
misc/ | Каталог, содержащий разнообразные ресурсы Perl, не вошедшие ни в один из перечисленных разделов
modules/ | Каталог модулей и расширений Perl
ports/ | Каталог двоичных дистрибутивов Perl для разных платформ
scripts/ | Каталог разных скриптов Perl на разные случаи жизни (просмотрите содержимое этого каталога, если не хотите изобретать велосипед!)
src/ | Каталог исходных текстов и исправлений самого Perl и вспомогательных утилит

Попасть на один из FTP-серверов архива CPAN можно по ссылке с домашней страницы архива (www.cpan.org) или по одному из следующих адресов, взятых из большого списка FTP-серверов, содержащихся в файле SITES.html: ftp://ftp.flmet.fi/pub/languages/perl/CPAN/ (Финляндия)

fitp://ftp.rz.nilir-uiii-bochum.de/pub/CPAN/ (Германия)

ftp://siinsite.aac.dk (Дания)

ftp://ftp.chg.ru/pub/Iang/perI/CPAN/ (Россия)

ftp://ftp.sai.msu.su/pub/lang/peri/CPAN/ (Россия)

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


Глава 1

Часть2. Структура программы


Глава 1

Простая программа

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

Язык Perl — достаточно простой язык программирования, семантика ключевых слов которого соответствует их значению в английском языке, поэтому даже начинающий его изучение программист, во всяком случае, так утверждают его разработчики, без особого труда может разобраться в простой программе Perl. Ну, что ж, может быть так оно и есть, но, как говорится, "лучше один раз увидеть, чем сто раз услышать". Итак, в примере 2.1 приведен текст программы, которая печатает на экране монитора приглашение ввести имя пользователя, а в ответ просто приветствует его.

Пример 2.1. Простая программа-приветствие

01 #! /bin/usr/perl

02

03 print "Ваше имя?\n"; # Приглашение ввести имя.

04 $name = <STDIN>; # Ввод имени с клавиатуры.

05

06 $~ = NAME_FORMAT; # Назначение формата вывода.

07 write; # Вывод приветствия.

08

09 $~ = NAME_FORMAT_BOTTOM; # Вывод нижней разделительной черты.

10 write;

11

12 format NAME_FORMAT= # Начало описания формата.

13 Привет, @»»»»»»! # Строка вывода.

14 $name # Переменная, значение которой

# подставляется в строку вывода.

15 . # Завершение описания формата.

16

17 format NAME_FORMAT_TOP= # Заголовок формата NAME_FORMAT.

18 ================================

19 Сообщение Perl-программы

20

21 .

22

23 format NАМЕ_FОRМАТ_TОP= # Формат вывода нижней разделительной черты

24 ================================

25 .

Эта программа не совсем типичная для Perl-программы — в ней отсутствуют операторы ветвления, цикла, определение собственных подпрограмм и их вызов и многое другое, но она все же отражает предназначение языка Perl как языка генерирования отчетов, осуществляя вывод приветствия в соответствии с технологией создания отчетов путем определения формата вывода, который в большинстве современных языков либо отсутствует вообще, либо практически не используется программистами.

Если читатель по тексту программы уже понял, как она будет работать (надеемся, что наши комментарии помогли ему в этом), то теперь самое время проверить его догадку — выполнить эту программу. Но прежде следует в обычном текстовом редакторе набрать ее текст и сохранить в файле с расширением pl. Программы Perl являются обычными текстовыми файлами и для их создания можно использовать любой текстовый редактор: в UNIX, например, всегда доступные vi и ed, а в Windows — Блокнот (notepad.exe) или редактор программы Far.

Замечание
Обычно расширение файла (до трех символов) используется для идентификации файлов определенной группы: выполнимые файлы программ (ЕХЕ), файлы текстового процессора Word (DOC) и т. д. Для сценариев Perl принято использовать двухбуквенное расширение pl.

Для выполнения программы примера 2.1, сохраненной в файле programl.pl, в любой операционной системе, имеющей командную строку, достаточно набрать в ней команду:

perl program1.pl

В результате выполнения программы на экране монитора появится приглашение ввести имя и после ввода имени отобразится приветствие Perl- программы. Дамп экрана после выполнения нашей программы можно увидеть в примере 2.2, где мы полужирным шрифтом выделили ввод пользователя.

Пример 2.2. Вывод программы примера 2.1

Ваше имя?

Александр

==========================

Сообщение Perl-программы

Привет, Александр!

==========================

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

Первый оператор — это специальный комментарий, который для системы UNIX определяет местонахождение интерпретатора perl. Дело в том, что в этой операционной системе можно сделать любой файл выполняемым с помощью команды

chmod +x programl.pl

и запустить на выполнение из командной строки:

./programl.pl

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

#! /bin/usr/perl -w -d

(О режимах работы интерпретатора perl и соответствующих ключах см.в части 14 )

Замечание
 При работе в операционной системе Windows можно ассоциировать с расширением файла определенную программу, которая будет вызываться при двойном щелчке мышью на любом файле с таким расширением. Программа установки интерпретатора языка Perl фирмы ActiveState Tool Corp., который называется Active Perl, автоматически устанавливает соответствие файлов с расширением рl и интерпретатора языка Perl. При двойном щелчке на файле сценарий действительно выполняется в окне DOS, которое, однако, автоматически закрывается после выполнения последнего оператора, что не позволяет просмотреть отображенные в нем результаты. Чтобы этого не происходило, следует заменить строку вызова приложения, генерируемую программой установки, на следующую:

C:\WINDOWS\Dosprmpt.pif /Полный__путь\perl.exe

Для этого следует выполнить команду Параметры меню Вид программы Проводник, в диалоговом окне Параметры на вкладке Типы файлов в списке Зарегистрированные типы выделить Perl File и нажатием кнопки Изменить отобразить диалоговое окно Изменение свойств типа файлов. В этом окне в списке Действия выбрать Open и нажать кнопку Изменить. В появившемся диалоговом окне Изменение действия для типа: Perl File в поле Приложение, исполняющее действие: ввести указанную строку, задав в ключе /с полный путь к папке, где расположен двоичный исполняемый файл интерпретатора Active Perl, который называется perl.exe.

При работе с интерпретатором Active Perl нет необходимости задавать первую строку, однако если необходимо установить режимы работы интерпретатора, то ее следует задать, причем не обязательно указывать полный путь расположения интерпретатора, достаточно только его имя perl и необходимые ключи.

Внимание
 Все примеры программ в этой книге проверены в интерпретаторе Active Perl 520 для Windows 95/NT и встроенном интерпретаторе Perl системы Linux RedHat 6.1, соответствующих текущей стабильной версии Perl 5.005.

Вообще любой комментарий в языке Perl начинается с символа "#" и распространяется до конца строки. Единственное исключение — это если сразу же после символа комментария следует символ "!".

Внимание
В строке специального комментария не следует вводить ничего, кроме пути местонахождения интерпретатора и его ключей. В противном случае можно получить сообщение об ошибке.

В строке 3 функция print посылает на системное устройство вывода (обычно это монитор компьютера) содержимое списка своих параметров, при необходимости преобразуя его в символьное представление. В нашей программе при выполнении этого оператора на экране монитора отобразится содержимое строки, передаваемой функции print в качестве параметра. Для тех, кто не знаком с языком С, последовательность символов "\n" может показаться странной, тем более, что в дампе экрана (см. пример 2.2) она даже не отображается. Это одна из так называемых управляющих или ESC- последовательностей, которые предназначены для представления неотображаемых символов — в. данном случае символа перехода на новую строку.

Оператор строки 4 ожидает ввод со стандартного устройства ввода (обычно это клавиатура) и присваивает переменной $name введенное пользователем имя. Забегая вперед, скажем, что при работе с файлами в Perl определяются дескрипторы файлов, представляющие собой обычные идентификаторы, используемые в программе для ссылки на файлы, с которыми они ассоциированы. В языке имеется несколько предопределенных дескрипторов файлов, одним из которых является STDIN , ассоциированный со стандартным устройством ввода. Операция о выполняет ввод из файла, заданного своим дескриптором.

Оператор строки 6 назначает системной переменной $~ имя используемого формата для выполнения вывода на стандартное устройство вывода системной функцией write из строки 7. Сам формат задается в строках 12-15 оператором format , в котором определяется имя формата ( NAME_FORMAT ), завершающееся символом равенства "=". В последующих строках до строки 15, содержащей единственный символ точка ".", фиксирующий завершение объявления формата, задается сам формат, который обычно представляет собой повторяющуюся последовательность двух строк: строки вывода и строки, перечисляющей через запятую переменные, чьи значения при выводе подставляются вместо поледержателей, объявленных в строке вывода. Строка вывода представляет собой именно строку, которая выводится функцией write в определяемый ее параметром файл (если параметр не задан, то вывод осуществляется на стандартное устройство вывода). В этой строке значащими являются все пробельные символы (сам пробел, символы перехода на новую строку и страницу, символ табуляции). Поледержатель — это специальная конструкция в строке вывода, начинающаяся с символа "@", за которым следует определенное количество специальных символов, задающих длину поля, в которое выводится значение переменной, ассоциированной с поледержателем и определенной в списке переменных, задаваемом в строке, непосредственно следующей за строкой вывода. Если в строке вывода поледержателей нет, то строку со списком ассоциированных с ними переменных задавать не надо.

В формате NAME_FORMAT определена одна строка вывода с одним поледержателем, который резервирует поле длиной в 12 символов и определяет, что выводимое значение должно быть прижато вправо (символ ">"). Это означает, что если значение ассоциированной с этим поледержателем переменной $name будет меньше 12 символов, то в этом поле при выводе они будут выровнены по правому краю. Если выводимое значение преобразуется в строку длиной более 12 символов, она обрезается по правому краю, т. е. отображаются первые 12 символов, считая слева направо.

Если для формата определен формат с таким же именем и суффиксом _ТОР, то этот формат определяет вид заголовка страницы, который будет отобра жаться всякий раз при выводе новой страницы с использованием основного формата. Для формата NAME_FORMAT определен формат заголовка в строках 17-19.

Завершается вывод приветствия печатанием разделительной черты функцией write строки 10 с использованием формата NAME_FORMAT_BOTTOM. Обратите внимание, что для использования нового формата вывода нам пришлось изменить значение системной переменной $~. Это следует делать всякий раз, когда необходимо организовать вывод с помощью нового формата.

Итак, мы разработали и выполнили нашу первую программу на языке Perl. Теперь пришло время обратиться к синтаксису языка и рассказать в самых общих чертах, из чего состоит программа на языке Perl, т. е. описать структуру программы.


Глава 1

Объявления и комментарии

Программа Perl представляет собой последовательность операторов и объявлений, которые обрабатываются интерпретатором в том порядке, как они появляются в тексте программы. Во многих языках программирования обязательны объявления всех используемых переменных, типов и других объектов. Синтаксис Perl является "демократичным" в этом отношении и предписывает обязательные объявления только форматов и подпрограмм.

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

В Perl нет специального оператора объявления переменной, она определяется при первом ее использовании, причем с помощью ключа - w интерпретатора можно задать режим отображения предупреждающих сообщений при попытке использования не инициализированной переменной. Переменные можно определять как глобальные, видимые из любой точки программы, так и с помощью функции ту как локальные, видимые в определенной части программы — блоке.

(Объявления локальных переменных описываются в части 3 и в части 11 )

Можно объявить подпрограмму с помощью оператора sub , не определяя ее, т. е. не задавая операторы, реализующие ее функцию. После такого объявления подпрограммы ее имя можно использовать как операцию, действующую на список, определяемый передаваемыми ей параметрами.

(Более подробно объявления и определения подпрограмм рассматриваются в части 11 )

Как уже отмечалось выше, если интерпретатор встречает символ #, то он игнорирует любой текст, расположенный за ним. Такая конструкция называется комментарием и ее действие распространяется до конца строки после символа #. Комментарии используются для документирования программы и не следует ими пренебрегать, так как они вносят ясность в понимание того, что выполняет программа. Однако и злоупотреблять ими не следует, так как слишком большое их количество может ухудшить читаемость программы — одну из важных характеристик любой программы.

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


Глава 1

Выражения и операторы

Оператор — это часть текста программы, которую интерпретатор преобразует в законченную инструкцию, выполняемую компьютером. С точки зрения синтаксиса языка (способов составления правильных конструкций, распознаваемых интерпретатором) оператор состоит из лексем — минимальных единиц языка, которые имеют определенный смысл для интерпретатора. Под минимальной единицей языка понимается такая его единица, которая не может быть представлена более мелкими единицами при дальнейшем ее синтаксическом разборе. В языке Perl лексемами могут быть идентификаторы, литералы, знаки операций и разделитель.

Мы дадим определения всем допустимым в языке лексемам. Хотя их семантика (смысл) может оказаться для начинающих программистов и не совсем ясна, но мы вернемся к некоторым определениям в последующих главах, где уточним их и синтаксис, и семантику в связи с вводимыми элементами языка. Дело в том, что, к сожалению, невозможно описать язык без ссылок вперед.

Идентификатор — это последовательность букв, цифр и символа подчеркивания "_", начинающаяся с буквы или подчеркивания и используемая для именования переменных, функций, подпрограмм, дескрипторов файлов, форматов и меток в программе. Программист может использовать любые правиль ные идентификаторы для именования перечисленных объектов программы, если только они не совпадают с ключевыми словами языка — предопределенными идентификаторами, которые имеют специальное значение для интерпретатора языка Perl, например if, unless, goto и т. д. Примеры правильных и неправильных идентификаторов представлены в примере 2.3.

Пример 2.3. Правильные и неправильные идентификаторы

# Правильные идентификаторы

myNamel

my_Namel

_myName__l

# Неправильные идентификаторы

1 myName # Начинается с цифры.

-myName # Начинается не с символа буквы или подчеркивания.

my%Name # Используется недопустимый для идентификаторов символ

my # my является зарезервированным словом.

Замечание
Забегая вперед, скажем, что так как имена переменных Perl начинаются со специального символа (" $ ", " @ ", " % "), определяющего их тип, после которого следует идентификатор, то в этом случае использование идентификатора, совпадающего с ключевым словом Perl, является правомочным и не вызывает ошибку интерпретатора. Так, следующие имена переменных являются допустимыми: $print , @do , %if , однако подобная практика не рекомендуется. Это замечание не относится к идентификаторам, используемым для именования дескрипторов файлов и меток, имена которых не начинаются с определенных символов.

(Как используются идентификаторы для объявления переменных см. часть 3 )

(Как используются идентификаторы в дескрипторах файлов см. часть 7 )

(Как используются идентификаторы для объявления форматов см. часть 8 )

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

123 # Целое число.

23.56 # Вещественное число с фиксированной точкой.

2Е+6 # Вещественное число с плавающей точкой.

"Язык Perl" # Строковый литерал.

(О литералах см. в части 3)

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

Пример 2.5. Операции языка Perl

++$п; # Унарная операция (++)

23 * $п; # Бинарная операция (*)

$п >= 3 ? print "true" : print "false"; # Тернарная операция (?:)

(Об операциях и используемых знаках операций см. в части 4)

Разделитель — это символ ";", которым завершается любой оператор и который сообщает об этом интерпретатору. Использование разделителя позволяет на одной строке задавать несколько операторов, хотя это и не принято в практике программирования, так как ухудшает читаемость текста программы.

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

Пример 2.6. Использование пробельных символов в операторе

$name

= <STDIN>

;

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

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

Операторы в языке Perl могут быть простыми или составными. Простой оператор — это выражение, завершающееся разделителем точкой с запятой ",-", и которое вычисляется исключительно ради своего побочного эффекта. Что такое побочный эффект выражения мы определим немного позже, а сейчас остановимся на понятии "выражение".

Выражение — последовательность литералов, переменных и функций, соединенных одной или более операцией, которые вычисляют скаляр или массив, т. е. при обработке интерпретатором выражения единственным действием является вычисление значения, а не выполнение некоторых других действий, например присвоение переменной нового значения. При вычислении выражения могут проявляться побочные эффекты, когда при вычислении выражения меняется значение переменной, входящей в выражение. Они могут вызываться, например, операциями увеличения (++) и уменьшения (--) или при вызове функции, которая изменяет значение своего фактического параметра. Как упоминалось выше, простой оператор и выполняется, чтобы реализовать этот побочный эффект, иначе какой смысл просто вычислить выражение, значение которого никоим образом нельзя использовать.

Пример 2.7. Простые операторы

++$n; # Значение переменной $n увеличивается на единицу.

123*$n; # Простой оператор без побочного эффекта.

Каждый простой оператор может иметь модификатор, который располагается после выражения перед завершающей точкой с запятой. Модификаторы представляют собой ключевые слова if , unless , while и until , за которыми следует некоторое выражение. Семантика использования модификатора заключается в том, что простой оператор выполняется, если истинно или ложно выражение, стоящее после модификатора, в зависимости от используемого модификатора. Модификаторы употребляются так же, как и в обычном разговорном английском языке. Например, простой оператор

$n++ while <STDIN>;

будет выполняться, увеличивая всякий раз значение переменной $n на единицу, пока пользователь будет осуществлять ввод с клавиатуры. Остановить выполнение этого оператора можно вводом комбинации клавиш <Ctrl>+<Z>ium <Ctrl>+<C>.

Замечание
Язык Perl вобрал в себя лучшие элементы других языков программирования, в основном С. Конструкция модификаторов заимствована из умершего языка BASIC/PLUS фирмы Digital Equipment Corp.

(Подробно все модификаторы простых операторов рассматриваются в части 5 )

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

{ оператор_1;

оператор_n; }

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

Пример 2.8. Составные операторы

if (выражение) БЛОК

if (выражение) БЛОК_1 else БЛОК__2

if (выражение_1) БЛОК_1 elsif (выражение_2) БЛОК_2 ... else БЛОК_п

МЕТКА while (вьражение) БЛОК

МЕТКА while (вьражение) БЛОК_1 continue ЕЛОК_2

МЕТКА for (вьражение_1; выражение_2; аьражение_3) БЛОК

МЕТКА foreach переменная (список) БЛОК .МЕТКА БЛОК_1 continue БЛОК_2

Обратим внимание читателя на то, что, в отличие от языков программирования С и Pascal, составные операторы Perl определяются в терминах блоков, а не в терминах операторов. Это означает, что там, где нужен блок, он всегда должен задаваться с помощью фигурных скобок. В составных операторах, если даже блок состоит из одного оператора, он должен быть заключен в фигурные скобки. Такой синтаксис не приводит к двусмысленностям и, например, во вложенных операторах условия всегда ясно, с каким if согласуется else или elsif . Метка, представляющая собой идентификатор с двоеточием ":", в составных операторах не обязательна, но если она присутствует, то имеет значение для операторов управления циклами next , last и redo .

(Подробно все составные операторы рассматриваются в части 5 )

В этой главе мы познакомились с основными синтаксическими понятиями, используемыми для формирования правильных конструкций языка Perl. Узнали из каких основных элементов состоит Peri-программа, а также разработали и выполнили нашу первую программу.


Глава 1

Вопросы и Упражнения

Вопросы для самоконтроля

  1. Что такое лексемы и какими элементами они представлены в языке Perl?

  2. Что такое выражение и зачем оно создается?

  3. Чем выражение отличается от оператора?

  4. Каковы различия между объявлением и оператором?

  5. Какие классы операторов имеются в языке Perl?

  6. Что представляет собой Peri-программа?

Упражнения

  1. Модифицируйте программу примера 2.1, чтобы она всегда приветствовала весь мир, т. е. при ее выполнении всегда отображалось бы "Привет, мир!".

  2. Модифицируйте программу примера 2.1, использовав для вывода оператор print , имеющий следующий синтаксис: print выражение1, выражение2, . . . , выражение n;

  3. Исправьте ошибку в программе: #! /usr/bin/perl Это строка показывает местонахождение интерпретатора print "Perl";

  4. Исправьте ошибки в программе:
    i! /usr/bin/perl -w $write = 24
    $two =3
    $rez = $write * $two

  5. Исправьте ошибку в программе:
    '#! /usr/bin/perl -w $write = 24;
    # Печать переменной! print $write;


Глава 1

Часть 3. Типы данных


Глава 1

Алфавит языка

Приступая к знакомству с любым языком программирования, мы прежде всего выясняем, а какие типы данных он позволяет обрабатывать, предоставляет ли язык механизм создания новых типов из уже существующих. Ведь язык программирования, как знаковая система обработки информации, как раз и предназначен для обработки информации, представленной данными и алгоритмами. Данные определяются своим типом — множеством значений и набором допустимых операций. Одни языки программирования предлагают большое число разнообразных типов данных, как, например, универсальный язык С, в котором даже тип данных, используемый для хранения символов, может быть со знаком или без знака; другие могут обходиться всего лишь двумя типами данных, как, например, VBScript, в котором для хранения и обработки любых скалярных данных (числовых, строковых и булевых) используется единственный вариантный тип данных и существует возможность создания массивов скалярных данных. В конечном счете, язык должен иметь такое разнообразие типов данных, чтобы программист мог с их помощью решать задачи, для которых предназначен язык программирования.

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

С чего начинается изучение любого языка? Конечно, с алфавита. Помните, как в первом классе мы усердно выводили в прописях все буквы русского алфавита, а в старших классах, приступая к изучению иностранного языка, мы сначала учили буквы его алфавита и какие звуки они обозначают, а потом из букв складывали слова, которые уже можно было использовать для составления предложений, несущих определенную информативность: "Мама мыла раму".

Аналогично происходит и при изучении языков программирования. Сначала мы должны выяснить, какие можно использовать символы для составления лексем (слов языка), из которых можно конструировать операторы (предложения языка).

В языке Perl можно использовать все буквы латинского алфавита (прописные и строчные), арабские цифры и знак подчеркивания "_". Perl относится к языкам, чувствительным к регистру. Это означает, что символы прописной и строчной буквы считаются различными. Поэтому, например, два идентификатора one и one, используемые для задания имени переменной, — это два различных идентификатора и следовательно, определяемые ими переменные одного и того же типа также являются различными.

Замечание
Буквы национальных алфавитов, в частности русского, можно использовать только в строковых данных. Идентификаторы переменных могут содержать буквы только латинского алфавита. Кроме букв, цифр и символа подчеркивания, которые называются алфавитно-цифровыми символами, используется набор специальных символов, представленный в табл. 3.1.

Таблица 3.1. Специальные символы языка Perl

Символ | Название | Символ | Название
` | Обратный апостроф | ~ | Тильда
! | Восклицательный знак | @ | Коммерческое AT
№ | Номер | $ | Знак доллара
% | Процент | ^ | "Крышка"
& | Амперсанд | * | Звездочка
- | Минус | + | Плюс
= | Равенство | | | Вертикальная черта
' | Прямой апостроф | " | Кавычки
< | Меньше | > | Больше
/ | Косая черта | \ | Обратная косая черта
, | Запятая | . | Точка
: | Двоеточие | ; | Точка с запятой
[ | Квадратная левая скобка | ] | Квадратная правая скобка
{ | Фигурная левая скобка | } | Фигурная правая скобка
( | Круглая левая скобка | ) | Круглая правая скобка
? | Вопросительный знак |  | Пробел

Анализ специальных символов показывает, что в языке Perl используются все символы, которые можно ввести с клавиатуры. Если буквенно-цифровые символы используются в составе идентификаторов, то специальные символы служат для определения знаков операций, уточнения синтаксиса выражений, а также именования специальных встроенных переменных языка Perl. Если читателю какой-то из перечисленных объектов сейчас и не совсем ясен, то при последующем изложении все встанет на свои места. Алфавит языка используется для создания "правильных" (распознаваемых интерпретатором языка) лексем. Среди всего множества таких лексем существует подмножество предопределенных лексем, называемых ключевыми словами и используемых для создания правильных конструкций языка. Набор ключевых слов языка Perl не велик и представлен ниже:
if, elseif, else, unless, while, until, foreach, for, next, continue, last, do, eval, goto, sub, my, return

Кроме перечисленных ключевых слов, определяющих синтаксические языковые конструкции, в языке Perl сложился набор стандартных функций, который реализован в любом интерпретаторе perl. Имена этих функций можно тоже считать зарезервированными словами языка и не использовать в качестве имен пользовательских функций или меток в программе. Мы не будем здесь перечислять имена всех стандартных функций, так как их количество достаточно велико, да и мало прока будет от такого простого перечисления, а отошлем читателя к приложению А, где он может увидеть имена всех стандартных функций.


Глава 1

Скалярный тип данных

Скалярный тип данных в Perl предназначен для представления и обработки числовых данных (чисел) и последовательности символов, называемых строками. Для задания в программе перечисленных данных используются буквальные константы, или литералы: числовые и строковые.

Числовые литералы используются для представления обычных чисел, необходимых для реализации какого-либо алгоритма в программе Perl. Обычно используются числа с основанием десять, или десятичные числа, но язык позволяет использовать и восьмеричные (с основанием восемь), и шестнадцатеричные (с основанием шестнадцать) числа, которые полезны при работе с содержимым памяти компьютера в процессе решения некоторых системных задач.

Десятичные числа могут быть целыми или вещественными с дробной частью, которые в программировании часто называют числами с плавающей точкой из-за способа их представления и хранения в памяти компьютера. Соответствующие им литералы ничем не отличаются от записи подобных чисел в математике: последовательность цифр без пробелов для целых чисел и последовательность цифр, в которой точка отделяет целую часть от дробной, для вещественных чисел (пример 3.1).

Пример 3.1. Числовые литералы

123 # Целое десятичное число.

234.89 # Вещественное число.

0.6780 # Вещественное с нулевой целой частью

678 # Незначащие нули можно не задавать

1_000_000.67 # Для отделения разрядов в целой части числа

# можно использовать символ подчеркивания.

Для вещественных чисел с плавающей точкой можно использовать и экспоненциальную форму записи:

[цифры] . [цифры] [Е | е] [+1 -] [цифры]

Эта форма записи означает, что для получения значения числа следует его мантиссу, заданную в форме действительного числа с точкой ([цифры]. [цифры]), умножить на десять в степени числа со знаком, заданного в его экспоненциальной части после символа Е или е (пример 3.2).

Пример 3.2. Экспоненциальная форма записи вещественных чисел

10.67Е56 # Знак "+" в экспоненте можно опускать.

10.67е+06 # Так экспонента лучше читаема.

1е-203 # Число близко к машинному нулю.

1е+308 # Число близко к бесконечно большому числу.

Замечание
Интерпретатор языка Perl представляет все числа (и целые, и вещественные) в формате чисел с плавающей точкой удвоенной точности. Это означает, что реально нельзя задать больше шестнадцати значащих цифр мантиссы, а экспонента ограничена диапазоном от -323 до +308. Интерпретатор не сгенерирует ошибки, если мантисса будет превосходить 16 цифр, а экспонента 3 цифры, но при отображении таких чисел мантисса будет приведена к шестнадцати значащим цифрам. Если экспонента меньше нижнего предела, то будет выводиться нуль, а если больше верхнего предела, то используется специальный символ 1.#INF, обозначающий бесконечно большое число. Подобный алгоритм представления очень больших и очень маленьких чисел не приводит к возникновению, соответственно, ошибок переполнения и исчезновения порядка, свойственной многим языкам программирования. Если задать целое число с числом значащих цифр больше 15, то при выводе оно будет отображаться как вещественное в экспоненциальной форме.

Некоторые системные установки или анализ некоторых системных параметров легче осуществлять с использованием чисел, представленных в восьмеричной или шестнадцатеричной системах счисления. Форма записи таких чисел аналогична их синтаксису в языке С: любое целое число, начинающееся с нуля "о", трактуется интерпретатором как восьмеричное целое число, а символы, непосредственно следующие за комбинацией "Ох", рассматриваются как шестнадцатеричные цифры. При использовании восьмеричных чисел следует помнить, что в них не может быть цифры больше, чем 7, а в шестнадцатеричных числах кроме десяти цифр от о до 9 используются буквы А или а, в или ь, с или с, о или d, Е или е, F или f для обозначения недостающих цифр числа (пример 3.3).

Пример 3.3. Восьмиричные и шестнадцатиричные числа

010 # Восьмеричное 10, равное десятичному 8.

0x10 # Шестнадцатеричное 10, равное десятичному 16.

0239 # Вызовет ошибку интерпретации: нельзя использовать цифру 9.

OxAIFf # Соответствует 41477 десятичному.

OxGA # Вызовет ошибку интерпретации: нельзя использовать букву G.

Замечание
Задание шестнадцатеричных цифр — это единственный случай в Perl, когда прописные и строчные буквы идентичны. В других случаях их употребления, например в идентификаторах, они различны.

Внимание
Нельзя вместо последовательности символов "Ох", идентифицирующей шестнадцатеричные числа, использовать последовательность "ох".

Строковые литералы, или просто строки, представляют последовательность символов, заключенную в одинарные ('), двойные (") или обратные (') ка вычки, которая рассматривается как единое целое. Использование одинарных и двойных кавычек для задания строк аналогично их применению для этих же целей в системе UNIX.

В строке, ограниченной одинарными кавычками, нельзя использовать ESC-, или управляющие последовательности, а также в нее нельзя подставить значение переменной. Единственное исключение составляют две управляющие последовательности: (V) и (\\). Первая используется для отображения одинарной кавычки в самой строке, так как иначе интерпретатор рассматривал бы первую, встретившуюся ему одинарную кавычку как признак завершения строки, что не соответствовало бы ее включению в строку. Вторая последовательность используется для отображения самой обратной косой черты. Примеры задания строковых литералов, ограниченных одинарными кавычками, можно найти в табл. 3.2.

Таблица 3.2. Символьные литералы, ограниченные одинарными кавычками

Строка | Отображение | Комментарий
'Простая строка #1' | Простая строка #1 | Строка без управляющих последовательностей
'Vperl.exeV ' | 'perl.exe' | Строка с одинарными кавычками
'D: \\perl.exe' | D: \perl . ехе | Строка с обратной дробной чертой
'Последовательность \n' | Последовательность \n | Управляющая последовательность \n не влияет на отображение строки
'Завтрак Бутерброд с ветчиной Чашка кофе ' | Завтрак Бутерброд с ветчиной Чашка кофе | Многострочный символьный литерал отображается в нескольких строках

Замечание
Esc-последовательности, состоящие из обратной, косой черты (\), за которой следует буква или комбинация цифр. В них символ обратной косой черты рассматривается как символ, изменяющий значение буквы. Они вместе являются одним целым и выполняют определенное действие при выводе на устройство отображения, например, переход на новую строку (\п). Комбинация цифр трактуется как ASCII-код отображаемого символа. Название таких последовательностей происходит от английского слова "escape", означающего изменять смысл. Их еще называют управляющие последовательности.

Строковый литерал может распространяться на несколько строк программы (см. последний литерал табл. 3.2). Для этого при его вводе с клавиатуры следует использовать клавишу <Enter> для перехода на новую строку.

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

Строки в двойных кавычках позволяют вставлять и интерпретировать управляющие последовательности, а также осуществлять подстановку значений переменных, содержащих скаляры или списки. Управляющие последовательности (табл. 3.3) при выводе строк могут интерпретироваться как символы новой строки, табуляции и т. п., а могут изменять регистр следующих за ними букв.

Таблица 3.3. Управляющие последовательности

Управляющая последовательность | Значение
\a | Звонок
\b | Возврат на шаг
\e | Символ ESC
\f | Перевод формата
\n | Переход на новую строку
\r | Возврат каретки
\t | Горизонтальная табуляция
\v | Вертикальная табуляция
\$ | Знак доллара
\@ | Амперсанд или AT коммерческое
\0nnn | Восьмеричный код символа
\xnn | Шестнадцатеричный код символа
\cn | Эмулирует нажатие комбинации клавиш + . Например, \сС соответствует +
\l | Переводит следующий символ в нижний регистр
\u | Переводит следующий символ в верхний регистр
\L | Переводит следующую за ней последовательность символов, ограниченную управляющей последовательностью \Е , в нижний регистр
\Q | В следующей за ней последовательности символов, ограниченной управляющей последовательностью \Е , перед каждым не алфавитно-цифровым символом вставляет обратную дробную черту
\U | Переводит следующую за ней последовательность символов, ограниченную управляющей последовательностью \Е , в верхний регистр
\E | Ограничивает действие управляющих последовательностей \L, \Q И \U
\\ | Символ обратной дробной черты
\" | Двойные кавычки
\' | Одинарные кавычки

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

Строковые литералы в двойных кавычках удобно использовать для структурированного вывода текстовой информации, заданной одной строкой. Примеры строк в двойных кавычках представлены в табл. 3.4.

Таблица 3.4. Символьные литералы, ограниченные двойными кавычками

Строка | Отображение | Комментарий
"'\Uline\E #1" | LINE #1 | Управляющие последовательности перевода регистра \l, \u, \L и \и действуют только на буквы латинского алфавита и не применимы к буквам русского алфавита
"Конец страницы\f" | Конец страницы | При выводе на экран монитора или в файл в конце строки отображается символ перехода на новую страницу; при выводе на принтер печать начинается с новой страницы после вывода этой строки
" \t3aвтpaк\nБyтepброд с ветчиной\пЧашка кофе\n" | Завтрак Бутерброд с ветчиной Чашка кофе | Символьный литерал задан одной строкой с управляющими символами

Последней разновидностью строковых литералов являются строки в обратных кавычках, которые, по существу, не являются строками данных в том смысле, что содержащиеся в них символы не обрабатываются при выводе интерпретатором языка Perl как некоторый поток отображаемых символов. Встретив строку в обратных кавычках, интерпретатор передает ее на обработку операционной системе, под управлением которой он функционирует: Windows, UNIX или какая-либо другая, которая выполняет переданную ей команду и возвращает в программу Perl результаты ее выполнения в виде строки, которую в дальнейшем можно использовать для организации вычислений. Таким образом, строки в обратных кавычках должны содержать значимую для операционной системы последовательность символов: команду операционной системы, строку загрузки приложения и т. п. Например, при выводе строки ~dir~ оператором print отобразится не слово "dir", a результат выполнения команды dir операционной системы. В системе Windows эта команда отобразит содержимое текущей папки (пример 3.4).

Том в устройстве D не имеет метки Серийный номер тома: 1F66-19F2 Содержимое каталога D:\PerlOurBook

<КАТАЛОГ> 09.01.00 16:01 .

<КАТАЛОГ> 09.01.00 16:01 ..

EXAMPLE PL 32 23.01.00 11:56 exarrple.pl

01 <КАТАЛОГ> 11.01.00 14:12 01

02 <КАТАЛОГ> 11.01.00 14:12 02

03 <КАТАЛОГ> 11.01.00 14:12 03

PERLINF ТХТ 1 781 12.01.00 11:39 perlinf.txt

EXAMPLE1 PL 347 18.01.00 18:02 examplel.pl

3 файл(а,ов) 2 160 байт

5 каталог(а,ов) 78 086 144 байт свободно

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

Замечание
Некоторые символы (их еще называют метасимволы) имеют специальное значение для командной оболочки. К ним относятся *,- <, >, ?, | и &. В системе UNIX, чтобы изменить интерпретацию метасимвола как символа со специальным значением, ставят перед ним обратную косую черту, которая изменяет (escape) его специальное назначение. Теперь он рассматривается командной оболочкой просто как символ, представляющий самого себя. Если во вводимой строке много таких специальных символов, то пользователю необходимо перед каждым поставить обратную косую черту, что приводит к плохой читаемости всей строки. Во избежание подобных "затруднений" в UNIX используются строки в одинарных кавычках, в которых все символы интерпретируются так, как они есть. Подобную же функцию одинарные кавычки выполняют и в языке Perl.

При работе в UNIX широко используется подстановка значений переменных в строку команды, которая передается на обработку оболочке shell. При задании команды в строке ввода используются строки в двойных кавычках, которые так же, как и строки в одинарных кавычках, отменяют специальные значения метасимволов, за исключением символа $, который используется для подстановки значения переменной. Обратная косая черта перед ним изменяет его специальное значение. Именно этот механизм строк в двойных кавычках послужил прототипом для аналогичных конструкций в языке Perl.

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

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

Более того, переменная определяет тип данных, хранимых в области памяти, на которую она ссылается. В большинстве языков программирования переменные до их использования в программе объявляются как переменные определенного типа, информируя транслятор, что в них можно хранить данные соответствующего типа. Как мы помним, в Perl нет операторов объявления переменных определенного типа; они автоматически объявляются при первом их использовании в конструкциях языка, например в операторе присваивания значения переменной. Любая переменная определяется заданием своего имени, которое представляет собой правильный идентификатор языка. В Perl имя любой переменной состоит из специального символа (префикса), определяющего тип переменной, за которым следует идентификатор. Для переменных скалярного типа (пример 3.5), или просто скалярных переменных, таким определяющим символом является знак доллара "$".

# Правильные имена скалярных переменных. $Name; $name_surname; $name_l;

# Неправильные имена скалярных переменных.

$l_name; # Идентификатор не может начинаться с цифры.

$Name@Surname; # Недопустимый символ @

Скалярная переменная может хранить только одно скалярное данное: числовое или строковое, причем не существует никакого способа определить, каг кой именно тип скалярных данных в ней содержится. Дело в том, что при использовании этих переменных в различных операциях хранимые в них данные автоматически преобразуются из одного типа в другой, т. е. в арифметических операциях строка преобразуется в число, а в строковых операциях числовое значение преобразуется в строковое. Преобразование строки в числовое значение осуществляется, если она содержит последовательность символов, которая интерпретируется как число, иначе интерпретатор генерирует ошибку. Шестнадцатеричные числа с префиксом "ох" и десятичные числа с символом подчеркивания для отделения триад в целой части числа, заданные как строки, не преобразуются в числа, а последовательность цифр,' начинающаяся с о, не интерпретируется как восьмеричное число. Для преобразования строк, содержащих представления шестнадцатеричных и восьмеричных чисел, в числовые значения следует пользоваться функцией oct. Как отмечалось выше, строки в двойных кавычках не только интерпретируют управляющие символы, но и позволяют подставлять значения скалярных переменных. Это означает, что в строке можно задать имя переменной, ко-торое при вычислениях заменяется значением, содержащимся в переменной на момент вычислений. (Подобную процедуру подстановки значения переменной в символьную строку в дальнейшем для краткости мы будем называть просто подстановкой переменной.) Например, следующая последовательность операторов

$s = "\$10";

$п = "Книга стоит $s долларов.";

print $n;

отобразит на экране монитора строку

Книга стоит $10 долларов.

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

При подстановке переменной ее имя должно быть отделено разделителями от остальных символов строки, причем это правило не обязательно для первого символа имени переменной, так как интерпретатор, встретив символ "$" в строке, ограниченной двойными кавычками, начинает выделять правильный идентификатор.

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

$day = 'пятницу';

$number = 5;

$html = "HTML";

$s = "${html}-документ отослан B\n$day\t$number февраля.";

print $s;

Результатом выполнения этого фрагмента будет отображение двух строк на экране монитора:

HTML-документ отослан в пятницу 5 февраля.

Переменная $htmi подставляется с явным указанием ее идентификатора, для выделения идентификаторов остальных переменных используются разделители.

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

Замечание
Рассмотренные в этом параграфе различные типы кавычек для задания строковых литералов на самом деле являются всего лишь удобной формой записи операций языка Perl: q//, qq/7, qx/7. (Эти операции подробно будут рассмотрены в главе 4).

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

$day = Friday; # Тождественно оператору $day = 'Friday';

Такие слова без кавычек в тексте программы иногда еще называют простыми словами (barewords).

Задание строковых литералов без кавычек возможно только для литералов, содержащих буквы латинского алфавита. Попытка применить подобную технику для литералов, содержащих буквы русского алфавита, приведет к ошибке компиляции.

Завершая разговор о литералах, следует упомянуть о специальных литералах языка Perl: _LINE_, _FILE_, _END_ и _DATA_. Они являются самостоятельными лексемами, а не переменными, поэтому их нельзя вставлять в строки. Литерал _LINE_ представляет номер текущей строки текста программы, а _FILE_— имя файла программы. Литерал _END_ используется для указания логического конца программы. Информация, расположенная в файле программы после этого литерала, не обрабатывается интерпретатором, но может быть прочитана через файл с дескриптором DATA. Последний литерал _DATA_ аналогичен литералу _END_, только дополнительно он открывает файл с дескриптором DATA для чтения информации, находящейся в файле программы после него. Программа примера 3.6 демонстрирует использование специальных литералов.

#! /per!520/bin/perl -w

$file = _FILE_;

$prog = _LINE_;

print "Находимся в строке: $prog\n",

"Файл: $file";; _END_ print "Текст после лексемы _END_";

Результат работы этой программы будет следующим, если файл программы хранится в файле D:\PerlEx\examplel.exe:

Находимся в строке: 3

Файл: D:\PERLEX\EXAMPLE1.PL

Вывода последнего в программе оператора печати не наблюдается, так как он расположен после лексемы _END_.


Глава 1

Массивы скаляров

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

(скаляр_1, скаляр_2, ... , скаляр_n)

Как и в других языках программирования, массив Perl представляет набор однотипных данных — скаляров, но скалярные данные могут быть как числовыми, так и строковыми, поэтому, с точки зрения других языков программирования, в массивах Perl хранятся смешанные данные — числа и строки. В качестве скаляра в конструкторе массива может использоваться числовой или строковый литерал или скалярная переменная, чье значение и будет являться элементом массива:

(5, "умножить на", 4) # Используются только литералы.

($first, 'равно', $second) # Используются значения скалярных переменных.

Массив может состоять из неограниченного числа элементов, а может не иметь ни одного. В таком случае его называют пустым массивом. Конструктор пустого массива — круглые скобки без содержащегося в них списка ().

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

(5, "умножить на", 4)[0] # Первый элемент массива: 5.

(5, "умножить на", 4)[2] # Последний элемент массива: 4.

(5, "умножить на", 4)[-1] f Последний элемент массива: 4.

(5, "умножить на", 4) [-3] # Первый элемент массива: 5.

При использовании отрицательных индексов индекс -1 соответствует последнему элементу массива, а индекс -п, где п — количество элементов массива, первому элементу массива.

Нельзя использовать индекс для извлечения элементов списка, возвращаемого некоторыми функциями Perl. Индекс можно применять только к массивам или их конструкторам, а для этого достаточно заключить список в круглые скобки. Например, конструкция stat(@m) [0] вызовет ошибку компиляции, если возвращаемым значением функции stat является список, тогда как конструкция (stat (@m)) [0] приведет к желаемому эффекту.

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

(1, (2, 3), 4}

эквивалентен массиву

(1, 2, 3, 4)

(О реализации многомерных массивов при помощи ссылок см. в части 9.)

В программе массивы хранятся в специальных переменных, имена которых начинаются с символа "@". Объявление переменной массива, или просто массива, чаще всего осуществляется в операторе присваивания (=), в правой части которого обычно задается конструктор массива:

Sarray = ($ml, '+', $m2, '=', $ml+$m2);

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

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

$ml[0] = "Первый"; # Задает первый элемент массива @ml.

$ml[l] = "Второй"; # Задает второй элемент массива @ml.

@m2 = (1, 2, 3, 4, 5); # Задание массива @т2.

print @ml, "\n", $m2[0]; # Печать массива toil и

# первого элемента массива @m2.

Массивы в Perl являются динамическими. Добавление нового элемента в массив автоматически увеличивает его размер. С помощью индекса можно добавить элемент, отстоящий от последнего определенного элемента массива на любое число элементов, и это действие не приведет к ошибке. Просто все промежуточные элементы будут не определены (их значение будет равно пустой строке ""). При такой реализации массивов программисту не надо заботиться, как в других языках программирования, что индекс выйдет за размеры массива. В случае обращения к не существующему элементу массива Perl возвращает значение, равное нулевой строке.

Замечание
Если для интерпретатора peri включен режим отображения предупреждающих сообщений во время выполнения, то в случае обращения к не определенному или не существующему элементу массива будет отображаться сообщение, что значение элемента не определено.

В любой момент можно определить число элементов массива. Для этого следует воспользоваться синтаксической конструкцией

$ #имя_массива

которая возвращает максимальный индекс массива или -1, если массив не определен. Чтобы получить количество элементов массива, следует к возвращаемому значению этой конструкции добавить 1, так как индексы массивов в Perl начинаются со.

При работе с массивами самой "утомительной" процедурой может оказаться процедура присваивания значений элементам массива. Хорошо, если все необходимые данные для заполнения массива большой размерности уже существуют в каком-либо текстовом файле. Тогда можно присвоить значения элементам массива, воспользовавшись операцией чтения из внешнего файла, но об этом в следующей главе. А если необходимо в программе создать массив натуральных чисел до 1000 включительно? Неужели надо писать 1000 операторов присваивания, или организовывать цикл, в котором осуществлять соответствующие присваивания, или создавать текстовый файл, содержащий эти числа? К счастью, нет! В Perl для подобных ситуаций предусмотрена операция диапазон, которую можно использовать в конструкторе массива. Например, чтобы создать массив натуральных чисел до 1000, достаточно одного оператора:

@naturalNumbers = (1..1000);

Общий синтаксис операции диапазон следующий:

первое_число..последнее_число

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

"а".."с" i Соответствует: "а", "b", "с"

"BCY".."BDB" # Соответствует: "BCY", "BCZ", "BDA", "ВОВ"

Эта операция особенно удобна для задания целых чисел, начинающихся с нуля, например, для представления дня месяца в дате вида "01.02.2000":

@ day_ofjnonth = ("01".."31");

(Подробнее операция диапазон рассматривается в части 4.)

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

@т = (1. .3);

print "Числа@{т}являются целыми";

будет строка

Числа! 2 являются целыми

Все правила определения идентификатора скалярной переменной при ее подстановке в строку переносятся и на переменную массива.

В связи с возможностью подстановки переменной массива можно указать быстрое решение проблемы распечатки содержимого массива. Дело в том, что если в операторе печати просто указать переменную массива, то его элементы выводятся сплошной строкой без пробелов между ними, что является неудобным при работе с числовыми данными. Если вставить переменную массива в строку, то при ее печати между элементами массива будут вставлены пробелы. Следующие два оператора печати

print @m, "\n"; print "@m\n";

отобразят массив @m предыдущего примера по-разному:

123

123

Подстановка в строку элемента массива с помощью индексного выражения ничем не отличается от подстановки скалярной переменной: элемент массива, полученный с помощью индекса, является скалярной величиной. Однако здесь может возникнуть одна интересная проблема, если в программе определена скалярная переменная с таким же идентификатором, что и у массива. Значение этой переменной или значение соответствующего элемента массива будет вставлено в строку? Например, если в программе определена скалярная переменная $var и массив @var (о том, почему это возможно, мы расскажем в разделе 3.5 данной главы), то значение переменной или элемента массива будет вставлено в строку "$var [0]".

Правильный ответ — значение элемента, так как при синтаксическом анализе интерпретатор будет рассматривать встретившуюся последовательность символов как лексему $var[0], а не как имя переменной $var с последующим символом "[". Если необходимо использовать значение скалярной переменной $var в строке, то можно предложить три способа решения этой проблемы:

"${var}[0.]" # Фигурные скобки ограничивают символы, рассматриваемые

# интерпретатором как единое целое с символом $. "$var\[0]"

# Обратная дробная черта ограничивает идентификатор

# переменной. "$var" .• "[0]" # Конкатенация строк (операция ".") позволяет однозначно

# интерпретировать переменную в первой строке.

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

@т = (10..19); # Исходный массив:

# (10, 11, 12, 13, 14, 15, 16, 17, 18, 19). @т[0, 2, 4, 6, 8];

# Фрагмент 1: (10, 12, 14, 16, 18). @т[6, 4, 5, 8, 6];

# Фрагмент 2: (16, 14, 15, 18, 16). @т[2..4];

# Фрагмент 3: (12, 13, 14). @т[8, 2..4, 0];

# Фрагмент 4: (18, 12, 13, 14, 10).

При выделении фрагмента массива используется имя переменной массива, начинающейся с символа "@", тогда как при ссылке на элемент массива префикс имени переменной заменяется на символ "$". Здесь опять прослеживается последовательное использование префикса для задания типа переменной. Фрагмент массива является массивом, а потому следует использовать символ "@".


Глава 1

Ассоциативные массивы

Ассоциативные массивы, называемые также хеш-массивами или просто хешами, — это то, чем гордятся программисты на языке Perl. Они позволяют легко создавать динамические структуры данных — списки и деревья разных видов, с помощью которых можно реализовать функциональность простой системы управления базой данных. Подобной конструкции не найти ни в одном современном языке программирования.

Ассоциативные массивы отличаются от массивов скаляров тем, что в них для ссылки на элементы используются строки, а не числовые индексы, т. е. концептуально они представляют собой список не просто значений элементов массива, а последовательность ассоциированных пар ключ/значение. Ключ является строковым литералом, и именно он и используется для доступа к ассоциированному с ним значению массива.

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

(ключ_1, значение_1, ключ_2, значение_2, ... , ключ_п, значение_п)

Для хранения ассоциативных массивов, как и для других элементов данных, используются переменные, первым символом которых является символ процента "%". Переменные, в которых хранятся ассоциативные массивы, часто называют хеш-переменными. Ассоциативный массив создается во время операции присвоения такой переменной списка значений:

%т = ("Имя", "Ларри", "Фамилия", "Уолл");

Замечание
Для краткости мы будем иногда говорить, что при создании массива его переменной присваивается список, подразумевая при этом, что в правой части операции присваивания задан конструктор массива.

В ассоциативном массиве %т ключами являются строки "имя" и "Фамилия", а ассоциированными с ними значениями, соответственно, "ларри" и "УОЛЛ". Теперь, чтобы получить значение, соответствующее какому-либо ключу, следует воспользоваться конструкцией:

$т{"ключ"}

Обратите внимание, что при работе с элементом ассоциативного массива, символ хеш-переменной "%" заменяется на символ скалярной переменной "$". Аналогично мы поступали и при ссылке на элемент массива скаляров. Единственное отличие — ключ задается в фигурных скобках. Итак, чтобы, например, присвоить некоторой скалярной переменной значение элемента хеш-массива %т, следует воспользоваться следующим оператором:

$surname = $m{"Фамилия"};

Теперь скалярная переменная $ surname имеет в качестве своего значения строку Уолл.

Замечание
Интерпретация списка как последовательности пар ключ/значение происходит только при операции его присвоения хеш-переменной. Если список, присваивается переменной массива скаляров, то он интерпретируется как простая последовательность значений элементов массива.

Инициализация хеш-массива с помощью списка, элементы которого отделяются друг от друга символом запятая ",", не очень удобно, так как в длинном списке трудно выделять соответствующие пары ключ/значение. Для улучшения наглядности пару ключ/значение можно соединить последовательностью символов "=>", заменив ей разделяющую запятую в списке. По правде говоря, и запятая ",", и символы "=>" представляют собой знаки операций в Perl, причем операция "=>" эквивалентна операции "запятая" с той лишь разницей, что ее левый операнд всегда интерпретируется как строковый литерал, даже если он не заключен в кавычки.

Замечание
Интерпретация левого операнда операции "=>" как строкового литерала справедлива для последовательности символов, в которой используются буквы латинского алфавита. Буквы русского алфавита вызовут ошибку интерпретации, так как последовательность символов не будет распознана как слово языка Perl.

Рассмотренный нами ранее хеш-массив %т можно инициировать и таким способом:

%т = (

"Имя" => "Ларри",

"Фамилия" => "Уолл" );

Если бы мы хотели в качестве индексов имени и фамилии использовать английские слова name и surname, то этот же ассоциированный массив можно было бы задать следующим оператором:

%т = (

Name => "Ларри",

Surname => "Уолл" );

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

$т{"Имя"} = "Гарри"; # Изменили значение существующего элемента. $т{"Телефон"} = "345-56-78"; # Добавили новый элемент.

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

Удалить элемент ассоциативного массива можно только с помощью встроенной функции delete:

delete($m{"Телефон"}); # Удалили элемент с ключом "Телефон".

Совет
При изменении значения элемента ассоциативного массива с помощью ключа следует проверять правильность его задания (отсутствие дополнительных пробелов, регистр букв), так как в случае несоответствия заданного ключа элемента с существующими в хеш-массив просто добавится новый элемент с заданным ключом

При работе с ассоциативным массивом часто требуется организовать перебор по множеству всех его ключей или значений. В языке существуют две встроенные функции — keys и values, которые представляют в виде списка, соответственно, ключи и значения ассоциативного массива, Следующий фрагмент программы

print keys(%m), "\n"; # Печать ключей. print values(%m), "\n";

# Печать значений.

отобразит на экране монитора строку ключей и строку значений массива %т

Фамилия Имя Телефон УоллЛарри345-56-11

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

Замечание
При создании элементов ассоциативного массива они сохраняются в памяти в порядке, удобном для их .последующего извлечения. Поэтому при его печати последовательность элементов не соответствует порядку их задания. Для упорядочивания значений хеш-массивов следует воспользоваться встроенной функцией сортировки.

Итак, хеш-массивы позволяют обращаться к своим элементам не с помощью числового индекса, а с помощью индекса, представленного строкой. Но что же здесь такого замечательного, какие возможности предоставляет подобная конструкция? Огромные. И первое, что приходит на ум, — это использовать ключи как аналог ключей реляционных таблиц. Правда, хеш-массивы не позволяют непосредственно хранить запись, а только один элемент, но и этого уже достаточно, чтобы создать достаточно сложные структуры данных (пример 3.7).

#! peri -w %friend = (

"0001", "Александр Иванов",

"0002", "Александр Петров",

"0003", "Александр Сидоров" ); %city = (

"0001", "Санкт-Петербург",

"0002", "Рязань", '"0003", "Кострома" ) ; %job = ( •

"0001", "учитель",

"0002", "программист",

"0003", "управляющий"

) ; . ' $person = "0001";

print "Мой знакомый $friend{$person}\n"; print "живет в городе $city{$person}\n"; print "и имеет профессию $job{$person}.\п";

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

Мой знакомый Александр Иванов живет в городе Санкт Петербург и имеет профессию учитель.

Если изменить значение переменной $person на другой ключ, то отобразится связанная информация о другом человеке.

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

Связанный список — это простейшая динамическая структура данных, расположенных в определенном порядке. Каждый элемент связанного списка состоит из некоторого значения, ассоциированного с данным элементом, и ссылки на следующий элемент списка. Последний элемент списка не имеет ссылки на следующий, что обычно реализуется в виде пустой ссылки. Для окончательного задания связанного списка следует объявить переменную, указывающую на первый элемент списка, которую называют заголовком. Для связанного списка определяются операции выбора, удаления и добавления элемента списка относительно заданного. Графически связанный список можно представить так, как показано на рис. 3.1, где указатель на следующий элемент обозначен серым цветом.

Рис 3.1. Графическое представление связанного списка

С помощью хеш-массивов связанный список реализуется просто. Для этого следует значение элемента задать в качестве ключа для следующего за ним элемента списка, определив таким образом указатель на следующий элемент. Значением последнего элемента в хеш-массиве (с ключом, равным значению последнего элемента связанного списка) будет пустая строка "". Переменная-заголовок должна иметь значение, равное ключу первого элемента списка. В примере 3.8 показана реализация связанного списка, а также добавление нового элемента.

%linked_list = ( "начало" => "первый", "первый" => "третий",

"третий" => ""

);

$header = "начало";

# Добавление элемента со значением "второй"

# после элемента со значением "первый".

$temp = $linked_lis.t{ "первый"};

# Запомнили старый, указатель ., $linked_list{"второй"} = $temp;

# Добавили новый элемент. $linked_list{"первый"} = "второй";

# Указатель на новый элемент.

$item = $header;

# Печать нового связанного.списка.

while ($linked_list{$item}) { # Пока не дойдем до пустой строки ""

print $linked_list{$item}, "\n"; # будем печатать значения элементов.

$item = $linked_list{$item};

}

Результатом выполнения программы примера 3.8 будет печать значений элементов нового связанного списка в следующем порядке:

первый второй третий

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


Глава 1

Переменные

Итак, мы познакомились с тремя основными типами данных и соответственно тремя типами переменных языка Perl, используемых для их обработки и хранения в программе. В этом последнем параграфе данной главы мы суммируем наши знания о переменных и дополним некоторыми аспектами, связанными с их реализацией в интерпретаторе и использованием в простейшей операции присваивания.

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

Первый символ имени переменной языка Perl определяет ее тип. В языке Perl можно определить переменные трех типов: скаляр, массив и хеш-массив. Для каждого типа переменных интерпретатор создает собственное пространство имен, храня в нем идентификаторы объявленных в программе переменных заданного типа. Это позволяет создавать переменные разных типов с одинаковыми идентификаторами. В программе Perl бесконфликтно могут сосуществовать и скалярная переменная $var, и массив скаляров @var, и хеш-массив %var.

Так как имя переменной всегда начинается с одного из символов "$", "@" или "%", то использование в качестве идентификатора "предопределенных" слов не приводит к конфликтам в процессе интерпретации программы.

Следует отметить, что в языке Perl существует достаточно большой набор специальных встроенных переменных, которые можно использовать для получения или изменения разнообразной системной информации. Большинство из них являются скалярными переменными с "идентификатором", состоящим из одного специального символа, например, $~, $__ и т. д.

(Подробно специальные переменные рассматриваются в части 14.)

Немного забегая вперед, скажем, что переменные используются в выражениях, которые, в свою очередь, могут являться операндами определенных в языке операций. Интерпретация операций и значений их операндов в Perl зависит от контекста, в котором они вычисляются. Существует два основных контекста: скалярный и списковый. Например, если левым операндом операции присваивания является скалярная переменная, то правый операнд вычисляется в скалярном контексте, т. е. его значением должен быть скаляр; если левый операнд массив или хеш (или фрагмент массива или хеша), то правый операнд вычисляется в списковом контексте, т. е. должен предоставить значение, являющееся списком.

Замечание
Использование конструктора массива с элементами, являющимися скалярными переменными, в качестве левого операнда операции присваивания предписывает вычислять правый операнд в списковом контексте.

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

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

@аггау = (0, 2, 4); # Массив скаляров баггау

# содержит три элемента: 0, 2, 4. $last = (0, 2, 4);

# Значение скалярной переменной $last равно 4.

Переменная массива скаляров в списковом контексте возвращает список всех элементов массива, а употребленная в скалярном контексте, в отличие от своего конструктора, будет иметь значение, равное числу элементов массива:

@new_array = Sarray; # Массив @new_array

# содержит все элементы массива @аггау. $number = Sarray;

# Скалярная переменная $number

# равна 3 - числу элементов массива Sarray.

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

($а, $b = (1, 2, 3); # $а = 1, $ b = 2.

($а, $b $с) = (1, 2); tt $а= 1, $ b =2, $с = "".

В языке Perl каждая операция имеет вычисляемое значение. Значением операции присваивания со скаляром в качестве правого операнда является значение этого скаляра в любом контексте. Если правым операндом является конструктор массива или массив, то в списковом контексте значением операции присваивания будет список элементов массива, а в скалярном контексте — число элементов массива.

$х = ( @а = (1, 2) ); # $х = 2.

Хеш-переменные так же, как и массивы скаляров, ведут себя по-разному в разных контекстах. Употребленные в списковом контексте, они вычисляются в виде обычного списка, состоящего из элементов всех пар ключ/значение. Однако порядок элементов в списке может не соответствовать порядку задания пар в хеше. Это связано с тем, что внутренняя реализация хеш-массивов основана на использовании хеш-таблиц для ускорения поиска соответствия ключ/значение, поэтому при вычислении хеша в виде списка порядок следования "пар" ключ/значение и не соответствует порядку их задания, но после ключа всегда следует его значение.

%hash = ( I => I, 2 =>2, 3 => 3, 4=> 4 );

@list = %hash; # @list = (blue, 3, green, 2, red, 1, black, 4)

В скалярном контексте вычисляемым значением хеш-массива является строка, состоящая из числа использованных участков записей (bucket) и числа выделенных участков записей, разделенных символом "/". Если присвоить скалярной переменной хеш-массив %hash предыдущего примера, то ее значением будет строка "4/8", которая говорит, что 4 из 8 выделенных участков записей хеш-таблицы используется.

Любая переменная в Perl может находиться в состоянии: определена или не определена. Если ей присвоено какое-либо значение, то такая переменная считается определенной. Если ей не было присвоено значение до ее использования в каком-либо выражении, то такая переменная считается не определенной. Чтобы узнать, определена или нет переменная, можно воспользоваться встроенной функцией defined, которая возвращает г (Истина) в случае определенности переменной и пустую строку "", когда переменная не определена:

@т = (1,2);

defined @т; '# Возвращает 1, массив скаляров @т определен.

defined $т; # Возвращает "", переменная $т не определена.

Определенную переменную можно в любой момент сделать неопределенной, выполнив функцию undef с параметром, равным имени этой переменной:

@т = (1,2);

defined @т; # Возвращает 1, массив скаляров @т определен.

undef @m;

defined @m; tt Возвращает "", массив скаляров @т не определен.

Если переменная сделана не определенной с помощью функции undef, TO она, естественно, теряет присвоенное ранее ей значение.

И последнее, о чем нам здесь хотелось бы упомянуть в связи с обсуждением переменных Perl, — это области видимости переменных, т. е. области доступности переменных. Во всех приведенных примерах все переменные являются глобальными, они доступны из любой точки программы. Язык Perl, однако, позволяет создавать переменные с областью видимости, ограниченной блоком или телом подпрограммы. Это так называемые локальные, или синтаксические переменные, имена которых могут совпадать с именами глобальных переменных, и которые существуют, только пока выполняются операторы некоторого блока или подпрограммы. После завершения выполнения операторов блока, эти переменные уничтожаются.

(Более подробно локальные переменные описаны в части 11.)


Глава 1

Вопросы и Упражнения

Вопросы для самоконтроля

  1. Перечислите три встроенных типа данных языка Perl.

  2. В чем отличие числового литерала от строкового. Объясните различие между строкой, ограниченной одинарными кавычками, и строкой, ограниченной двойными кавычками.

  3. Каким образом можно выполнить системную команду из программы Perl?

  4. Что такое массив скаляров и ассоциативный массив?

  5. Как задаются в программе массивы и хеш-массивы?

  6. Как объявляются в программе переменные для хранения скалярных данных, массивов скаляров и хеш-массивов?

  7. Что такое интерполяция переменной?

  8. Можно ли интерполировать массивы скаляров и хеш-массивы?

  9. Какие два контекста для операции присваивания вы знаете, и как ведут себя массивы скаляров и хеш-массивы в них?

Упражнения

  1. Найдите ошибки в следующем фрагменте кода Perl:
    $m.= 'Исходные данные:\п'; @data = ( 1, 2, 3, 4} ; print $m, 'Запись: Sdata';
  2. Что напечатают следующие операторы и почему:
    $т = "Скаляр \$m\n";
    @т = ( 1, 2, 3);
    print "Значение равно $m[0]\n";
    print "Значение равно $m [0]";
  3. Предположим, что есть группа слушателей курса по языку Perl, состоящая из 10 человек. В середине курса слушатели сдают промежуточный экзамен, а в конце — выпускную работу. За экзамен и за выпускную работу выставляется оценка по пятибалльной системе. По окончании курса каждый слушатель получает удостоверение, в котором указано, естественно, его имя, а также оценки за экзамен и выпускную работу. Разработайте базу данных слушателей курса, которую можно использовать для автоматизации подготовки удостоверений об успешном окончании курса. (Указание: воспользуйтесь хеш-массивами.)
  4. Дополните программу примера 3.8 удалением первого и последнего элемента связанного списка. (Указание: воспользуйтесь функцией delete ().)
  5. После выполнения упражнения 4 в связанном списке останется один элемент. Удалите его, распечатайте, а затем снова добавьте два элемента в список и распечатайте.


Глава 1

Часть 4. Операции и выражения

Арифметические операции
Операции конкатенации и повторения
Операции отношения
Логические операции
Побитовые операции
Операции присваивания
Ссылки и операция разыменования
Операции связывания
Именованные унарные операции
Операции ввода/вывода
Разные операции
Списковые операции
Операции заключения в кавычки
Выражения
Вопросы и Упражнения


Глава 1

Арифметические операции

Язык программирования, предоставляя возможность определения разнообразных типов данных, должен обеспечивать их обработку, т. к. его основной целью является реализация алгоритмов обработки данных. Выполнение допустимых действий над данными осуществляется с помощью набора определенных в языке программирования операций. Операция — это выполнение определенного действия над операндами, результатом которого является новое значение. С точки зрения математики операцию можно рассматривать как некую функцию, которая по заданным переменным (операндам) вычисляет новое значение. Все знают со школьной скамьи четыре основных арифметических действия, выполняемых над числами: сложение (+), вычитание (-), умножение (*) и деление (/). Эти действия (операции) мы всегда выполняем над двумя числами (операндами), получая в результате новое число. Язык программирования определяет не только арифметические операции над числовыми данными, но и операции, применимые к другим допустимым типам данных. Это могут быть операции над строками, массивами и т. д. Важно только одно, что есть операция, определяемая своим знаком, и есть участвующие в ней операнды, в совокупности позволяющие получить (вычислить) новое значение, которое может принадлежать к одному из допустимых типов. В качестве операндов можно использовать литералы, переменные и выражения, представляющие собой комбинации основных операций. Общий синтаксис операции можно представить следующим образом:

операнд знак_операции операнд

Для некоторых операций один из операндов может быть опущен. В этом случае операция называется одноместной или унарной, например, вычисление противоположного по знаку значения операнда -$т. Если в операции участвуют два операнда, то операция называется двуместной или бинарной. Практически все допустимые операции являются унарными или бинарными, но в некоторых современных языках программирования определена од-на-единственная условная тернарная операция, требующая три операнда. Значением этой операции является второй или третий операнд, в зависимости от истинности и ложности первого:

операнд_1 ? операнд_2 : операнд_3

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

В этой главе подробно рассматриваются операции языка Perl и их использование при конструировании выражений. В языке Perl определено несколько десятков операций. Их можно сгруппировать по типу выполняемых действий (арифметические, побитовые, логические, отношения, присваивания), на какие данные они воздействуют (строковые, списковые, файловые), и не относящиеся ни к одному из перечисленных типов (операции запятая, ссылки, разыменования и выбора). Следуя именно такой классификации операций языка Perl, мы и познакомим с ними нашего читателя. Одни операции будут разобраны подробно, о других мы только дадим общее представление, отнеся более детальное описание в другие главы нашей книги.

Все арифметические операции можно разбить на три группы: бинарные, унарные и увеличения/уменьшения. Их основное назначение — выполнить определенные вычисления над числовыми данными, но во всех арифметических операциях в качестве операндов могут выступать и строковые данные, причем не обязательно, чтобы они конвертировались в числовые данные.

Бинарные арифметические операции

Бинарные арифметические операции — это известные всем четыре арифметических действия: сложение (+), вычитание (-), умножение (*) и деление (/), к которым добавляются еще два: остаток от деления двух целых чисел (%) и возведение в степень (**). Примененные к числовым данным или строковым, которые содержат .правильные литералы десятичных чисел, они выполняют соответствующие арифметические действия (пример 4.1).

3.14 + 123; # Результат: 126.14

"3.14" + "123"; # Результат: 126.14

"3.14" + 123; # Результат: 126.14

"3.14" * 10; # Результат: 31.4

300 - 200; # Результат: 100

300 / 200; # Результат: 1.5

3 % 2; # Результат: 1

2 ** 3; . # Результат: 8

(-2) ** 3; # Результат: -8 '

2 ** (-3); # Результат: 0.125

2.5 ** 1.5; # Результат: -23.95284707521047

Как видим, бинарные арифметические операции "работают" именно так, как мы привыкли их использовать в обычных арифметических вычислениях в нашей повседневной жизни.

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

Замечание
 Нельзя возводить отрицательное число не в целую степень. Если такое случается, то интерпретатор не выдает никакой ошибки, но результатом такой операции является нуль: (-2.5) ** (1.3) = о.

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

"3fl4" •+ "12-30"; # Результат: 15 ("3" + "12")

"а!20" + "12-30"; # Результат: 12 ("О" + "12")

."а!20" + "-0012-30"; # Результат: -12 ("О" + "-12")

Замечание
 Если установить режим отображения предупреждающих сообщений интерпретатора (ключ -w), то при попытке использовать в бинарных арифметических операциях строки, не содержащей правильные числовые литералы, будет отображено сообщение вида:

Argument "al20" isn't numeric in add at D:\EXAMPLE1.PL line 2.

Бинарные арифметические операции выполняются в скалярном контексте. Это означает, что операндами должны быть скалярные переменные, а переменные массивов скаляров и хеш-массивов принимают значения, равные, соответственно, количеству элементов массивов скаляров или количеству использованных в хеш-таблице записей в соответствии с требованиями скалярного контекста (пример 4.3).

@m = (2, 4, 6, 8, 10) ;

%ml = ( 1 => "а", 2 => "Ь"};

$n = 100;

$n + @m; # Результат: 105 (100 + 5)

@m + %ml; # Результат: 7 (5+2)

Замечание
В скалярном контексте хеш-массив принимает строковое значение, состоящее из числа использованных участков записей в хеш-таблице и числа выделенных участков записей, разделенных символом "/" (см. главу 3). Используемое в арифметических операциях число получается выделением из этой строки числового литерала, который как раз и соответствует количеству использованных в хеш-таблице записей.

Унарные арифметические операции

В языке Perl есть только две унарные арифметические операции (+) и (-). Унарный плюс +, примененный к данным любого типа, представленным литералами или своими переменными, не имеет никакого семантического эффекта. Он полезен перед выражением в круглых скобках, стоящим непосредственно после имени функции, если необходимо чисто визуально акцентировать тот факт, что функция фактически является списковой операцией.

Унарный минус (-) выполняет арифметическое отрицание числового операнда. Это означает, что если число было отрицательным, то оно станет положительным, и наоборот. Если операндом является идентификатор, то результатом выполнения этой операции будет строка, состоящая из символа "-", за которым следует идентификатор. Если операндом является строка, начинающаяся с символа минус или плюс, то результатом также будет строка, в которой минус заменен на плюс и наоборот. Для строк, не начинающихся с плюса или минуса, операция унарного минуса добавляет его первым символом в строку. Все перечисленные случаи употребления унарного минуса показаны в примере 4.4.

-'12.09'; # Результат: -12.09

-(-12.09);•# Результат: 12.09

-id; # Результат: '-id'

-'+id"; # Результат: '-id'

-"-id"; # Результат: "+id"

-'а!20'; # Результат: '-а!20'

Операции увеличения и уменьшения

Операции увеличения (++) и уменьшения (--) аналогичны таким же операциям в языке С. (Авторы языка Perl не скрывают, что они многое заимствовали из этого языка.) Результат этих операций зависит от того, стоят ли они перед (префиксная форма) или после переменной (постфиксная форма). При использовании префиксной формы они, соответственно, увеличивают или уменьшают числовое значение переменной на единицу до возвращения значения. Постфиксная форма этих операций изменяет числовое значение переменной после возвращения ими значения. Действие этих операций на числовые переменные иллюстрируется примером 4.5 (операторы фрагмента программы выполняются последовательно).

 $n = 10.7; # Начальное значение

$infl = —$n; # Результат:
  $infl = 9.7и$n=9.7

$inf2 = ++$n; # Результат: $inf2 = 10.7 и $n = 10.7

$postl = $n--; # Результат: $postl = 10.7 но $n= 9.7

$post2 = $n++; # Результат: $post2 = 9.7 но $n= 10.7

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

Аналогичные действия осуществляются, если строка завершается последовательностью цифр: последняя цифра увеличивается на единицу. Если строка завершается идущими подряд цифрами 9, то все они заменяются на о, а примыкающий к ним символ "увеличивается" на единицу: для буквы он переходит в следующий по алфавиту, а для цифры в следующую по порядку цифру. Если последовательность целиком состоит из девяток, то все они заменяются на нули, перед которыми добавляется единица. Префиксная и постфиксная формы операции действуют как обычно. Несколько иллюстраций этих операций представлены в примере 4.6.

$s = "abc"

$sl = ++$s; # Результат: $sl = "abd"

$s = "abC";

$sl = ++$s; # Результат: $sl = "abD"

$s = "abz";

$sl = ++$s; # Результат: $sl = "аса"

$s = "abzZz";

$sl = ++$s; # Результат: $sl = "acaAa"

$s = "ab09";

$sl = ++$s; # Результат: $sl = "ablO"

$s = "99"; .

$sl = ++$s; # Результат: $sl = "100"

Замечание
Операция уменьшения (—) работает со специальными строками так же, как и с обычными. Осуществляется попытка выделить числовой литерал, начиная с первого символа. Если такое оказывается возможным, то числовое значение строки приравнивается выделенному числовому литералу, если нет — ее значение считается равным 0. После этого применяется операция уменьшения к вычисленному числовому значению строки


Глава 1

Операции конкатенации и повторения

Бинарная операция конкатенации, или соединения объединяет два строковых операнда в одну строку. Знаком этой операции служит точка ".":

"one_string"."two_string"; # Результат: "one_stringtwo_string"

В новой строке содержимое первого операнда и содержимое второго операнда соединяются без пробела между ними. Обычно эта операция используется для присваивания переменной некоторого нового значения. Если необходимо соединить две или более строки со вставкой пробелов между ними, то следует воспользоваться операцией join (см. гл. 10 "Работа со строками"). Можно, однако, для соединения строк со вставкой пробела (или любого другого символа между ними) воспользоваться свойством подстановки значения скалярной переменной в строку, ограниченную двойными кавычками: $sl = "one_string"; $s2 = "two_string"; $s = "$sl $s2"; # Значение $s: "one_string two_string"

Можно использовать операцию конкатенации строк последовательно в одном выражении для соединения нескольких строк:

$sl = "one";

$s2 = "two";

$s3 = "three";

$s = $sl.$s2.$s3; # Значение $s: "onetwothree"

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

$nl = 23.5;

$n2 = Зе01;

$n = $nl.$n2; t Значение $n: "23.530"

$n = 23.5.3е01; # Значение $n:'"23.530"

Заметим, что последний оператор выглядит несколько экзотично и его семантика не определяется с первого взгляда.

Для работы со строками в языке Perl предусмотрена еще одна операция — повторение строки х (просто символ строчной буквы "х"). Эта бинарная операция создает новую строку, в которой строка, заданная левым операндом, повторяется определяемое правым операндом количество раз:

"аА" х 2; # Результат: "аАаА"

10.0 х "3"; # Результат: "101010"

101е-1 х 3; # Результат: "101010" $n = 010;

$n х 2; # Результат: "88"

10.1 х 3.9; # Результат: "10.110.110.1"

"101е-1" х 2; # Результат: "101е-1101е-1"

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

Эта операция удобна, если надо напечатать или отобразить на экране монитора повторяющийся символ или последовательность символов. Например, следующий оператор выведет на экран монитора строку, целиком состоящую из символов подчеркивания: print "_" х 80;

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

(1) х 3; # Результат: (1, 1, 1) (1, 2) х 2; # Результат: (1, 2, 1, 2)

Это пример использования операции Perl в разных контекстах: скалярном и списковом (о контекстах мы поговорим ниже в этой же главе). Операция повторения в списковом контексте удобна для задания массива скаляров с одинаковыми значениями элементов или групп элементов:

@аггау = ("а", "b") х 2; # Результат: Оаrrау = ("а", "Ь", "а", "Ь") @аrrау = ("а") х 3; # Результат: @аrrау = ("а", "а", "а")

Аналогично, эту операцию можно использовать для инициализации хеш-массива одинаковыми значениями:

@keys = ( one, two, three); # Определение ключей хеш-массива. @hash{@keys} = ("а") х @keys; # Инициализация значений хеш-массива.

В последнем операторе присваивания в правой части массив скаляров @keys используется в списковом контексте и представляет список своих значений, тогда как в левой части он используется в скалярном контексте и имеет значение, равное числу своих элементов.

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

$nx$m;

интерпретатор определит, что в ней идут подряд две переменные $nх и $m, a не операция повторения содержимого переменной $п, что приведет к синтаксической ошибке.


Глава 1

Операции отношения

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

Таблица 4.1. Операции отношения

Операция | Числовая | Строковая | Значение
Равенство | == | eq | Истина, если операнды равны, иначе ложь
Неравенство | != | ne | Истина, если операнды не равны, иначе ложь
Меньше | < | lt | Истина, если левый операнд меньше правого, иначе ложь
Больше | > | gt | Истина, если левый операнд больше правого, иначе ложь
Меньше или равно | <= | le | Истина, если левый операнд больше правого или равен ему, иначе ложь
Больше или равно | >= | ge | Истина, если правый операнд больше левого или равен ему, иначе ложь
Сравнение | <=> | cmp | 0, если операнды равны 1, если левый операнд больше правого -1, если правый операнд больше левого

Результатом операций отношения (кроме последней сравнения) является Истина, значение 1, или Ложь, пустая строка "".

Замечание
Значение истина в арифметических операциях интерпретируется как число 1, а в строковых как строка "1". Значение ложь в арифметических операциях интерпретируется как число 0, а в строковых как пустая строка " ".

Числовые операции отношения

Числовые операции отношения применяются к числовым данным, причем один или оба операнда могут задаваться строкой, содержащей правильное десятичное число. Если в числовых операциях отношения какой-либо из операндов задан строкой, содержимое которой не представляет правильное десятичное число, то его значение принимается равным о и отображается предупреждение о некорректном использовании операнда в числовой операции отношения (если включен режим отображения предупреждений интерпретатора Perl). Смысл операций отношения для числовых данных соответствует обычным математическим операциям сравнения чисел (пример 4.7).

123 > 89; # Результат: 1 (истина)

123 < 89; # Результат: "" (ложь)

123 <= 89; # Результат: "" (ложь)

89 <= 89; # Результат: 1 (истина)

23 >= 89; # Результат: "" (ложь)

23 <=> 89; # Результат: -1 (правый операнд больше левого)

89 <=> 23; # Результат: 1 (правый операнд больше левого)

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

#! peri -w

$z = 0.7;

$zz =• 10+0.7-10; # Переменная $zz содержит число 0.7

# Печать строки "z равно zz", если равны значения переменных $z и $zz print "z равно zz\n" if ($z == $zz);

При попытке выполнить пример 4.8 мы с удивлением обнаружим, что наша программа ничего не напечатает. В чем же дело? Разгадка лежит в операторе вычисления значения переменной $zz. При выполнении арифметических операций в результате ошибок округления получается значение 0.699999999999999 (можете вставить оператор печати переменной $zz и убедиться в этом), хотя и близкое к 0.7, но не равное ему в точности. Следовательно, операция сравнения отработала верно!

Совет
Не используйте операцию сравнения на равенство вещественных чисел, ее результат может не соответствовать ожидаемому с точки зрения математики. Если необходимо проверить равенство двух вещественных чисел, то лучше сравнивать абсолютное значение их разности с некоторым очень маленьким числом (в зависимости от требуемой точности):

abs($a-$b) <= 0.00000001; # Проверка равенства

Строковые операции отношения

Сравнение строковых данных базируется на их упорядочении в соответствии с таблицей кодов ASCII, т. е. символ с меньшим кодом ASCII предшествует символу с большим кодом. Сравнение строк осуществляется посимвольно слева направо. Это означает, что если равны первые символы строк, то сравниваются вторые и если они равны, то сравниваются третьи и т. д. Причем, если строки разной длины, то в конец строки меньшей длины добавляется недостающее для равенства количество символов с кодом о. Следует отметить, что в отличие от некоторых других языков программирования в Perl замыкающие строку пробельные символы являются значимыми при сравнении строк. В примере 4.9 показаны сравнения строк, иллюстрирующие изложенные правила.

"A" It "a"; | # Результат: истина (код "А" - \101, код "а" - \141)
"a" It "aa"; | # Результат: истина (к строке "а" добавляется символ # с кодом \000, который меньше кода \141 # второго символа "а" строки правого операнда)
"a" It "a "; | # Результат: истина (к строке "а" добавляется символ # с кодом \000, который меньше кода \040 # замыкающего пробела строки правого операнда)
"12" It "9"; | # Результат: истина (код "1" - \061, код "9" - \071)
" 9" eq "09"; | # Результат: ложь (код " " - \040, код "О" - \060)

Обратим внимание на две последние операции сравнения строковых литералов. Содержимое их операндов может быть преобразовано в правильные числа, и поэтому к ним применимы аналогичные числовые операции отношения. Однако их результат будет существенно отличаться от результата выполнения строковых операций отношения. При использовании операции < в предпоследнем выражении результат будет Ложь, а если в последнем выражении применить операцию ==, то результат будет Истина. Об этом всегда следует помнить, так как Perl автоматически преобразует символьные данные в числовые там, где это необходимо.


Глава 1

Логические операции

Рассмотренные в предыдущем параграфе операции сравнения используются в условном операторе if (о нем и других операторах Perl в следующей главе) для организации ветвления в программе. Однако, иногда желательно проверять одновременно результаты нескольких операций сравнения и предпринимать соответствующие алгоритму действия. Можно подобную ситуацию запрограммировать с помощью вложенных операторов if, а можно в одном операторе использовать сложное выражение, результатом вычисления которого будет, например, истинность двух или более каких-либо операций сравнения. Для формирования подобных проверок и служат логические операции языка Perl.

В языке определены бинарные операции логического сравнения | 1 (ИЛИ), s & (И) и унарная операция логического отрицания !. Их действие аналогично действию соответствующих математических операций исчисления предикатов. Результатом операции | | (логическое ИЛИ) является Истина, если истинен хотя бы один из операндов, в остальных случаях она возвращает Ложь (остальные случаи представляют единственный вариант, когда оба операнда ложны). Операция логического И && возвращает в качестве результата Истину, только если оба операнда истинны, в противном случае ее результат Ложь. Операция логического отрицания ! работает как переключатель: если ее операнд истинен, то она возвращает Ложь, если операнд имеет значение Ложь, то ее результатом будет Истина.

Замечание
В языке Perl нет специальных литералов для булевых значений Истина и Ложь. В качестве значения Истина принимается любое скалярное значение, не равное нулевой строке "" или числу 0 (а также его строковому эквиваленту "О"). Естественно, нулевая "" строка и о (вместе с его строковым эквивалентом "О") представляют значение Ложь.

Начиная с Perl 5.001, в язык были введены логические операции or, and, not и хог. Первые три полностью аналогичны логическим операциям | |, && и !, тогда как операция хог реализует исключающее ИЛИ:

Истина хоr Истина = Ложь

Истина хоr Ложь = Истина

Ложь хоr Истина = Истина

Ложь хоr Ложь = Ложь

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

В Perl вычисление логических операций ИЛИ и И осуществляется по "укороченной схеме". Это непосредственно связано со смыслом этих операций. Если при вычислении операции ИЛИ определено, что значение ее первого операнда Истина, то при любом значении второго операнда результатом всей операции будет Истина, поэтому нет смысла вообще вычислять второй операнд. Аналогично для операции логического И: если значение первого операнда Ложь, то результат всей операции Ложь вне зависимости от значения второго операнда. В отличие от операций отношения, результатом которых может быть о (или пустая строка "") или 1, соответствующие булевым значениям Ложь и Истина, результатом логических операций является значение последнего вычисленного операнда. Пример 4.10 иллюстрирует вычисление логических операций.

$opl = 0; $ор2 = "s"; $орЗ = ""; $ор4 = 25; $ор5 = "0";

$ор4 II $ор2; # Результат: истина. Значение: 25.

$ор2 I| $ор4; # Результат: истина. Значение: "s".

$opl && $ор2; # Результат: ложь. Значение: 0.

$ор2 && $ор4; # Результат: истина. Значение: 25.

!$ор2; # Результат: ложь. Значение: "".

not $орЗ; # Результат: истина. Значение: 25.

$ор4 and $op5; # Результат: ложь. Значение: "".

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

($х = 0) II ($т = 1/$х);

При вычислении результата этой операции сначала вычисляется левый операнд, который сравнивает значение переменной $х с нулем. Если это значение действительно равно нулю, то результатом операции сравнения будет Истина, а поэтому второй операнд операции логического ИЛИ не вычисляется, так его значение не влияет на результат выполнения логической oпeрации, и не возникает ситуации деления на нуль. Если значение переменной $х не равно нулю, то результатом вычисления первого операнда операции | | будет Ложь, и обязательно будет вычисляться ее второй операнд, в котором осуществляется деление на не равную нулю переменную $х.


Глава 1

Побитовые операции

Данные в компьютере представляются в виде последовательности битов. В языке Perl определены бинарные операции побитового логического сравнения целых чисел и строк: & (И), | (ИЛИ) и л (исключающее ИЛИ), а также унарная операция логического отрицания ~. Результат их вычисления зависит от того, к данным какого типа они применяются: числовым или строковым. Эти операторы различают числовые данные и строки, содержимое которых может быть преобразовано в число.

Кроме логических операций побитового сравнения, две операции сдвигают влево («) и вправо (») биты в представлении целых чисел. Эти операторы не работают со строками.

Числовые операнды

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

Чтобы понять сущность побитовых операций над числовыми данными, необходимо представлять, как хранятся в программе целые числа. При задании чисел мы можем использовать одно из трех представлений: десятичное, восьмеричное или шестнадцатеричное. Однако в компьютере числа не хранятся ни в одном из указанных представлений. Они переводятся в двоичные числа — числа с основанием 2, цифры которых и называются битами. Двоичные числа представляют собой запись чисел в позиционной системе счисления, в которой в качестве основания используется число 2. Таким образом, двоичные цифры, или биты, могут принимать значения только о или 1. Например, десятичное число ю, переведенное в двоичное, представляется в виде loio. Для обратного перевода этого числа в десятичную форму представления следует, в соответствии с правилами позиционной системы счисления, произвести следующие действия:

1 * (2**3) + 0 * (2**2) + 1 * -(2**1) + 0 * (2**0)

Язык Perl гарантирует, что все целые числа имеют длину 32 бит, хотя на некоторых машинах они могут представляться и 64 битами. Именно с двоичными представлениями целых чисел и работают все побитовые операции, преобразуя и отрицательные, и положительные числа к целому типу данных.

Операция & побитового логического И сравнивает каждый бит правого операнда с соответствующим битом левого операнда. Если оба сравниваемых бита имеют значение 1, то соответствующий бит результирующего значения операции устанавливается равным 1, в противном случае значением бита результата будет о. В качестве примера рассмотрим следующее выражение

45.93 & 100

Прежде всего, правый операнд преобразуется к целому 45, двоичное представление которого будет

00000000000000000000000000101101

Двоичное представление левого операнда юо будет иметь вид

00000000000000000000000001100100

Результатом побитового логического И будет следующая последовательность битов

00000000000000000000000000100100

Она соответствует десятичному целому числу 36. Следовательно, значением выражения 45.93 & юо будет целое число 36.

Замечание
В этом примере мы специально использовали 32-битное представление целых чисел, хотя для бинарных побитовых операций лидирующие нули не имеют значения. Они существенны только при операциях побитового логического отрицания и сдвигов.

При побитовой операции логического ИЛИ | бит результата устанавливается равным 1, если хотя бы один из сравниваемых битов равен 1. Операция побитового логического ИЛИ для тех же двух чисел

45.93 | 100

даст результат равный юэ, так как при применении побитового логического ИЛИ к операндам

00000000000000000000000000101101 (десятичное 45)

И

00000000000000000000000001100100 (десятичное 100)

дает следующую цепочку битов

00000000000000000000000001101101 (десятичное 109:

2**6+2**5+2**3+2'**2+2**1)

Побитовое исключающее ИЛИ при сравнении битов дает значение \ тогда, когда точно один из операндов имеет значение равное 1. Следовательно, 1 ^ 1=о и о ^ о=о, в остальных случаях результат сравнения битов равен о. Поэтому для тех же чисел результатом операции будет десятичное число 73.

Операция логического отрицания ~ является унарной и ее действие заключается в том, что при последовательном просмотре битов числа все значения о заменяются на 1, и наоборот. Результат этой операции существенно зависит от используемого количества битов для представления целых чисел. Например, на 32-разрядной машине результатом операции ~1 будет последовательность битов

11111111111111111111111111111110

представляющая десятичное число 42949б7294=2 31 +2 30 +...+2 1 , тогда как на 16-разрядной машине эта же операция даст число б534=2 31 +2 30 +...+2 1 .

Бинарные операции побитового сдвига осуществляют сдвиг битов целого числа, заданного левым операндом, влево («) или вправо (») на количество бит, определяемых правым целочисленным операндом. При сдвиге вправо недостающие старшие биты, а при сдвиге влево младшие биты числа дополняются нулями. Биты, выходящие за разрядную сетку, пропадают. Несколько примеров операций сдвига представлено ниже:

# Битовое представление числа 22: (00000000000000000000000000010110)

22 » 2 # Результат: (00000000000000000000000000000101) = 5

22 << 2 # Результат: (00000000000000000000000001011000) = 88

Все перечисленные операции работают и с отрицательными целыми числами, только при их использовании следует учитывать, что они хранятся в дополнительном коде. Двоичная запись неотрицательного целого числа называется прямым кодом. Обратным кодом называется запись, полученная поразрядной инверсией прямого кода. Отрицательные целые числа представляются в памяти компьютера в дополнительном коде, который получается прибавлением единицы к младшему разряду обратного кода. Например, представление числа -1 получается следующим образом:

00000000000000000000000000000001 # положительное число 1 111111111111111111111111111111Ю # обратный код числа 1 11111111111111111111111111111111 # добавляем к младшему разряду 1

# и получаем представление числа -1

Именно с этим кодом числа -i будут работать все побитовые операции, если оно будет задано в качестве операнда одной из них.

Внимание

В языке Perl, как отмечалось в гл. 3, в арифметических операциях используется представление всех чисел в виде чисел с плавающей точкой удвоенной точности. Там же говорилось, что целое число можно задавать с 15 значащими цифрами, т.е. максимальное положительное целое число может быть 999 999 999 999 999. Но это число не имеет ничего общего с представлением целых чисел в компьютере, для которых может отводиться 64, 32 или 16 битов, в зависимости от архитектуры компьютера. Во всех побитовых операциях можно предсказать результат только если операндами являются целые числа из диапазона -2 32 -1. . 2 32 -1, так как ясен алгоритм их представления. Вещественные числа, не попадающие в этот диапазон, преобразуются к целым, но алгоритм их преобразования не описан авторами языка.

Строковые операнды

Если оба операнда являются строковыми литералами или переменными, содержащими строковые данные, то операции побитового логического сравнения сравнивают соответствующие биты кода каждого символа строки. Для кодирования символов используется таблица ASCII-кодов. Если строки разной длины, то при сравнении полагается, что строка меньшей длины содержит необходимое число недостающих символов с восьмеричным кодом \ооо. Например, результатом сравнения двух строк "++" и "з" с помощью операции | побитового логического ИЛИ будет строка ",-+". Операнды этой операции представляются следующими битовыми последовательностями (каждый символ представляется 8 битами):

00101011 00101011 # Восьмеричный код символа "+" равен 053. 00110011 # Восьмеричный код символа "3" равен 063.

Вторая строка дополняется восемью нулевыми битами и последовательно для каждой пары соответствующих бит двух строк одинаковой длины выполняется операция логического ИЛИ. Результатом выполнения этой процедуры является битовая последовательность

00111011 00101011

При ее интерпретации как строки символов получается последовательность двух символов с восьмеричными кодами 07 3 и 053, которые соответствуют символам ";" и "+"

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

45.93 I 100 # Результат: число 109.

"45.93" I 100 # Результат: число 109.

45.93 I "100" # Результат: число 109.

"45.93" I "100" # Результат: строка "55>93".

В первых трех операциях этого примера строки преобразуются в числа, а потом выполняется соответствующая операция побитового логического ИЛИ двух чисел.

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

~"1" # Результат: "+".

~"ab" t Результат: "ЮЭ".


Глава 1

Операции присваивания

Присваивание переменной какого-либо значения, определенного литералом, или присваивание одной переменной значения другой переменной является наиболее часто выполняемым действием в программе, написанной на любом языке программирования. В одних языках это действие определяется с помощью оператора, а в других — с помощью операции. Отличие заключается в том, что в языках, где присваивание является операцией, оно может использоваться в выражениях как его составная часть, так как любая операция вычисляет определенное значение, тогда как оператор всего лишь производит действие. В языке Perl присваивание является операцией, которая возвращает правильное lvalue. Что это такое, мы разъясним буквально в следующих абзацах.

Операция присваивания =, с которой читатель уже немного знаком, является бинарной операцией, правый операнд которой может быть любым правильным выражением, тогда как левый операнд должен определять область памяти, куда операция присваивания помещает вычисленное значение правого операнда. В этом случае и говорят, что левый операнд должен быть правильным lvalue (от английского left value — левое значение). А что мы можем использовать в программе для обозначения области памяти? Правильно, переменные. Следовательно, в качестве левого операнда операции присваивания можно использовать переменную любого типа или элемент любого массива. (В языке Perl существуют и другие объекты, которые можно использовать в качестве левого операнда операции присваивания, но об этом в свое время.) Следующая операция простого присваивания $а = $Ь+3;

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

($а = $Ь) = 3;

является синтаксически правильной и в результате ее вычисления переменной $а будет присвоено значение з, так результатом вычисления операции присваивания $а = $ь будет присвоение переменной $а значения переменной $ь, а возвращаемым значением можно считать переменную $а, которой в следующей операции присваивается значение з. Читатель спросит: "А зачем городить такие сложности, если тот же самый результат можно получить простой операцией присваивания $а = з?". Действительно, замечание справедливое. Но на этом примере мы показали, как можно использовать операцию присваивания в качестве правильного lvalue. Более интересные примеры мы покажем, когда определим составные операции присваивания, заимствованные из языка С.

Синтаксические правила языка Perl позволяют осуществлять присваивание одного и того же значения нескольким переменным в одном выражении:

$varl = $var2 = $varl[0] =34;

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

$а = $а+3;

Однако, язык Perl предлагает более эффективный способ решения подобных проблем, предоставляя в распоряжение программиста бинарную операцию составного присваивания +=, которая прибавляет к значению левого операнда, представляющего правильное lvalue, значение правого операнда и результат присваивает переменной, представленной левым операндом. Таким образом, оператор составного присваивания

$а += 3; I Результат: $а = $а+3

эквивалентен предыдущему оператору простого присваивания. Единственное отличие заключается в том, что его реализация эффективнее реализации простого присваивания, так как в составном операторе присваивания переменная $а вычисляется один раз, тогда как в простом ее приходится вычислять дважды. (Под вычислением переменной понимается вычисление адреса представляемой ею области памяти и извлечение значения, хранящегося в этой области памяти.)

Для всех бинарных операций языка Perl существуют соответствующие составные операции присваивания. Все они, вместе с примерами их использования, собраны в табл. 4.2.

Таблица 4.2. Составные операции присваивания

Операция | Пример | Эквивалент с операцией простого присваивания
**= | $а **= 3; | $а = $а ** 3;
+= | $а += 3; | $а = $а + 3;
-= | $а -= 3; | $а = $а - 3;
.= | $а .= "а"; | $а = $а . "а";
*= | $а *= 3; | $а = $а * 3;
/= | $а /= 3; | $а = $а / 3;
%= | $а %= 3; | $а = $а % 3;
х= | $а х= 3; | $а = $а х 3;
&= | $а &= $b; | $а = $а & $b;
|= | $а |= 3; | $а = $а | 3;
^= | $а ^= 3; | $а = $а ^ 3;
«= | $а «= 3; | $а = $а « 3;
»= | $а »= 3; | $а = $а » 3;
&&= |  |
||= | $а ||= $b == 0; | $а = $а || $b == 0;

Возвращаемым значением каждой из составных операций присваивания, как и в случае простого присваивания, является переменная левого Операнда (правильное lvalue), поэтому их можно использовать в любом операнде других операций присваивания (пример 4.11).

$b = 1; |
$а = ($Ь +=3); | # Результат: $а = $b = 4
$а += ($Ь += 3); | # Результат: $а = $а+$b+3
( ($а += 2) **= 2) -= 1; | # Результат: $а = ($а+2)**2-1

Замечание
При использовании операции присваивания (простой или составной) в качестве левого операнда другой операции присваивания обязательно ее заключение в круглые скобки. Иначе может сгенерироваться синтаксическая ошибка, или выражение будет интерпретировано не так, как задумывалось. При наличии нескольких операций присваивания в одном выражении без скобок интерпретатор peri начинает его разбор справа. Например, если последнее выражение примера 4.11 записать без скобок

$а += 2 **= 2 -•= 1;

то при его синтаксическом анализе интерпретатор сначала выделит операцию присваивания

2 -= 1;

и сообщит об ошибке, так как ее синтаксис ошибочен (левый операнд не является переменной или элементом массива).


Глава 1

Ссылки и операция разыменования

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

$m ='5;

$рт = \$m; f Ссылка на скалярную величину

Ссылки хранятся в скалярных переменных и могут указывать на скалярную величину, на массив, на хеш и на функцию:

@аrrау = (1,2,3);

$раггау = \@аггау; # Ссылка на массив скаляров

%hesh = (опе=>1, two=>2, three=>3);

$phesh = \%hesh; § Ссылка на массив скаляров

Если распечатать в программе переменные-ссылки $pm, $parray и $phesh, то мы увидим строки, подобные следующим:

SCALAR(Ox655a74) ARRAY (Ох655ЫО) HASH(0x653514)

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

Для получения содержимого области памяти, на которую ссылается переменная-указатель, требуется выполнить операцию разыменования ссылки. Для этого достаточно перед именем такой переменной поставить символ, соответствующий типу данных, на который ссылается переменная ($, @, %):

@keys = keys(%$phash); # Массив ключей хеша @values = values(%$phash); # Массив значений хеша print "$$pm \n@$parray \n@keys \n@values";

Этот фрагмент кода для определенных в нем переменных-ссылок на скаляр, массив и хеш напечатает их значения:

5 # Значение скалярной переменной $m

123 # Значения элементов массива скаляров @аrrау

three two one # Ключи хеша %hash

321 # Значения хеша %hash

Использование описанной выше простой операции разыменования может приводить к сложным, трудно читаемым синтаксическим конструкциям при попытке получить значения элементов сложных конструкций: массива массивов, массива хешей и т. п. Для подобных целей в языке Perl предусмотрена бинарная операция ->, левым операндом которой может быть ссылка на массив скаляров или хеш-массив, а правым операндом индекс элемента массива или хеша, значение которого необходимо получить: print "$parray->[0], .$parray->[1], .$parray->[2]\n"; print "$phash->{one} r $phash->{two}, $phash->{three}\n";

Эти операторы напечатают значения элементов массива ©array и хеша %hash.
(Более подробно ссылки и операции разыменования рассматриваются в части 9.)


Глава 1

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

Операции сопоставления с образцом, используемые многими утилитами обработки текста в Unix, являются мощным средством и в языке Perl. Эти операции с регулярными выражениями включают поиск (m//), подстановку (s///) и замену символов (tr///) в строке. По умолчанию они работают со строкой, содержащейся в системной переменной $_. Операции =~ и \ ~ связывают выполнение сопоставления с образцом над строкой, содержащейся в переменной, представленной левым операндом этих операций: $_ = "It's very interesting!";

s/very/not/; # Переменная $_ будет содержать строку

# "It's not interesting!"

$m = "my string";

$m =~ s/my/our/; i Переменная $m будет содержать строку

tt "our string"

Возвращаемым значением операции =~ является Истина, если при выполнении соответствующей ей операции сопоставления с образцом в строке была найдена последовательность символов, определяемая регулярным выражением, и Ложь в противном случае. Операция ! ~ является логическим дополнением к операции =~. Следующие два выражения полностью эквивалентны:

$m !~ m/my/our/; not $m =~ m/my/our/;

(Более подробно регулярные выражения и операции связывания рассматриваются в части 10.)


Глава 1

Именованные унарные операции

В языке Perl определено большое количество встроенных функций, выполняющих разнообразные действия. Некоторые из них, с точки зрения синтаксического анализатора языка, на самом деле являются унарными операциями, которые и называют именованными унарными операциями, чтобы отличить их от унарных операций со специальными символами в качестве знаков операций (например, унарный минус "-", операция ссылки "\", логического отрицания "!" и т. д.). Некоторые из именованных унарных операций перечислены ниже:

chdir, cos, defined, goto, log, rand, rmdir, sin, sqrt, do, eval, return (Является ли функция унарной операцией, можно определить в приложении 1.)

К именованным унарным операциям относятся также все операции проверки файлов, синтаксис которых имеет вид:

-символ [имя_файла\дескриптор_файла]

Например, для проверки существования файла определена операция -е, выяснить возможность записи в файл можно операцией -w.

(Более подробно операции проверки файлов рассматриваются в части 7 щелкни здесь.)


Глава 1

Операции ввода/вывода

Для взаимодействия и общения с внешним окружением в любом языке программирования предусмотрены операции ввода/вывода. Perl не является исключением. В нем определен ряд операций, обеспечивающих ввод и вывод данных в/из программы.

Операция print

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

print "@m", "\n", $m, "\n"; print("@m", "\n", $m, "\n");

(Более подробно эта операция рассматривается в части 6.)

Выполнение системных команд

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

$command = ~dir"; # Переменная $command после выполнения операционной . # системой KOMaHflbi'dir' содержит результат ее # выполнения.

Содержимое строкового литерала в обратных кавычках должно быть, после подстановки значений переменных, которые могут в нем присутствовать, правильной командой операционной системы.

(Более подробно эта операция рассматривается в части 6.)

Операция <>

При открытии файла с помощью функции open () одним из ее параметров является идентификатор, называемый дескриптором файла, с помощью которого можно в программе Perl ссылаться на файл. Операция ввода из файла осуществляется заключением в угловые скобки его дескриптора <дескриптор_файла>. Результатом вычисления этой операции является строка файла или строки файла в зависимости от скалярного или спискового контекста ее использования. Следующие операторы иллюстрируют эту особенность данной операции:

open( MYFILE, "data.dat"); tt Открытие файла "data.dat" и назначение ему

# дескриптора MYFILE

$firstline = <MYFILE>; # Присваивание первой строки файла @remainder = <MYFILE>; # Оставшиеся строки файла присваиваются

# элементам массива скаляров.

Дескриптор файла можно использовать и в операции print, организуя вывод не на стандартное устройство вывода, а в файл, представляемый дескриптором:

print MYFILE @m;

Особый случай представляет операция чтения из файла с пустым дескриптором о: информация считывается либо из стандартного файла ввода, либо из файлов, заданных в командной строке.

(Более подробно операции ввода/вывода из/в файл рассматриваются в части 6.) (Работа с файлами более подробно рассматривается в части 7.)


Глава 1

Разные операции

В этом параграфе собраны операции, которые не вошли ни в одну из рассмотренных нами групп операций. Две из них упоминались при описании массивов и хешей (операции диапазон и запятая), а третья является единственной тернарной операцией языка Perl (операция выбора).

Операция диапазон

Бинарная операция диапазон ".." по существу представляет две различных операции в зависимости от контекста, в котором она используется.

В списковом контексте, если ее операндами являются числа (числовые литералы, переменные или выражения, возвращающие числовые значения), она возвращает список, состоящий из последовательности увеличивающихся на единицу целых чисел, начинающихся со значения, определяемого левым операндом, и не превосходящих числовое значение, представленное правым операндом. Операцию диапазон часто используют для задания значений элементов массивов и хешей, а также их фрагментов (см. главу 3). Она удобна для организации циклов for и f oreach:

# Напечатает числа от 1 до 5, каждое на новой строке, foreach $cycle (1..5){ print "$cycle\n"; }

# Напечатает строку "12345".

for(l..5){

print; '

}

(Операторы цикла рассматриваются в части 5.)

Замечание
Если значение какого-либо операнда не является целым, оно приводится к целому отбрасыванием дробной части числа.

Если левый операнд больше правого операнда, то операция диапазон возвращает пустой список. Подобную ситуацию можно отследить с помощью функции defined О, возвращающей истину, если ее параметр определен, или простой проверкой логической истинности массива, элементам которого присваивались значения с помощью операции диапазон:

$min = 2; .

$max = ~2;

Эаггау = ($min .. $max); # Массив не определен.

print "Эаггау array\n" if defined(@array); # Печати не будет!

print "Sarray array\n" if Эаггау; # Печати не будет!

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

(-5..5) # Список чисел: (-2, -1, О, 1, 2). (-5..-10) # Пустой список.

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

@а = ("a".."d" ); # Массив @а: "а", "Ь", "с", "d"

@а = ("01".."31" ); # Массив @а: "01", "02", ... , "30", "31"

@а = ("al".."d4" ); # Массив Эа: "al", "a2", "аЗ", "а4"

Если левый операнд меньше правого, с точки зрения лексикографического порядка, то возвращается единственное значение, равное левому операнду.

Замечание
Операция диапазон не работает со строками, содержащими символы национальных алфавитов. В этом случае она всегда возвращает единственное значение, соответствующее левому операнду.

В скалярном контексте операция диапазон возвращает булево значение Истина или Ложь. Она работает как переключатель и эмулирует операцию запятая "," пакетного редактора sed и фильтра awk системы Unix, представляющую диапазон обрабатываемых строк этими программами.

Каждая операция диапазон поддерживает свое собственное булево состояние, которое изменяется в процессе ее повторных вычислений по следующей схеме. Она ложна, пока ложным остается значение ее левого операнда. Как только левый операдц становится истинным, операция диапазон переходит в состояние Истина и находится в нем до того момента, как ее правый операнд не станет истинным, после чего операция снова переходит в состояние Ложь. Правый операнд не вычисляется, пока операция находится в состоянии Ложь; левый операнд не вычисляется, пока операция диапазон находится в состоянии Истина.

В состоянии Ложь возвращаемым значением операции является пустая строка, которая трактуется как булева Ложь. В состоянии Истина при повторном вычислении она возвращает следующее порядковое число, отсчет которого начинается с единицы, т. е. как только операция переходит в состояние Истина, она возвращает 1, при последующем вычислении, если она все еще находится в состоянии Истина, возвращается 2 и т. д. В момент, когда операция переходит в состояние Ложь, к ее последнему возвращаемому порядковому числу добавляется строка "ко", которая не влияет на возвращаемое значение, но может быть использована для идентификации последнего элемента в диапазоне вычислений. Программа примера 4.12 и ее вывод, представленный в примере 4.13, иллюстрируют поведение оператора диапазон в скалярном контексте. Мы настоятельно рекомендуем внимательно с ними ознакомиться, чтобы "почувствовать", как работает эта операция.

#! peri -w

$left =3; # Операнд! $right =2; # Операнд2

# Заголовок таблицы

print "\$i\tflnana3OH\tOnepaHfll\tOnepaHfl2\n";

print '-' х 48, "\n\n";

# Тест операции

for($1=1; $i <= 10; $i++) {

$s = $left..$right;

print "$i\t $s\t\t $left \t\t $right\n";

$slO = 3 if $i==5; # Когда переменная цикла $i равна 5, # $slO устанавливается равной 3.

if ($right==0) {} else {—$right}; # Уменьшение $right на 1, пока

# $right не достигла значения 0.

—$left; }

Замечание
В целях экономии времени мы не объясняем смысл незнакомых операторов примера 4.12, надеясь, что читатель сможет понять их смысл. Если все же это окажется для него сложным, мы рекомендуем снова вернуться к этой программе после прочтения главы 5.

$1 | Диапазон | Операнд1 | Операнд2
1 | 1ЕО | 3 | 2
2 | 1ЕО | 2 | 1
3 | 1 | 1 | 0
4 | 2 | 0 | 0
5 | 3 | -1 | 0
6 | 4EO | -2 | 2
7 | 1EO | -3 | 1
8 | 1 | -4 | 0
9 | 2 | -5 | 0
10 | 3 | -6 | 0

Сделаем замечания относительно работы программы примера 4..12. На первом шаге цикла левый операнд операции диапазон истинен, следовательно сама операция находится в состоянии Истина и возвращает первое порядковое число (i). Но правый операнд становится также истинным ($right = 2), следовательно она переходит в состояние Ложь и к возвращаемому ей значению добавляется строка "ЕО". На втором шаге цикла левый операнд истинен ($ieft = 2) и операция переходит в состояние Истина, возвращая значение д, к которому опять добавляется строка "ЕО", так как истинный правый операнд ($ right = 1) переводит операцию в состояние Ложь.

На третьем шаге операция становится истинной ($ieft = 1), возвращая i, и правый операнд со значением Ложь ($right = о) не влияет на ее состояние. На следующих шагах 4 и 5 правый операнд остается ложным, а операция возвращает соответственно следующие порядковые числа 2 и з. На шаге 6 операция находится в состоянии Истина и возвращает 4, но правый операнд, принимая значение Истина ($right = о), переводит ее в состояние Ложь, в котором к возвращаемому значению добавляется строка "ЕО" и т. д.

Подобное поведение, связанное с переходом из состояния Истина в состояние Ложь и одновременным изменением возвращаемого значения (добавлением строки "ЕО") эмулирует поведение операции запятая фильтра awk. Для эмуляции этой же операции редактора sed, в которой изменение возвращаемого значения осуществляется при следующем вычислении операции диапазон, следует вместо двух точек в знаке операции ".." использовать три точки "...". Результаты вывода программы примера 4.12, в которой осуществлена подобная замена, представлены в примере 4.14.

$i | Диапазон | Операнд 1 | Операнд2
1 | 1 | 3 | 2
2 | 2EO | 2 | 1
3 | 1 | 1 | 0
4 | 2 | 0 | 0
5 | 3 | -1 | 0
6 | 4EO | -2 | 2
7 | 1 | -3 | 1
8 | 2 | -4 | 0
9 | 3 | -5 | 0
10 | 4 | -6 | 0

Еще одно достаточно полезное свойство операции диапазон в скалярном контексте, используемое при обработке строк файлов, заключается в том, что если какой-либо операнд этой операции задан в виде числового литерала, то он сравнивается с номером прочитанной строки файла, хранящейся в специальной переменной $., возвращая булево значение Истина при совпадении и Ложь в противном случае. В программе примера 4.15 иллюстрируется такое использование операции диапазон. В ней осуществляется пропуск первых не пустых строк файла, печатается первая строка после пустой строки и после этого завершается цикл обработки файла.

#! peri -w

open{POST, "file.txt") or die "Нельзя открыть файл file.txt!";

LINE:

while(<POST>) {

$temp = 1../^$/; # Истина, пока строка файла не пустая.

next LINE if ($temp); # Переход на чтение новой строки,

# если $temp истинна.

print $_; # Печать первой строки файла после не пустой

last; # Выход из цикла

}

close(POST);

В этой программе для нас интересен оператор присваивания возвращаемого значения операции диапазон переменной $temp. Прежде всего отметим, что эта операция используется в скалярном контексте, причем ее левый операнд представлен числовым литералом. На первом шаге цикла читается первая строка файла и специальной переменной $. присваивается ее номер 1, который сравнивается со значением левого операнда операции диапазон. Результатом этого сравнения является булево значение Истина, и операция диапазон переходит в состояние Истина, в котором она и остается, если первая строка файла не пустая, так как операция поиска по образцу / Л $/ возвращает в этом случае Ложь. Операция next осуществляет переход к следующей итерации цикла, во время которой читается вторая строка файла. Операция диапазон остается в состоянии Истина, если прочитанная строка файла пустая, увеличивая возвращаемое значение на единицу. Далее операция next снова инициирует чтение новой строки файла. Если строка файла пустая, то операция поиска по образцу возвращает значение Истина, переводя тем самым операцию диапазон в состояние Ложь, которое распознается при следующем ее вычислении, поэтому считывается еще одна строка файла (первая после пустой). Теперь операция next не выполняется, так как операция диапазон возвращает Ложь, печатается первая не пустая строка и операция last прекращает дальнейшую обработку файла.

Операция запятая

Бинарная операция запятая "," ведет себя по-разному в скалярном и списковом контексте.

В списковом контексте она является всего лишь разделителем между элементами списка:

@а = (1, 2); # Создается массив скаляров

# и его элементам присваиваются значения 1 и 2.

В скалярном контексте эта операция полностью соответствует аналогичной операции языка С: вычисляется левый операнд, затем правый операнд, вычисленное значение которого и является возвращаемым значением этой операции. Если в предыдущем примере заменить массив @ а скалярной переменной $ а, то ей будет присвоено значение 2:

$а = (1, 2); # Переменной $а присваивается значение 2.

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

Для операции запятая в языке Perl существует удобный синоним — операция =>, которая полностью идентична операции запятая и удобна при задании каких-либо величин, которые появляются парами, например, ключ/значение в ассоциированных массивах. Правда, эта операция обладает еще одним свойством, достаточно удобным для ее использования при задании ассоциированных массивов: любой идентификатор, используемый в качестве ее левого операнда, интерпретируется как строка.

Операция выбора

Единственная тернарная операция выбора

операнд! ? операнд2 : операндЗ

полностью заимствована из языка С и работает точно так же, как и ее двойник. Если операнд! истинен, то возвращается значение операнд2, в противном случае операнда:

($n = 1) ? $а : Sarray;

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

$а = $yes ? $b : @b; tt Скалярный контекст. Если возвращается

# массив @Ь, то присваивается количество его t элементов.

Операцию выбора можно использовать в качестве левого операнда операции присваивания, если и второй, и третий ее операнды являются правильными lvalue, т. е. такими значениями, которым можно присвоить какое-либо значение, например, именами переменных:

($а = $yes ? $b : @b) = @с;

В связи с тем, что результатом операции выбора может оказаться правильное lvalue, следует использовать скобки для уточнения ее операндов. Например, если в следующем выражении

($а % 3) ? ($а += 2) : ($а -= 2);

опустить скобки вокруг операндов

$а % 3 ? $а += 2 : $а -= 2;

то оно будет откомпилировано следующим образом

(($а % 3) ? ($а += 2) : $а). -= 2;


Глава 1

Списковые операции

Списковая операция — это операция над списком значений, причем список не обязательно заключать в круглые скобки.

Мы уже знакомы с одной из таких операций — операцией вывода на стандартное устройство print. Иногда мы говорили об этой операции как о функции — и это справедливо, так как все списковые операции Perl выглядят как вызовы функций (даже их описание находится в разделе "Функции" документации по Perl), и более того, они позволяют заключать в круглые скобки список их параметров, что еще сближает их с функциями. Таким образом, списковую операцию (или функцию) print можно определить в программе любым из следующих способов:

print $a, "string", $b; # Синтаксис списковой операции, print($a, "string", $b); # Синтаксис вызова функции.

(Встроенные функции более подробно рассматриваются в части 11.)

Замечание
Описание многих встроенных функций (списковых операций) можно найти в главах, в которых вводятся понятия языка, для работы с которыми и предназначены определенные встроенные функции. Например, функции print, printf и write описаны в главе 6.


Глава 1

Операции заключения в кавычки

Кавычки (одинарные, двойные и обратные) в Perl мы используем для задания строковых литералов, причем получающиеся результирующие строковые данные существенно зависят от используемого типа кавычек: символы строки в одинарных кавычках трактуются так, как они в ней заданы, тогда как некоторые символы ($, @) или даже последовательности символов (\ п , \t) в строке в двойных кавычках выполняют определенные действия. Всё дело в том, что в Perl кавычки — это всего лишь удобный синтаксический эквивалент определенных операций, выполняемых над символами строки. В языке, кроме трех перечисленных операций заключения в кавычки, определен еще ряд операций, выполняющих определенные действия со строковыми данными и внешне похожих на операции заключения в кавычки, на которые мы будем в дальнейшем ссылаться так же, как на операции заключения в кавычки.

Все операции заключения в кавычки представлены в табл. 4.3 с эквивалентным синтаксисом (если таковой существует) и кратким описанием действий, выполняемых при их выполнении.

Таблица 4.3. Операции заключения в кавычки

Общая форма | Эквивалентная форма | Значение | Возможность подстановки
q{ } | * i | Строковый литерал | Нет
qq{ } | it ii' | Строковый литерал | Да
qx{ } |  | Команда системы | Да
qw { } | 0 | Список слов | Нет
m{ } | // | Поиск по образцу | Да
qr{ } |  | Образец | Да
s { } { } |  | Подстановка | Да
tr{ }{ } | у/// | Транслитерация | Нет

При использовании общей формы операции заключения в кавычки вместо фигурных скобок {}, представленных в табл. 4.3, можно использовать любую пару символов, выбранную в качестве разделителя. Если выбранный символ не является какой-либо скобкой (круглой, угловой, квадратной или фигурной), то он ставится в начале и в конце строки, к которой должна быть применена соответствующая операция, тогда как в случае использования скобок-разделителей сначала используется открывающая скобка, а в конце закрывающая. Между знаком операции и строками в символах-разделителях может быть произвольное число пробельных символов. Обычно в качестве разделителя программистами Perl используется косая строка "/", хотя это и не обязательно.

В табл. 4.3 в последнем столбце также указывается, осуществляет ли соответствующая операция подстановку значений скалярных переменных и массивов, а также интерпретацию управляющих символов.

В этом параграфе мы остановимся только на первых четырех операциях заключения в кавычки. Остальные операции, как непосредственно связанные с регулярными выражениями, будут подробно рассмотрены в главе 10.

Операция q{}

Эта операция аналогична заданию строкового литерала в одинарных кавычках. В нем каждый символ строки представляет самого себя, подстановка значений переменных не выполняется. Единственное исключение — обратная косая черта, за которой следует символ-разделитель или еще одна обратная косая черта. Эти последовательности символов позволяют ввести непосредственно в строку символ разделителя или обратную косую черту (хотя обратная косая черта и так представляет саму себя). Несколько примеров:

q-сДескриптор \<FILE\»; # Строка символов: Дескриптор <FILE>

д!Каталог \\bin\usr\n!; # Строка символов; Каталог \bin\usr\n

'Каталог \\bin\usr\n'; # Эквивалентно предыдущей операции

Операция qq{}

Эта операция аналогична заданию строкового литерала в двойных кавычках. При ее выполнении осуществляется подстановка в строку значений скалярных переменных, начинающихся с символа $, и переменных массивов скаляров, начинающихся с символа @, а также осуществляется интерпретация управляющих последовательностей. После выполнения указанных действий будут сформированы строковые данные. Для задания в строке символа разделителя, используемого в этой операции, можно воспользоваться обратной косой чертой перед этим символом. Несколько примеров:

qq(print\(\) - функция вывода); # Строка символов:

# print() - функция вывода $т = 123; qq/4enoe\t$m\n/; # Строка символов:

# Целое 123 "Ifenoe\t$m\n"; # Эквивалентно предыдущей операции

Операция qx{}

Эта операция аналогична заданию строкового литерала в обратных кавычках. При ее вычислении сначала осуществляется подстановка значений скалярных переменных и переменных массивов скаляров (если таковые присутствуют) в строку, заданную между разделителями операции, а затем полученная строка, как некая команда, передается на выполнение командному интерпретатору операционной системы и результат ее выполнения подставляется в формируемое операцией qx{} окончательное строковое значение. Таким способом можно ввести в программу Perl результаты выполнения определенных команд или пользовательских программ. Несколько примеров:

$file = "file.tmp";

qxfdel $file); # Удаление файла с именем file.tmp

$rez = qx(progl -a); # Переменная

$rez содержит результаты вывода

# на экран программы progl

$rez = 'progl -a~; # Эквивалентно предыдущей операции

Операция qw{}

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

@m = qw( one two ); I Эквивалентно: $m[0] = 'one'; $m[l] = 'two'; Действие операции qw{СТРОКА} эквивалентно действию встроенной функции

split С ', qfCTPOKA});

(Описание функции split см. в части 10.)

Наиболее часто встречающаяся ошибка при использовании этой операции — отделить слова запятыми. При включенном режиме отображения предупреждений -w будет сгенерировано сообщение о том, что, возможно, запятая используется для разделения слов, а не входит в состав слова.

Операция "документ здесь"

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

Ее синтаксис прост: после знака операции « задается завершающий идентификатор, который служит признаком окончания задания строковых данных. Это означает, что все строки данных, расположенные между текущей строкой, содержащей операцию "документ здесь" и строкой, содержащей завершающий идентификатор, рассматриваются как единый фрагмент строковых данных:

$multi_line_string = «LINES; строка 1 строка 2 LINES

В приведенном фрагменте кода скалярная переменная $muiti_iine_string будет содержать строку "строка 1\пстрока 2\п". Как видим, введенные нами с клавиатуры символы перехода на новую строку сохраняются при использовании операции "документ здесь". По умолчанию операция "документ здесь" интерпретирует содержимое всех строк программы до завершающего идентификатора как строковые данные, заключенные в двойные кавычки, сохраняя в них символ перехода на новую строку "\n". Perl позволяет явно

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

print «m;

line 1

m

print << "m" ;

line 1

m

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

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

При задании завершающего идентификатора в кавычках на строковые данные распространяются все правила подстановок переменных и управляющих символов, применяемые к строкам, ограниченным соответствующим типом кавычек. Пример 4.16 демонстрирует использование различных кавычек в операции "документ здесь".

#! peri -w

$var = "Александр"; print «FIRST; # Отобразит: Пользователь: # Пользователь: \t$var # Александр FIRST #

print «'FIRST 1 ; # Отобразит:

Пользователь: I Пользователь:

\t$var # \t$var

FIRST #

$coml = "echo Alex";

print « 4 FIRST'; # Отобразит:

$coml i Alex FIRST # .

Обратите внимание, что в примере 4.16 использовался одинаковый завершающий идентификатор FIRST. Это не приводит к двусмысленностям и ошибкам компиляции, так как компилятор ищет первую после операции « строку с завершающим идентификатором. Главное, чтобы завершающий идентификатор в строке завершения был задан именно так, как он задан в самой операции.

Замечание
При использовании операции "документ здесь" с завершающим идентификатором в обратных кавычках в некоторых операционных системах может возникнуть проблема с обработкой потока команд, определяемого в нескольких строках. Не все командные интерпретаторы могут обрабатывать несколько строк команд. Обычно они ориентированы на ввод команды в строке ввода, выполнения ее и ожидания следующего ввода команды. Некоторые командные оболочки могут обрабатывать несколько команд, заданных в одной строке через разделитель, например, командный интерпретатор cmd системы Windows NT, в котором разделителем служит символ &.

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

$var = "Александр";

print « х2; i Отобразит 2 раза следующие 3 строки:

Пользователь: # Пользователь:

\t$var # Александр

х2 1x2

# Пустая строка завершает операцию «

В этом фрагменте кода ошибочно поставлен пробел перед завершающим идентификатором х2. Компилятор разобрал строку с операцией печати print следующим образом: строковые данные, вводимые операцией -«, завершаются пустой строкой, после чего они просто повторяются 2 раза (последовательность символов х2 понимается как операция повторения строки х с правым операндом равным 2).

Этот пример подобран специально таким образом, чтобы он нормально откомпилировапся. Если вместо идентификатора х2 поставить, например FIRST, то компилятор сгенерирует ошибку.

Результат выполнения операции "документ здесь" можно использовать в качестве операнда строковых операций. Можно даже использовать несколько операций « в одном операторе, расположив строки их данных последовательно Друг под другом, не забыв, конечно, строки с завершающим идентификатором:

$stack = «"ONE". «"TWO";

Первый

операнд

ONE

Второй

операнд

TWO

Значением скалярной переменной $stack будет следующая строка:

"Первый\поперанд\пВторой\поперанд"


Глава 1

Выражения

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

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

Таким образом, выражение можно мыслить как последовательность термов, соединенных знаками операций.

Термы

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

Из всех перечисленных объектов языка, которые рассматриваются как термы, требует некоторого пояснения последний — функция с параметрами в круглых скобках.

В действительности в Perl отсутствуют истинные функции, понимаемые в смысле, например, языка С, в котором регламентировано обращение в программе к функции указанием ее имени с параметрами, заданными в круглых скобках. По существу, "функции" языка Perl являются списковыми и унарными именованными операциями, ведущими себя как функции, так как синтаксис языка позволяет заключать их параметры в круглые скобки. Вызывая в программе функцию (с заключенными или не заключенными в скобки параметрами), мы выполняем операцию (списковую или унарную именованную). Причем следует иметь в виду, что коль скоро выполняется операция, то она не только выполняет предписанные ей действия, но и возвращает определенный результат, который используется при вычислении выражения. Например, функция print возвращает Истину, если ее вывод завершен успешно, и Ложь в противном случае. Что напечатается при вычислении следующей операции?

print print "О";

Ответ — строка 01, так как первая операция print должна напечатать результат вычисления второй операции print, которая успешно выводит на экран монитора о. Следовательно, ее результат Истина, а она представляется числом 1.

При синтаксическом разборе выражений и операторов (о них в следующем разделе) как термы рассматриваются конструкции do{} и evaiu, вызовы подпрограмм и методов объектов, анонимные конструкторы массивов скаляров [ ] и хешей {}, а также все операции ввода/вывода. Все эти понятия будут рассмотрены в последующих главах книги, но мы сочли необходимым, в целях полноты изложения термов, просто привести их полный список.

Приоритет операций

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

4+3*2

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

В табл. 4.4 представлены все операции Perl в порядке убывания их приоритета, в ней также рпределен порядок выполнения операций с одинаковым приоритетом (столбец Сочетаемость).

Таблица 4.4. Приоритет и сочетаемость операций Perl

Приоритет | Операция | Сочетаемость
1 | Вычисление термов и левосторонних списковых операций | Слева направо
2 | -> | Слева направо
3 | ++ -- | Не сочетаются
4 | * * | Справа налево
5 | ! ~ \ унарные + и - | Справа налево
6 | =~ ! = | Слева направо
7 | * / % х | Слева направо
8 | + - . | Слева направо
9 | « » | Слева направо
10 | Именованные унарные операции | Не сочетаются
11 | <><=>= It gt le ge | Не сочетаются
12 | == != <=> eq ne cmp | Не сочетаются
13 | & | Слева направо
14 | I л | Слева направо
15 | && | Слева направо
16 | I I | Слева направо
17 | . . ... | Не сочетаются
18 | ?; | Справа налево
19 | = **= += -= .= *= /= %= х= &= |= л = | Справа налево
 | «= »= &&=|| = |
20 | , => | Слева направо
21 | Правосторонние списковые операции | Не сочетаются
22 | not | Справа налево
23 | and | Слева направо
24 | or xor | Слева направо

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

use Math::Trig; # В пакете определена константа

# pi = 3.14159265358979

sin I * pi; # sin( 1 * pi) = 1.22460635382238e-016 sin (1) * pi; f (sin 1) * pi = 2.64355906408146

В последнем выражении sin (i) рассматривается как терм, так как после имени операции первой распознаваемое лексемой стоит открывающая круглая скобка, а если это терм, — то и вычислять его надо в первую очередь, как операцию с наивысшим приоритетом.

Можно чисто визуально в тексте программы списковую или унарную именованную операцию с параметрами в круглых скобках сделать не похожей на вызов функции, поставив префикс + перед списком ее параметров:

sin +(1) * pi; # (sin 1} * pi = 2.64355906408146

Этот префикс не выполняет никакой семантической роли в программе, даже не преобразует параметр в числовой тип данных. Он просто служит для акцентирования того факта, что sin не является функцией, а представляет собой унарную именованную операцию.

Если в списковой операции отсутствуют скобки вокруг параметров, то она может иметь либо наивысший, либо самый низкий (ниже только логические операции not, and, or и хог) приоритет. Это зависит от того, где расположена операция относительно других операций в выражении: слева или справа. Все операции в выражении, расположенные слева от списковой операции (сама операция расположена справа от них), имеют более высокий приоритет относительно такой списковой операции, и вычисляются, естественно, раньше нее. Именно это имелось в виду, когда в табл. 4.4 вносилась строка с правосторонними списковыми операциями. Следующий пример иллюстрирует правостороннее расположение списковой операции:

$т = $п II print "Нуль, пустая строка или не определена!";

По замыслу программиста это выражение должно напечатать сообщение, если только значение переменной $п равно нулю, пустой строке или не определено. На первый взгляд, кажется, так и должно быть: выполнится операция присваивания и возвратит присвоенное значение. Если оно не равняется нулю, пустой строке или значение переменной $п не определено, то в булевом контексте операции логического ИЛИ (| |) оно трактуется как Истина, а поэтому второй операнд этой логической операции (операция печати) не вычисляется, так как мы помним, что логическое ИЛИ вычисляется по укороченной схеме. Однако реально переменной $т всегда будет присваиваться 1, правда сообщение будет печататься именно тогда, когда переменная $п равна нулю, пустой строке или не определена.

В чем дело? Программист забыл о приоритете правосторонних списковых операций! В этом выражении списковая операция print расположена справа от всех остальных операций, поэтому она имеет самый низкий приоритет. Выражение будет вычисляться по следующему алгоритму. Сначала будет вычислен левый операнд операции i i. Если он имеет значение Истина (переменная $п имеет значение, не равное нулю или пустой строке), то второй операнд этой операции (print) вычисляться не будет, а переменной $т будет присвоена Истина, т. е. 1. Если первый операнд вычисляется как Ложь (переменная $п равна нулю, пустой строке или не определена), то вычисляется второй операнд, выводящий сообщение на экран монитора. Но так как возвращаемым значением операции печати является Истина, то именно она и присваивается переменной $т.

Правильное решение — использовать низкоприоритетную операцию or логического ИЛИ:

$m = $n or print "Нуль, пустая строка или не определена! " ; или скобками изменить порядок выполнения операций:

($m = $n) I I print "Нуль, пустая строка или не определена!";

Теперь обратимся к случаю, когда списковая операция стоит слева от других операций в выражении (левосторонняя списковая операция). В этом случае, в соответствии с табл. 4.4, она имеет наивысший приоритет и все, что стоит справа от нее, она рассматривает как список своих параметров. Рассмотрим небольшой пример. Предположим, что необходимо удалить из массива @а все элементы, начиная со второго, и вставить их в создаваемый массив @т после второго элемента. Списковая операция splice со списком параметров @а, 1 удаляет из массива @а все элементы, начиная с элемента с индексом 1, т. е. со второго элемента до конца массива, и возвращает список удаленных элементов. Ее можно использовать в конструкторе нового массива для решения поставленной задачи:

@а = ("al", "a2", "аЗ", "а4");

9m = ("m0", "ml", splice @a, 1, "m2", "mЗ"); ,

В конструкторе массива мы специально задали параметры операции splice без скобок. Если выполнить этот фрагмент и распечатать значения элементов массивов, то результат будет следующим:

@m: m0 ml

@а: al mЗ а2 аЗ а4

Совершенно не то, что нам надо: в массив @т не вставлен фрагмент массива @а, да и из него самого не удалены элементы, начиная со второго. Все дело в том, что операция splice в этом выражении левосторонняя, и весь расположенный справа от нее список рассматривает как список своих параметров: @а, i, "m2", "тЗ". Ее третьим параметром должно быть число, определяющее количество удаляемых из массива элементов, начиная с элемента, индекс которого определен вторым параметром. В нашем случае третий параметр не является числовым, и функция завершается с ошибкой, возвращая Ложь. Исправить положение помогут опять скобки:

@m = ("m0", "ml", (splice @a, 1), "т2", "тЗ");

ИЛИ """' @т = ("m0", "ml", splice (@a, 1), "т2", "тЗ");

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

$т += $п += 1;

Как следует его понимать? Как ($т += $п) +=1 или как $т += ($п += 1)? Ответ дает правило сочетаемости. Смотрим в табл. 4.4 и видим, что все операции присваивания сочетаются справа налево. Это означает, что сначала должно выполниться присваивание $п += 1, а потом результат увеличенной на единицу переменной $п прибавляется к переменной $т. Следовательно, это выражение эквивалентно следующему:

$т += ($п += 1) ;

Аналогично применяется правило сочетаемости и к другим операциям языка Perl:

$a>$b<$c; # Эквивалентно: ($а>$Ь)<$с; Сочетаемость: слева направо. $а**$Ь**$с; # Эквивалентно: $а**($Ь**$с); Сочетаемость: справа налево.

Скобки изменяют порядок вычислений, определяемый по правилу приоритетов и сочетаемости. Любое, заключенное в скобки подвыражение, будет вычисляться с наивысшим приоритетом, так как Perl рассматривает его как терм, имеющий наивысший приоритет.

Контекст

Наш разговор о выражениях Perl был бы не полным, если бы обошли стороной такое понятие, как контекст. Каждая операция и каждый терм вычисляются в определенном контексте, который определяет поведение операции и интерпретацию возвращаемого ею значения. Существует два основных контекста: скалярный и списковый. В главе 3 мы уже немного познакомились с ними, когда определяли поведение конструктора массива и переменной массива в правой части оператора присваивания. Их можно "определить" так: если для выполнения операции требуются скалярные данные, то действует скалярный контекст, если необходимы массивы скаляров, то программа находится в списковом контексте. Например, если левый операнд операции присваивания, скалярная переменная, то действует скалярный контекст, если же в левой части задан массив, хеш или фрагмент массива или хеша, то вычисления в правой части осуществляются в списковом контексте. Присваивание списку скалярных переменных также инициирует списковый контекст для вычислений, осуществляемых в правой части операции.

Некоторые операции Perl распознают контекст, в котором они вычисляются, и возвращают список в списковом контексте и скалярное значение в скалярном контексте. Обладает ли операция подобным поведением, можно всегда выяснить из ее описания в документации. Например, можно рассматривать префикс @ перед идентификатором как унарную операцию объявления массива скаляров, распознающую контекст, в котором она вычисляется. В списковом контексте она возвращает список элементов массива, а в скалярном — число элементов массива.

Некоторые операции поддерживают списковый контекст для своих операндов (в основном это списковые операции), и это также можно узнать из описания их синтаксиса, в котором присутствует СПИСОК (LIST). Например, известная уже нам операция создания фрагмента массива splice поддерживает списковый контекст для своих параметров, поэтому ее можно вызывать вот таким образом:

@s = (1, 2);

splice @m, @s; # Эквивалентно: splice @m, I, 2;

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

В языке существует булевый контекст — специальный тип скалярного контекста, в котором вычисленное значение выражения трактуется только как Истина или Ложь. Как уже отмечалось ранее, в Perl нет специального булева типа данных. Здесь любая скалярная величина трактуется как Истина, если только она не равна пустой строке "" или числу о. (Строковый эквивалент ложности — строка из одного нуля "о"; любая другая строка, эквивалентная нулевому значению, считается в булевом контексте истинной, например, "оо" или "о.о".)

Другим специфическим типом скалярного контекста является void-контекст. Он не только не заботится о подтипе возвращаемого значения — скалярный или числовой, но ему и не надо никакого возвращаемого значения. Этот контекст возникает при вычислении выражения без побочного эффекта, т. е. когда не изменяется никакая переменная программы. Например, следующие выражения вычисляются в void-контексте:

$n; "текст";

Этот контекст можно "обнаружить", если установить ключ -w компилятора Perl. Тогда можно получить предупреждающее сообщение следующего типа:

Useless use of a variable in void context at D:\P\EX.PL line 3.

(Бесполезное использование переменной в, void-контексте в строке 3 программы D:\P\EX.PL) х

Завершит наш рассказ о контексте подстановочный контекст (interpolative context), в котором вычисляются операции заключения в кавычки (кроме заключения в одинарные кавычки). В этом контексте вместо любой переменной, заданной в строке, в нее подставляется значение этой переменной, а также интерпретируются управляющие последовательности.

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


Глава 1

Вопросы и Упражнения

Вопросы для самоконтроля

  1. Какую роль выполняют операции в программе?
  2. Какие основные группы операций существуют в Perl?
  3. Объясните "укороченную схему" вычисления логических операций. Где она используется?
  4. Что такое выражение?
  5. Определите понятие терм. Что считается термом в языке Perl?
  6. Что такое приоритет операций и как он применяется при вычислении выражений?
  7. Когда необходимо применять свойство сочетаемости операции?
  8. Объясните понятие "контекст". Какие два основных типа контекста используются в языке Perl?

Упражнения

  1. Что будет отображено на экране монитора при вычислении выражения print print 1;
  2. Определите результат вычисления следующих выражений:
    print "О" I I print "1"; print "О" or print "1";
  3. Что будет отображено на экране монитора и каковы будут значения элементов массива @т в результате выполнения следующей операции присваивания: @m = (print "p\n", 2, print 3, 4);
  4.   Определите результат выполнения
    следующих операторов:
$varO = 2; $varl = 1;
$rezl = $varO ** 3 * 2 !I 4 + $varl, $varl++;
$rez2 = ($varl++, $varO ** 3 * 2 || 4 + $varl, "6");
@rez3 = ($varl++, $varO ** 3 *
    2 || 4 + $varl, "6");
  5.   Что напечатает следующий фрагмент
    программы при вводе числа или строки и почему:
$input = <STDIN>;
$hello = "Hello ";
$hello += $input;
print $hello;
  6. Найдите ошибку в программе: $first_number =34; $second_number = 150; if( $first_number It $second_number ) { print $first_number; }


Глава 1

Часть 5. Операторы

Простые операторы
Модификаторы простых операторов
Составные операторы
Операторы цикла
Команды управления циклом
Именованные блоки
Оператор безусловного перехода
Вопросы и Упражнения


Глава 1

Простые операторы

Простой оператор представ

Perl является императивным языком программирования: его программа состоит из последовательности операторов, определяющих некоторые действия. Оператор — это завершенная инструкция интерпретатору на выполнение определенного действия. Все операторы языка Perl делятся на простые и составные. Простой оператор представляет собой выражение, возможно, снабженное модификатором. Составной оператор определяется в терминах блоков.

Каждый оператор Perl имеет возвращаемое значение. Для простого оператора — это значение вычисляемого в нем выражения, для составного оператора — значение последнего вычисленного в нем оператора.

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

ляет собой любое выражение, завершенное точкой с запятой ";". Символ точка с запятой обязателен. Он может отсутствовать, только если простой оператор является последним оператором в блоке, специальной синтаксической конструкции, о которой мы поговорим чуть-чуть позже.

Основное назначение простого оператора — вычисление выражения с побочным эффектом, который связан с изменением значения некоторых переменных в выражении при его вычислении. Обычно он реализуется операциями увеличения/уменьшения на единицу (++, --):

$п++; # Переменная $п увеличивается на единицу. —$п**2; # Переменная $п уменьшается на единицу.

Никакие другие операции Perl не вызывают побочных эффектов в выражении, если не считать операции присваивания (простое и составное), результатом вычисления которой является изменение значения левого операнда. Вызовы функций также могут приводить к побочным эффектам, но об этом подробнее мы расскажем в главе 11.

Простой оператор Perl может содержать выражение и без побочного эффекта. Все равно он будет рассматриваться интерпретатором как допустимый оператор, не выполняющий никакого действия, а только вычисляющий значение выражения. Однако если установлен флаг (-w) отображения интерпретатором предупреждающих сообщений, то будет получено сообщение о бесполезности использования соответствующей операции в void-контексте. Например, при выполнении оператора

($n*$m)**4 + 6;

будет отображено сообщение

Useless use of addition in void context at D:\PERL\EXAMPLE3.PL line 4.

Отметим, что в сообщении упоминается о последней выполненной операции в выражении.

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

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

sub raySub {

какие-то операторы condition == true ? "Успех" : "Провал"; # Последний оператор * '

Последний оператор функции mysub вычисляет операцию выбора, в которой второй и третий операнды представлены просто строковыми литералами — выражениями без побочного эффекта. Результат вычисления одного из них и является возвращаемым значением функции.


Глава 1

Модификаторы простых операторов

Каждый простой оператор может быть снабжен модификатором, представляющим ключевое СЛОВО if, unless, while, until ИЛИ foreach, за которым следует выражение-условие. В самом операторе модификатор стоит непосредственно за выражением, составляющим простой оператор, перед завершающим символом точка с запятой. Каждый простой оператор может иметь только один модификатор. Семантически роль модификатора сводится к тому, что оператор вычисляется при выполнении условия, определяемого модификатором. Например, следующий оператор присваивания

$n = $l/$m if $т != 0;

с модификатором if будет выполнен при условии, что переменная $т не равна о. Общий синтаксис простого оператора с модификатором имеет следующий вид:

ВЫРАЖЕНИЕ ключ_слово_модификатора [(]ВЫРАЖЕНИЕ-УСЛОВИЕ [)];

Модификаторы if и unless

Модификаторы if и unless употребляются в прямом смысле их английского значения. Простой оператор с модификатором If выполняется, если ВЫРАЖЕНИЕ-УСЛОВИЕ истинно. Семантически простой оператор

ВЫРАЖЕНИЕ if ВЫРАЖЕНИЕ-УСЛОВИЕ;

эквивалентен следующему оператору условия:

if(ВЫРАЖЕНИЕ-УСЛОВИЕ) { ВЫРАЖЕНИЕ; }

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

Модификатор unless является прямой противоположностью модификатора if: простой оператор выполняется, если ВЫРАЖЕНИЕ-УСЛОВИЕ не истинно. Общий синтаксис простого оператора с модификатором unless имеет следующий вид:

ВЫРАЖЕНИЕ unless ВЫРАЖЕНИЕ-УСЛОВИЕ;

Это всего лишь удобная форма записи оператора условия

if( ! ВЫРАЖЕНИЕ-УСЛОВИЕ) { ВЫРАЖЕНИЕ; }

Замечание
 ВЫРАЖЕНИЕ-УСЛОВИЕ вычисляется в булевом контексте: оно трактуется как Ложь, если равно о или пустой строке " ", и Истина — в любом другом случае.

Использование модификаторов if и unless показано в примере 5.1 — простой программе решения квадратного уравнения.

# peri -w

$а = <STDIN>;

$b = <STDIN>;

$с = <STDIN>;

$d = $b**2 - 4*$a*$c; # Вычисление дискриминанта уравнения

# Вычисление корней, если дискриминант положителен

( $xl =.(-$b+sqrt $d)/$a/2, $x2 = (-$b-sqrt $d)/$a/2 ) unless $d < 0;

# Печать результатов

print "Коэффициенты:\п а = $а b = $b с = $с"; print "\tPenieHHe:\n\t$xl\t$x2" if defined $xl; print "^Решения нет!" unless defined $xl;

Наша программа решения квадратного уравнения, конечно, примитивна. Из всех возможных проверок в ней проверяется на положительность только дискриминант квадратного уравнения, хотя стоило бы проверить на нуль значение вводимого пользователем с клавиатуры старшего коэффициента уравнения, сохраняемого в переменной $а.

Модификатор unless используется в операторах вычисления корней и печати сообщения об отсутствии решения. Обратите внимание, что в операторе печати проверяется, определена ли переменная $xi, а будет она определена только в случае положительности дискриминанта $d. В модификаторе if оператора печати корней уравнения также проверяется, определена ли переменная $xl.

Модификаторы while и until

Эти два модификатора немного сложнее модификаторов if и unless. Они реализуют процесс циклического вычисления простого оператора. Их синтаксис таков:

ВЫРАЖЕНИЕ while ВЫРАЖЕНИЕ-УСЛОВИЕ; ВЫРАЖЕНИЕ until ВЫРАЖЕНИЕ-УСЛОВИЕ;

Модификатор while повторно вычисляет ВЫРАЖЕНИЕ, пока истинно ВЫРАЖЕНИЕ-УСЛОВИЕ. Модификатор until противоположен модификатору while: ВЫРАЖЕНИЕ повторно вычисляется до момента, когда ВЫРАЖЕНИЕ-УСЛОВИЕ станет истинным, иными словами оно вычисляется, пока ВЫРАЖЕНИЕ-УСЛОВИЕ ложно.

Семантически эти модификаторы простых операторов эквивалентны следующим составным операторам цикла:

while(ВЫРАЖЕНИЕ-УСЛОВИЕ) { ВЫРАЖЕНИЕ; } until(ВЫРАЖЕНИЕ-УСЛОВИЕ) { ВЫРАЖЕНИЕ; }

Пример 5.2 дает представление о том, как работают модификаторы повтора while И until.

# peri -w $first = 10;

• $second = 10; $first++ while $first < 15; # $first увеличивается, пока не станет

# равной 15

—$second until $second < 5; # $second уменьшается, пока не станет

# равной 4

print "\$first $first\n"; print "\$second $second\n";

Оператор увеличения на единицу переменной $first будет выполняться, пока выражение $ first < 15 остается истинным. Когда значение переменной $first станет равным 15, выражение модификатора while становится ложным, и оператор завершает работу. Аналогично работает и следующий оператор уменьшения переменной $second на единицу. Единственное отличие от предыдущего оператора заключается в том, что он выполняется, пока выражение $ second < 5 модификатора until остается ложным. Два оператора печати выведут на экран значения переменных $first и $second равными соответственно 15 и 4.

Замечание
 Модификаторы повтора следует применять к простым операторам, вычисление которых приводит к изменению условий модификаторов. Если это не так, то простой оператор либо вообще не будет выполнен, либо будет выполняться бесконечно. Например, следующий оператор
print $var while $var < 15;
либо ни разу не напечатает значение переменной $var (если $var > =15), либо будет печатать бесконечно (если $var < 15). В последнем случае произойдет так называемое зацикливание и только нажатием комбинации клавиш <Ctrl>+<C> можно будет остановить выполнение этого оператора.

Модификаторы while и until сначала проверяют истинность или ложность своих выражений-условий, а потом, в зависимости от полученного результа, либо выполняют простой оператор, либо нет. Таким образом, они реализуют цикл с предусловием, при котором оператор, для которого они являются модификаторами, может не выполниться ни одного раза.

Существует единственное исключение из этого правила, когда модификаторы повтора применяются к синтаксической конструкции do <}, которая не является оператором (хотя внешне и похожа), а относится к термам (см. главу 4). Поэтому, если ее завершить точкой с запятой, то такая конструкция будет являться простым оператором, к которому можно применять все возможные модификаторы Perl. Семантика этой конструкции заключается в том, что она вычисляет операторы, заданные в фигурных скобках {}, и возвращает значение последнего выполненного оператора. Так вот, если к простому оператору do {}; применить модификаторы повтора, то сначала выполнятся операторы конструкции do {}, а потом будет проверено условие модификатора. Это позволяет написать следующий простой оператор, который сначала осуществит ввод с клавиатуры, а потом проверит введенную информацию на совпадение с символом завершения ввода:

$string = ""; do{

$line = <STDIN>;

$string .= $line; I until $line eq ".\n";

Этот фрагмент кода будет накапливать вводимые пользователем строки в переменной $string до тех пор, пока не будет введена строка, состоящая из единственного символа точки, после чего оператор do{} until; завершит свою работу. Обратите внимание, что проверка в модификаторе until ведется на совпадение со строкой ". \п", в которой присутствует символ перехода на новую строку. Дело в том, что операция ввода <STDIN> передает этот символ в программу, так как пользователь именно этим символом завершает ввод строки (нажатие клавиши <Enter>).

Модификатор foreach

Модификатор foreach ВЫРАЖЕНИЕ относится к модификаторам цикла. Он повторно выполняет простой оператор, осуществляя итерации по списку значений, заданному в ВЫРАЖЕНИЕ. На каждой итерации выбранный элемент списка присваивается встроенной переменной $_, которую можно использовать в простом операторе для получения значения выбранного элемента списка. Например, следующий оператор распечатает все элементы массива @т:

print "$_ " foreach @т;

Общий синтаксис простого оператора с модификатором foreach следующий:

ВЫРАЖЕНИЕ foreach ВЫРАЖЕНИЕ-СПИСОК;

Простой оператор с модификатором foreach всего лишь удобная форма записи составного оператора jforeach:

foreach (ВЫРАЖЕНИЕ-СПИСОК) { ВЫРАЖЕНИЕ; }

Эта форма составного оператора foreach в качестве переменной цикла использует встроенную переменную $_ (см. раздел 5.4.3). Обратим внимание читателя на то, что ВЫРАЖЕНИЕ-список вычисляется в списковом контексте, поэтому все используемые в нем переменные ведут себя так, как они должны вести в списковом контексте. Например, хеш-массив представляет обычный список, составленный из последовательности его пар ключ/значение. Следующий фрагмент кода

%hash = ( one=>6, two=>8, three=>10 ); print "$_ " foreach %hash;

напечатает строку

three 10 two 8 one 6

Эта строка и есть тот простой список, который возвращает хеш в списковом контексте.

Относительно модификатора foreach (это же относится и к его эквивалентному оператору foreach) следует сказать одну важную вещь. Дело в том, что переменная $_ является не просто переменной, в которой хранится значение элемента списка текущей итерации, она является синонимом имени этого элемента. Это означает, что любое изменение переменной $_ в простом операторе приводит к изменению текущего элемента списка в цикле. Пример 5.3 демонстрирует, как просто можно умножить каждый элемент массива на некоторое число:

# peri -w

§array = (1, 2, 3);

$_ *= 2 foreach Sarray; # Умножение каждого элемента на 2.

print "@array"; # Напечатает строку: 246


Глава 1

Составные операторы

Составные операторы — это второй тип операторов языка Perl. С их помощью реализуют ветвления в программе и организуют циклические вычисления. Эти операторы, в отличие от аналогичных операторов других языков программирования, определяются в терминах блоков — специальном понятии языка Perl, задающим область видимости переменных. Именно с блоков мы и начнем изучение составных операторов.

Блоки

Блок — последовательность операторов, определяющая область видимости переменных. В программе блок обычно ограничен фигурными скобками {...}. Определяя синтаксис составных операторов, мы будем иметь в виду именно такой блок — последовательность операторов в фигурных скобках и обозначать его БЛОК. Интерпретатор рассматривает БЛОК как один оператор, вычисляемым значением которого является значение последнего выполненного оператора блока. Это означает, что там, где можно использовать один оператор, можно использовать и БЛОК. Такая ситуация встречается при использовании функции тар(). Она выполняет определяемый ее первым параметром оператор для всех элементов списка, заданного вторым параметром. Значение каждого элемента списка при вычислениях временно присваивается встроенной переменной $_. Возвращает эта функция список вычисленных значений оператора:

@rez = map $_ **= 2, @array; # Список квадратов элементов массива.

В качестве первого параметра этой функции можно использовать БЛОК. Следующий оператор также вычисляет список квадратов элементов массива @аггау, одновременно подсчитывая количество его элементов:

@rez = map { ++$kol; $_ **= 2} @array; # Список квадратов элементов

# массива и подсчет их количества.

Обратите внимание, что возвращаемым значением блока операторов в этом примере является значение последнего оператора блока, которое и попадает в возвращаемый функцией тар () список.

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

Как сказано в начале этого раздела, блок определяет область видимости переменных. Это означает, что в блоке можно создать переменные, обращаться к которым можно только из операторов, расположенных в этом блоке. Пока мы в блоке, мы можем присваивать им новые значения, использовать в вычислениях и т. п., но как только мы вышли из блока, мы теряем с ними "связь", они становятся "не видимыми". Такие переменные еще называют локальными переменными.

Локальные переменные создаются с помощью функции ту (). Ее параметром является список переменных, область видимости которых ограничена блоком, в котором вызывается эта функция. Если список переменных состоит из одной переменной, то скобки не обязательны. Созданные функцией my о переменные называются также лексическими переменными, так как область их действия ограничена фрагментом текста программы — блоком операторов.

В языке Perl можно создавать другой тип локальных переменных, область действия которых определяется динамически во время выполнения программы. Они создаются функцией local о и называются локальными динамическими переменных. Однако именно переменные, созданные функцией ту (), являются "истинными" локальными переменными: они создаются при входе в блок и уничтожаются при выходе из него (хотя существуют ситуации, когда Perl не уничтожает локальную лексическую переменную при выходе из ее области действия). Функция local о всего лишь временно сохраняет старое значение глобальной переменной при входе в блок и восстанавливает его при выходе из него.

(Более подробно лексические и динамические переменные рассматриваются в части 11.)

Локальные переменные удобны для создания временных переменных, которые нигде больше не будут использоваться в программе, а только в одном определенном месте. Например, при отладке части кода часто приходится создавать временные переменные и выводить на печать их значения. Локальные переменные могут иметь такие же имена, как и глобально используемые переменные. Это не приводит к конфликту. После завершения операторов блока значение глобальной переменной имеет то же значение, которое она имела до начала выполнения операторов блока (пример 5.4).

# peri -w

$var = "outer"; # Глобальная переменная $var $glob = "glob"; # Глобальная переменная $glob my $lex = "outer_l"; # Лексическая переменная $1ех {

my($var) = "inner"; # Внутренняя переменная $var my($varl) = "inner_l"; # Внутренняя переменная $varl print "В блоке \$var = $var\n"; # Напечатает inner print "В блоке \$varl = $varl\n"; # Напечатает inner_l print "В блоке \$lex = $lex\n"; # Напечатает outer_l print "В блоке \$glob = $glob\n"; # Напечатает glob }

print "Вне блока \$var = $var\n"; # Напечатает outer print "Вне блока \$1ех = $lex\n"; # Напечатает outer_l print "Вне блока \$varl = $varl\n"; # Напечатает пустую строку ""

Программа примера 5.4 демонстрирует области видимости лексических переменных. Внутри блока {...} "видны" переменные, созданные вне блока: и глобальные, и лексические ($giob, $iex), если только они не переопределены внутри блока ($var). При выходе из внутреннего блока восстанавливаются значения переменных, которые были переопределены внутри блока ($var). Доступ к локальным переменным блока извне невозможен ($vari).

Операторы ветвления

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

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

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

В языке существует три формы оператора ветвления if:

if (ВЫРАЖЕНИЕ) БЛОК

if (ВЫРАЖЕНИЕ) БЛОК1 else БЛОК2

if (ВЫРАЖЕНИЕ!) БЛОК1 elsif (ВЫРАЖЕНИЕ2) БЛОК2 ... else БЛОКп

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

Первый оператор if реализует простейшее ветвление. Если ВЫРАЖЕНИЕ истинно, то выполняются операторы из БЛОК, в противном случае БЛОК просто пропускается:

$var = 10;

if ( $var -= 5 ) {

print "Переменная \$var - $var"; }

Обратите внимание, что в этом примере ВЫРАЖЕНИЕ представляет операцию составного присваивания. Это может показаться необычным, так как в большинстве языков программирования здесь требуется выражение, возвращающее булево значение. В Perl можно использовать любое выражение, в том числе и присваивание. Результат его вычисления интерпретируется в булевом контексте: если вычисленное значение равно о или пустой строке "", то оно трактуется как Ложь, иначе — Истина. Возвращаемым значением операции присваивания является значение, присвоенное переменной левого операнда. В нашем примере это число 5, следовательно в булевом контексте оно трактуется как Истина, а поэтому оператор печати print будет выполнен. Если перед выполнением оператора if переменная $var будет равняться 5, то выражение условия будет вычислено равным Ложь и все операторы блока будут просто пропущены.

Обычно выражение условия представляет собой сложное выражение, составленное из операций отношения, связанных логическими операциями. Использование операции присваивания в выражении условия оператора if не совсем типично. Здесь мы его использовали, чтобы подчеркнуть то обстоятельство, что в Perl любое правильное выражение может быть использовано в качестве выражения условия, которое вычисляется в булевом контексте. Вторая форма оператора if используется, когда необходимо выполнить одну группу операторов (БЛОК!) в случае истинности некоторого выражения (ВЫРАЖЕНИЕ), а в случае его ложности — другую группу операторов (влок2):

if ($var >= 0} # ВЫРАЖЕНИЕ {

print "Переменная неотрицательна."; # БЛОК1, если ВЫРАЖЕНИЕ истинно } else {

print "Переменная отрицательна."; # БЛОК2, если ВЫРАЖЕНИЕ ложно }

По существу, первая форма оператора if эквивалентна второй форме, если БЛОК2 не содержит ни одного оператора.

Последняя, третья форма оператора if реализует цепочку ветвлений. Семантика этого оператора такова. Выполняются операторы из БЛОК!, если

ИСТИННО ВЫРАЖЕНИЕ!. ЕСЛИ ОНО ЛОЖНО, ТО ВЫПОЛНЯЮТСЯ Операторы ИЗ БЛОК2

в случае истинности выражение2. Если и оно ложно, то проверяется ВЫРАЖЕНИЕ З и т. д. Если ни одно из выражений условия оператора if не истинно, то выполняются операторы блока, определяемого после ключевого слова else в случае его наличия. В противном случае выполняется следующий после оператора if оператор программы. При выполнении следующего оператора ветвления if

if( $var < 0) { # ВЫРАЖЕНИЕ!

print "Переменная отрицательна"; i БЛОК! } elsif ( $var == 0) { # ВЫРАЖЕНИЕ2

print "Переменная равна нулю"; # БЛОК2 } else {

print "Переменная положительна"; # БЛОКЗ }

сначала проверяется условие отрицательности переменной $var. Если значение переменной строго меньше нуля (ВЫРАЖЕНИЕ1), то печатается сообщение из БЛОК! и оператор завершает свою работу. Если значение переменной не меньше нуля, то оно проверяется на равенство (ВЫРАЖЕНИЕ2) и в случае истинности выполняется оператор печати из блока операторов elsif (влок2). Если проверка на равенство нулю дала ложный результат, то выполняется оператор печати из блока операторов else (БЛОКЗ).

Замечание
 Ключевое слово else вместе со своим блоком операторов может быть опущено.

В операторе if со множественными проверками может быть сколько угодно блоков elsif, но только один блок else.
Так как все операторы ветвления определяются в терминах блоков операторов, то не возникает двусмысленности при определении, какие операторы в какой части выполняются.

При работе с операторами ветвления важно помнить, что только один блок операторов будет выполнен — тот, для которого истинно соответствующее выражение условия.

Во всех операторах ветвления ключевое слово if может быть заменено на unless. В этом случае проверка выражения условия осуществляется на его ложность. Последний оператор if можно записать и так:

unless( $var >= 0} { # ВЫРАЖЕНИЕ!

print "Переменная отрицательна"; # БЛОК! } elsif ( $var == 0) { # ВЫРАЖЕНИЕ2

print "Переменная равна нулю"; # БЛОК2 } else {

print "Переменная положительна"; # БЛОКЗ }

При этом нам пришлось заменить ВЫРАЖЕНИЕ! на противоположное по смыслу.

Замечание
Все операторы if (unless) могут быть вложенными, т. е. в любом их блоке можно свободно использовать другие операторы ветвления.


Глава 1

Операторы цикла

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

Мы уже познакомились с операторами ветвления Perl, а теперь пришло время узнать, какие конструкции цикла можно применять в Perl. Их всего три: while, for и foreach. Все они относятся к классу составных операторов и, естественно, определяются в терминах блоков БЛОК.

Циклы while и until

Цикл while предназначен для повторного вычисления блока операторов, пока остается истинным задаваемое в нем выражение-условие. Его общий синтаксис имеет две формы:

МЕТКА while (ВЫРАЖЕНИЕ) БЛОК

МЕТКА while (ВЫРАЖЕНИЕ) БЛОК continue БЛОК1

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

Оператор while выполняется по следующей схеме. Вычисляется выражения-условия ВЫРАЖЕНИЕ. Если оно истинно, то выполняются операторы БЛОК. В противном случае оператор цикла завершает свою работу и передает управление следующему после него оператору программы (цикл 1 примера 5.5). Таким образом, оператор цикла while является управляющей конструкцией цикла с предусловием: сначала проверяется условие завершения цикла, а потом только тело цикла, определяемое операторами БЛОК. Поэтому может оказаться, что тело цикла не будет выполнено ни одного раза, если при первом вхождении в цикл условие окажется ложным (цикл 3 примера 5.5).

Вместо ключевого слова while можно использовать ключевое слово until. В этом случае управляющая конструкция называется циклом until, который отличается от разобранного цикла while тем, что его тело выполняется, только если выражение условия ложно (цикл 2 примера 5.5).

# peri -w

# цикл 1

$i = 1;

while <$i•<= 3) {

$a[$i] = l/$i; # Присвоить значение элементу массива

++$i; >

print "Переменная цикла \$i = $i\n"; # $i = 4 print "Массив \@a: @a\n"; # @a = (I, 0.5. 0.333333333333333)

# цикл 2, эквивалентный предыдущему

$i = 1;

until ($i > 3) {

$a[$i] = l/$i; # Присвоить значение элементу массива

++$i; }

print "Переменная цикла \$i = $i\n"; # $i = 4 print "Массив \@a: @a\n"; # @a = (1, 0.5. 0.333333333333333)

# цикл З, тело цикла не выполняется ни одного раза

$i = 5;

while ($i-<= 3) {

$a[$i] = l/$i;

++$i; } print "Переменная цикла \$i = $i\n"; # $i = 5

# цикл 4, бесконечный цикл (не изменяется выражение условия)

$i = 1;

while ($i <= 3) {

$a[$i] = l/$i; } .

Обратим внимание на то, что в теле цикла должны присутствовать операторы, вычисление которых приводит к изменению выражения условия. Обычно это операторы, изменяющие значения переменных, используемых в выражении условия. Если этого не происходит, то цикл while или until будет выполняться бесконечно (цикл 4 примера 5.5).

Замечание
Цикл с постусловием реализуется применением модификатора while к конструкции do{), и рассматривался нами в разделе 5.2.2 " Модификаторы while и until".

Блок операторов БЛОК!, задаваемый после ключевого слова continue, выполняется всякий раз, когда осуществляется переход на выполнение новой итерации. Это происходит после выполнения последнего оператора тела цикла или при явном переходе на следующую итерацию цикла командой next. Блок continue на практике используется редко, но с его помощью можно строго определить цикл for через оператор цикла while.

Пример 5.6 демонстрирует использование цикла while для вычисления степеней двойки не выше шестнадцатой. В этом примере оператор цикла while функционально эквивалентен циклу for. Блок continue выполняется всякий раз по завершении очередной итерации цикла, увеличивая переменную $i на единицу. Он эквивалентен выражению увеличения/уменьшения оператора for.

# peri -w

# Вычисление степеней числа 2 $1 = I;

while ($i <= 16) {

print "2 в степени $i: ", 2**$i, "\n"; } continue {

++$i; f Увеличение переменной $i перед выполнением следующей итерации }

Цикл for

При выполнении циклов while и until заранее не известно, сколько итераций необходимо выполнить. Их количество зависит от многих факторов: значений переменных в выражении условия до начала выполнения цикла, их изменении в теле цикла, виде самого выражения условия и т. п. Но иногда в программе необходимо выполнить заранее известное количество повторений определенной группы операторов. Например, прочитать из файла 5 строк и видоизменить их по определенным правилам. Конечно, можно такую задачу запрограммировать операторами цикла while и until, но это может выглядеть не совсем выразительно. В том смысле, что при прочтении программы придется немного "пошевелить" мозгами, прежде чем понять смысл оператора цикла. Для решения подобных задач с заранее известным числом повторений язык Perl предлагает специальную конструкцию цикла — цикл for:

МЕТКА for (ВЫРАЖЕНИЕ1; ВЫРАЖЕНИЕ2; ВЫРАЖЕНИЕЗ) БЛОК

ВЫРАЖЕНИЕ1 используется для установки начальных значений переменных, управляющих циклом, поэтому его называют инициализирующим выражением. Обычно это одна или несколько операций присваивания, разделенных запятыми.

ВЫРАЖЕНИЕ2 определяет условие, при котором будут повторяться итерации цикла. Оно, как и выражение-условие цикла while, должно быть истинным, чтобы началась следующая итерация. Как только это выражение становится ложным, цикл for прекращает выполняться и передает управление следующему за ним в программе оператору.

ВЫРАЖЕНИЕЗ отвечает за увеличение/уменьшение значений переменных цикла после завершения очередной итерации. Обычно оно представляет собой список выражений с побочным эффектом или список операций присваивания переменным цикла новых значений. Его иногда называют изменяющим выражением.

Алгоритм выполнения цикла for следующий:

1. Вычисляется инициализирующее выражение (ВЫРАЖЕНИЕ!).

2. Вычисляется выражение условия (вырАЖЕШЕ2). Если оно истинно, то выполняются операторы блока БЛОК, иначе цикл завершает свое выполнение.

3. После выполнения очередной итерации вычисляется выражение увеличения/уменьшения (ВЫРАЖЕНИЕЗ) и повторяется пункт 2.

Как отмечалось в предыдущем разделе, цикл for эквивалентен циклу while с блоком continue. Например, следующий цикл

for ($i = 1; $i .<= 10; $i++) { }

эквивалентен циклу while

$i =1;

while ($i <= 10) {

} continue {

$i++; }

Существует единственное отличие между этими двумя циклами. Цикл for определяет лексическую область видимости для переменной цикла. Это позволяет использовать в качестве переменных цикла локальные переменные, объявленные с помощью функции ту:

$i = "global";

for (my $i = 1; $i <= 3; $i++) {

print "Внутри цикла \$i: $i\n"; } print "Вне цикла \$i: $i\n ";

При выполнении этого фрагмента программы оператор печати будет последовательно отображать значения 1, 2 и з локальной переменной цикла $1. При выходе из цикла локальная переменная $i будет уничтожена и оператор печати вне цикла напечатает строку global — значение глобальной переменной $1, определенной вне цикла for.

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

for (;;) {

}

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

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

$i = 1;

for (•; $i <= 3;) {

$i++; }

Совет
Хотя существует возможность изменения переменной цикла в теле цикла, не рекомендуется ею пользоваться. Цикл for был введен в язык именно для того, чтобы собрать в одном месте все операторы, управляющие работой цикла, что позволяет достаточно быстро изменить его поведение.

Цикл for позволяет использовать несколько переменных для управления работой цикла. В этом случае в инициализирующем и изменяющем выражениях используется операция запятая. Например, если мы хотим создать хеш, в котором ключам, представляющим цифры от 1 до 9, соответствуют значения этих же цифр в обратном порядке от 9 до 1, то эту задачу можно решить с помощью цикла for с двумя переменными цикла:

for ($j = 1, $k = 9; $k >0; $j++, $k—) {

$hash{$j} = $k; }

Этот же пример показывает, что в цикле for переменная цикла может как увеличиваться, так и уменьшаться. Главное, чтобы выражение условия правильно отслеживало условия продолжения итераций цикла.

Цикл for достаточно гибкая конструкция, которую можно использовать не только для реализации цикла с заранее заданным числом итераций. Он позволяет в инициализирующем и изменяющем выражениях использовать вызовы встроенных и пользовательских функций, а не только определять и изменять переменные цикла. Основное — чтобы изменялось выражение условия завершения цикла. Пример 5.7 демонстрирует именно такое использование цикла for.

# peri -w

for (print "Введите данные, для завершения ввода нажмите <Enter>\n"; <STDIN>;

print "Введите данные, для завершения ввода нажмите <Enter>\n") {

last if $_ eq "\n"; print "Ввели строку: $_"; }

В этом примере пользователь вводит в цикле строки данных. Перед вводом новой строки отображается подсказка с помощью функции print (), которая определена в изменяющем выражении цикла. Выражение условия представляет операцию ввода из файла стандартного ввода <STDIN>. Так как это выражение вычисляется всякий раз, когда цикл переходит на очередную итерацию, то на каждом шаге цикла программа будет ожидать ввода с клавиатуры. Выход из цикла осуществляется командой last, вызываемой в случае ввода пользователем пустой строки. Введенные данные сохраняются во встроенной переменной $_, причем в ней сохраняется и символ перехода на новую строку, являющийся признаком завершения операции ввода данных. Поэтому при вводе пустой строки на самом деле в переменной $_ хранится управляющая последовательность "\n", с которой и осуществляется сравнение для реализации выхода из цикла.

Пример 5.8 демонстрирует программу, читающую 3 строки файла егг.егг. Операция чтения из файла задается в инициализирующем и изменяющем выражении вместе с определением и изменением переменной цикла $1.

 # peri -w

open (FF, "err.err") or die "Ошибка открытия файла";

for ($line=<FF>, $count = 1; $count  <=3;  $line=<FF>,
  $count++)

{

print "Строка $count:\n $line\n";

t ' • }

close(FILE);

Цикл  foreach

Одно из наиболее частых применений циклов в языках программирования — организация вычислений с элементами массивов: найти максимальный элемент, распечатать элементы массива, выяснить, существует ли элемент массива, равный заданному значению, и т. п. Подобные задачи легко решаются с помощью циклов while и for. В примере 5.9 определяется максимальный элемент массива (в предположении, что он содержит числовые данные).

#! peri -w

@array = (1,-6,9,18,0,-10);

$max = $array[0]; ,

for ($i = 1; $i <= $farray; $i++) {

$max = $array[$i] if $array[$i] > $max; }

После выполнения программы примера 5.9 переменная $max будет иметь значение 18, равное максимальному элементу массива $аггау. Обратим внимание читателя на то, что в цикле for (как и в цикле while) доступ к элементам массива организуется с помощью индекса.

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

МЕТКА foreach ПЕРЕМЕННАЯ (СПИСОК) БЛОК

МЕТКА foreach ПЕРЕМЕННАЯ (СПИСОК) БЛОК continue БЛОК

Он реализует цикл по элементам списка список, присваивая на каждом шаге цикла переменной ПЕРЕМЕННАЯ значение выбранного элемента списка. Блок операторов continue выполняется всякий раз, как начинается очередная итерация, за исключением первой итерации, когда переменная $temp равна первому элементу списка. Список можно задавать или последовательностью значений, разделенных запятыми, или массивом скаляров, или функцией, возвращаемым значением которой является список. Определение максимального элемента массива можно переписать с циклом foreach (пример 5.10).

#! peri, -w

Sarray = (1,-6,9,18,0,-10) ; $max = $array[0]; foreach $temp (Sarray) (

$max = $temp if $temp > $max; } print "$max";

На каждом шаге цикла переменная $temp последовательно принимает значения элементов массива $аггау. Обратите внимание на внешний вид программы — в отсутствии индексов массива она стала лучше читаемой.

Отметим несколько особенностей цикла foreach. Прежде всего следует сказать, что ключевое слово foreach является синонимом ключевого слова for. Цикл из примера 5.10 можно было бы записать и так:

for $temp (@array) { # Ключевое слово foreach синоним for.

$max = $temp if $temp > $max; }

Однако, как нам кажется, использование foreach лучше отражает семантику этого оператора цикла, так как в самом ключевом слове уже отражена его сущность (for each — для каждого).

Следующая особенность оператора foreach связана с переменной цикла. По умолчанию эта переменная является локальной, область видимости которой ограничена телом цикла. Она создается только на время выполнения оператора foreach, доступна внутри тела цикла и уничтожается при выходе из цикла.

Обычно программисты, работающие на языке Perl, вообще не применяют в циклах foreach переменную цикла. Это связано с тем обстоятельством, что в отсутствии явно заданной переменной цикла Perl по умолчанию использует специальную переменную $_. На каждом шаге цикла именно она будет содержать значение элемента списка или массива. С учетом этого факта цикл foreach примера 5.10 можно переписать так:

foreach (@array) { # В качестве переменной цикла используется $_.

$гоах = $_ if $_ > $max;

} . l

Последняя особенность оператора foreach, которая также связана с переменной цикла, заключается в том, что фактически на каждом шаге выполнения цикла эта переменная является синонимом того элемента списка, значение которого она содержит. Это означает, что ее изменение в цикле приводит к изменению значения соответствующего элемента списка. Это свойство цикла foreach удобно для изменения значений элементов списка. Отметим, что его можно применять к спискам, хранящимся в массивах. Например, возвести в квадрат каждый элемент списка можно следующим оператором foreach:

foreach $temp (@array) {

$temp **= 2; }

Список, по элементам которого организуется цикл, может быть задан не только явно конструктором или переменной массива, но и функцией, возвращаемым значением которой является список. Канонический способ печати хеш-массива в упорядоченном порядке представлен в примере 5.11.

 # peri -w %array = {

blue => 1,

red => 2,

green => 3,

yellow => 3 ); foreach (sort keys %array) {

print "$_\t => $array{$_}\n"; } '

Эта программа напечатает пары ключ/значение хеш-массива %аггау в соответствии
  с алфавитным порядком его ключей:

blue => 1

green => 3

red => 2

yellow => 3

Замечание
Цикл foreach выполняется быстрее аналогичного цикла for, так как не требует дополнительных затрат на вычисление индекса элемента списка


Глава 1

Команды управления циклом

Каждый цикл в программе завершается при достижении некоторого условия, определяемого самим оператором. В циклах while и for это связано с ложностью выражения-условия, а в цикле foreach с окончанием перебора всех элементов списка. Иногда возникает необходимость при возникновении некоторых условий завершить выполнение всего цикла, либо прервать выполнение операторов цикла и перейти на очередную итерации. Для подобных целей в языке Perl предусмотрены три команды last, next и redo, которые и называют командами управления циклом.

Синтаксис этих команд прост — ключевое слово, за которым может следовать необязательный идентификатор метки:

last ИДЕНТИФИКАТОР_МЕТКИ; next ИДЕНТИФИКАТОР_МЕТКИ; redo ИДЕНТИФИКАТОР_МЕТКИ;

Семантика этих команд также проста. Они изменяют порядок выполнения циклов, принятый по умолчанию в языке, и передают управление в определенное место программы, завершая выполнение цикла (last), переходя на следующую итерацию цикла (next) или повторяя выполнение операторов тела цикла при тех же значениях переменных цикла (redo). Место перехода задается меткой. Помните синтаксис операторов цикла? Каждый из них может быть помечен. Именно идентификаторы меток операторов цикла и используются в командах управления для указания места передачи управления.

Метка в программе Perl задается идентификатором, за которым следует двоеточие. В командах управления циклом используется именно идентификатор метки, а не метка.

Несколько слов о терминологии. Читатель, наверное, обратил внимание, что мы не называем команды управления циклом операторами. И это справедливо. Они не являются операторами, хотя могут использоваться как операторы. Их следует считать унарными операциями, результатом вычисления которых является изменение последовательности выполнения операторов. Поэтому команды управления циклом можно использовать в любом выражении Perl. Заметим, что их следует использовать в таких выражениях, где имеет смысл их использовать, например в выражениях с операцией "запятая":

open (INPUT_FILE, $file)

or warn ("Невозможно открыть $file: $!\n"), next FILE;

Приведенный оператор может являться частью программы, которая в цикле последовательно открывает и обрабатывает файлы. Команда next инициирует очередную итерацию цикла с меткой FILE, если не удалось открыть файл в текущей итерации. Обратите внимание, что она используется в качестве операнда операции "запятая". В таком контексте эта команда имеет смысл. Следующий оператор является синтаксически правильным, но использование в нем команды redo не имеет никакого смысла:

print "qu-qu", 5 * redo OUT, "hi-hi\n";

Результатом выполнения этого оператора будет повторение вычислений операторов цикла с меткой ODT, т. е. простое выполнение команды redo OUT.

Относительно команд управления циклом следует сказать, что к ним можно применять модификаторы, так как употребленные самостоятельно с завершающей точкой с запятой они рассматриваются как простые операторы: next if $a — 2;

Переход на следующую итерацию цикла осуществится только, если переменная $а равна 2.

Команда last

Команда last немедленно прекращает выполнение цикла, в котором она задана, и передает управление на оператор, непосредственно следующий за оператором цикла. Ее целесообразно использовать для нахождения одного определенного значения в массиве (пример 5.12).

#! peri -w

@letters = ("A".."Z");

for ($index=0; $index<01etters; $index++) {

last if $letters[$index] eq "M"; } print $index;

Цикл в программе примера 5.12 будет выполняться, пока перебор элементов массива $ letters не достигнет элемента, содержащего символ "м". После чего будет выполнен первый после оператора for оператор программы. В результате будет напечатано число 12 — индекс элемента, содержащего символ "м".

Метка используется для конкретизации передачи управления в случае вложенных циклов: управление передается непосредственно на оператор, следующий за оператором цикла с указанной меткой (пример 5.13).

CYCLE_1: while (...){ , CYCLE_2: for (...) {

CYCLE_3: foreach (...) { last CYCLE_2; }

Операторы цикла CYCLE_2 } Операторы цикла CYCLE_1 # Сюда передает управление

t оператор last CYCLE_2; }

Если в команде last указать метку CYCLE_I, то управление будет передано на первый после самого внешнего цикла оператор программы. Если в команде last задать метку CYCLE_S (или задать ее вообще без метки), то управление будет передано на первый оператор группы операторы цикла CYCLE_2.

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

Команда last осуществляет выход из цикла, не выполняя никаких блоков операторов continue.

Команда next

Команда next позволяет пропустить расположенные после нее в теле цикла операторы и перейти на следующую итерацию цикла. Если оператор цикла содержит блок continue, то его операторы выполняются до проверки условия окончания цикла, с которой начинается следующая итерация. Одно из применений этой команды — обработать определенные элементы массива, ничего не делая с другими. Программа примера 5.14 присваивает всем элементам массива, содержащим четные числа, символ звездочка "*".

#! peri -w

@array = (2, 5, 8, 4, 7,, 9); print "До: @array\n"; fоreach (Sarray) { next if $_ % 2;

$_ = »*»;

}

print "После: @array\n";

Результат выполнения программы примера 5.14 показан ниже:

До: 2 5 8 4 7 9 После: * 5 * * 7 9

Если элемент массива нечетное число, то результат операции $_ % 2 равен i (Истина) и команда next инициирует следующую итерацию цикла foreach, не изменяя значение текущего элемента массива. Если значением элемента массива является четное число, то команда next не выполняется и значение элемента меняется на символ "*".

Команда next, употребленная совместно с идентификатором метки прерывает выполнение цикла, в теле которого она находится, и начинает новую итерацию цикла с указанной меткой, выполнив предварительно его блок continue, если таковой имеется (пример 5.15).

#! peri -w

$out =- 0;

OUT: while ($out < 2) {

print "Начало внешнего цикла\п";

for($in=0; $in<=2; $in++) {

print "\$out: $out\t\$in: $in\n"; next OUT if $in =1; }

print "\$out: $out\n"; # Никогда не выполняется! } continue {

print "Блок continue внешнего цикла\п"; $out++; }

Вывод этой программы будет следующим:

Начало внешнего цикла

$out: 0 $in: 0

$out: 0 $in: 1

Блок continue внешнего цикла

Начало внешнего цикла

$out: I $in: 0

$out: I $in: 1

Блок continue внешнего цикла

Обратите внимание, что количество итераций внутреннего цикла for равно двум, так как на второй его итерации выполняется команда next OUT, прекращающая его выполнение и инициализирующая выполнение очередной итерации внешнего цикла OUT. Оператор печати этого цикла пропускается, выполняется блок операторов continue, проверяется условие и если оно истинно, то тело цикла выполняется. Таким образом, оператор печати внешнего цикла OUT не выполняется ни одного раза, что подтверждается приведенным выводом из программы примера 5.15.

Команда redo

Команда redo повторно выполняет операторы тела цикла, не инициализируя следующую итерацию. Это означает, что ни выражение изменения цикла for, ни операторы блока continue, если он присутствует, ни выражение условия не вычисляются. Операторы тела цикла, расположенные за оператором redo, пропускаются и снова начинается выполнение тела цикла со значениями переменных, которые они имели перед выполнением этой передачи управления. Программа примера 5.16 демонстрирует использование команды redo.

I! peri -w $notempty•= 0;

$total = 0;

for (;;) { tt Бесконечный цикл

$line=<STDIN>; # Ввод строки

chop($line);

last if $line eq "END"; # Выход из цикла

++$total;

redo if $line eq ""; # Возврат на чтение строки

++$notempty; } print "Всего прочитано строк: $total\nM3 них не пустых: $notempty\n";

Эта программа в бесконечном цикле ожидает ввода пользователем на клавиатуре строки данных и в переменной $ total подсчитывает количество введенных строк. В переменной $notempty вычисляется количество введенных не пустых строк. Если введена пустая строка, то команда redo начинает повторное выполнение операторов тела цикла, не увеличивая на единицу переменную $notempty. Для завершения бесконечного цикла следует ввести строку END. В этом случае выполняется команда last.

Функция chop используется для удаления из введенной пользователем строки символа перехода на новую строку "\п", поэтому в программе она сравнивается со строками без завершающего символа перехода на новую строку (сравни с примером 5.7).

Если команда redo используется с идентификатором метки, то ее действие аналогично действию команды next с той лишь разницей, что она просто передает управление на первый оператор тела цикла с указанной меткой, не инициируя следующей итерации и не вычисляя операторов блока continue. В качестве иллюстрации такого использования команды redo перепишем программу примера 5.16 следующим образом:

#! peri -w $notempty = 0; $total = 0; OUT: while (1) {

print "Введи строки\п"; # Сюда передает управление команда redo OUT; for {;;) {

$line=<STDIN>;

chop($line);

last OUT if-$line eq "END"; I Выход из всех циклов

++$total;

redo OUT if $line eq "";

++$notempty; } } print "Всего прочитано строк: $tptal\nH3 них не пустых: $notempty\n";

В примере 5.17 мы ввели внешний бесконечный цикл OUT и изменили команды redo и last, добавив к ним метку на внешний цикл. Теперь в случае, если пользователь вводит пустую строку, команда redo OUT передает управление на первый оператор внешнего цикла, и программа печатает приглашение ввести строки.


Глава 1

Именованные блоки

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

BLOCK1: {

$i = 1;

last BLOCK1; } continue {

++$i; }

print "Переменная \$i после BLOCK1: $i\n"; BLOCK2: {

$i = 1;

next BLOCK2; } continue {

++$i; } print "Переменная \$i после BLOCK2: $i\n";

Первый оператор print этого фрагмента кода напечатает значение переменной $i равным n, тогда как второй оператор print напечатает 2, так как при выходе из блока BLOCK2 будет выполнен оператор увеличения на единицу переменной $i из блока continue.

Замечание
Если в простом блоке операторов задан блок continue, то при нормальном завершении простого блока (без использования команд управления циклом) блок операторов continue также будет выполнен.

Блоки могут иметь метки, и в этом случае их называют именованными блоками. Подобные конструкции используются для реализации переключателей — конструкций, которые не определены в синтаксисе языка Perl. Существует множество способов создания переключателей средствами языка Perl. Один из них представлен в примере 5.18.

#! peri -w $var = 3; SWITCH: {

$casel = 1, last SWITCH if $var == 1;

$case2 = 1, last SWITCH if $var == 2;

$case3 = 1, last SWITCH if $var = 3;

$nothing = 1; }

После выполнения именованного блока операторов SWITCH переменная $casei будет равна 1, если $var равна i, $case2 будет равна 2, если $var равна 2 и, наконец, $case3 будет равна 3, если $var равна 3. В случае, если переменная $var не равна ни одному из перечисленных значений, то переменная $nothing будет равна i. Конечно, это простейший переключатель, разработанный всего лишь для демонстрации возможности быстрого создания переключателя в Perl. Для выполнения группы операторов в переключателе можно использовать не модификатор if, а оператор выбора if.

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

$notempty = 0; $total = 0; INPUT: {

$line=<STDIN>; chop($line);

last INPUT if $line eq "END"; # Выход из бесконечного цикла ++$total;

redo INPUT if $line eq ""; ++$notempty; redo INPUT; }

Узнаете программу примера 5.16? Действительно, это реализация без оператора цикла программы ввода строк и подсчета общего числа введенных, а также непустых строк. Единственное, что нам пришлось добавить — еще одну команду redo в конце блока операторов.


Глава 1

Оператор безусловного перехода

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

goto МЕТКА; goto ВЫРАЖЕНИЕ; goto &ПОДПРОГРАММА;

Первая форма goto МЕТКА передает управление на оператор с меткой МЕТКА, который может быть расположен в любом месте программы, за исключением конструкций, требующих определенных инициирующих действий перед их выполнением. К ним относятся цикл foreach и определение подпрограммы sub.

Замечание
Компилятор Perl не генерирует никаких ошибок, если в операторе goto задана не существующая метка, или он передает управление в конструкцию foreach или sub. Все ошибки, связанные с этим оператором, возникают во время выполнения программы.

Во второй форме оператора безусловного перехода goto ВЫРАЖЕНИЕ возвращаемым значением выражения должен быть метка, на которую и будет передано управление в программе. Эта форма оператора goto является аналогом вычисляемого goto языка FORTRAN:

@label = ("OUT", "IN");

goto $label[l]; •

В приведенном фрагменте кода выражение в операторе goto будет вычислено равным строке IN и именно на оператор с этой меткой будет передано управление.

Последняя форма оператора goto «ПОДПРОГРАММА обладает магическим свойством, как отмечают авторы языка. Она подставляет вызов указанной в операторе подпрограммы для выполняемой в данной момент подпрограммы. Эта процедура осуществляется подпрограммами AUTOLOAD (), которые загружают одну подпрограмму, скрывая затем, что на самом деле сначала была вызвана другая подпрограмма.

Описание оператора goto приведено нами исключительно для полноты изложения. В программах его следует избегать, так как он делает логику программы более сложной и запутанной. Намного лучше использовать структурированные команды управления потоком вычислений next, last и redo. Если в процессе программирования выяснится, что не обойтись без оператора безусловного перехода, то это будет означать только одно: на этапе проектирования программы она была не достаточно хорошо структурирована. Вернитесь снова к этапу проектирования и постарайтесь реструктурировать ее таким образом, чтобы не требовалось использовать оператор goto.

В этом разделе мы познакомились с основными операторами языка Perl, которые используются для написания программ. Узнали, что операторы могут быть простыми и составными.

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

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


Глава 1

Вопросы и Упражнения

Вопросы для самоконтроля

  1. Как определяются простые операторы Perl?
  2. Что такое модификаторы простых операторов и как они влияют на выполнение простых операторов?
  3. Перечислите составные операторы языка Perl.
  4. Что такое блок операторов и что он определяет в программе?
  5. Определите лексическую переменную.
  6. Какой оператор цикла удобнее для перебора всех элементов списка и почему?
  7. Какие команды используются в Perl для управления выполнением циклов?
  8. Как реализуются в Perl переключатели?

Упражнения

  1. Какие из следующих операторов являются простыми, а какие составными: "abc" if 1; if ($a) { print $a;} do{ $а++; $Ь—;} until $b; while( $a eq "а") { $а—;}
  2. Найдите ошибку в программе:
    # peri -w $а = "true";
    $b = "false";
    if ($a) $a = $b;
    elsif ($b) $b == $a;
  3. Напишите программу, которая по заданному числу STEP печатает лесенку из STEP ступеней (каждая следующая ступень на один символ "-" шире предыдущей):
    I (первая •• ступень}
    I (вторая ступень)
    I (третья ступень)
  4. Напишите программу, которая во вводимой пользователем строке подсчитывает количество слов, количество не пробельных символов и количество пробельных символов. Словом считать непрерывную последовательность алфавитно-цифровых символов, ограниченных пробельными символами ("\n", "\t", " "). Для завершения программы пользователь должен ввести пустую строку.

5. Напишите программу, которая читает целую величину ROW и печатает первые ROW строк треугольника Паскаля:

1
1 1
121
1331
14641


Глава 1

Часть 6. Операции ввода/вывода


Глава 1

Операция ввода команды

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

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

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

Для отображения в стандартный файл вывода STDOUT используется уже знакомая нам функция print, которая, однако, может выводить информацию и в файл, определенный своим дескриптором.

Заключенная в обратные кавычки "•"• строка символов является всего лишь удобной формой записи операции ввода команды операционной системы qx{}, с которой мы уже знакомы (см. часть 4).

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

Операция ввода команды различает скалярный и списковый контексты, в которых она может выполняться. В скалярном контексте возвращается одна строка, содержащая весь вывод на экран монитора выполненной команды, включая символы новой строки в случае многострочного вывода. В списковом контексте возвращается список значений, каждое из которых содержит одну строку вывода. Пример 6.1 демонстрирует использование операции ввода команды в соответствующих контекстах.

#! peri -w

$command = "dir";

$scalar = ~$command'; # Скалярный контекст. ,

@list = '$command~; # Списковый контекст.

print $scalar;

print $list[0], $list[lj;

При выполнении операции заключения в кавычки сначала осуществляется подстановка значения скалярной переменной $ command, а потом полученная строка передается на выполнение операционной системы. Переменная $scaiar (скалярный контекст) содержит весь вывод на экран монитора содержимого текущего каталога, поэтому при ее печати мы увидим все, что вывела команда dir. Когда результаты выполнения команды присваиваются массиву @iist (списковый контекст), то каждая строка вывода команды становится элементом массива, поэтому последний оператор печати примера 6.1 выводит первую и вторую строки.

В списковом контексте разбиение вывода команды операционной системы на элементы списка осуществляется в соответствии со значением встроенной переменной $/, которое используется в качестве разделителя. По умолчанию эта переменная содержит символ конца строки "\п" — поэтому и разбиение на элементы происходит по строкам. Присваивая этой переменной новое значение, мы тем самым определяем новое значение разделителя, которое будет использоваться при формировании элементов списка. Разделителем может быть любая последовательность символов. Например, в примере 6.2 в качестве разделителя задается строка "<КАТАЛОГ>".

§! peri -w

$/ = "<КАТАЛОГ>";

@list = ~dir s ; t Списковый контекст.

print $list[l], $list[2];

Теперь, в отличие от примера 6.1, элемент массива $iist[0j содержит не первую строку вывода команды dir, а вывод команды до первой встретившейся в нем последовательности символов "<КАТАЛОГ>". Аналогично, элемент $list[l] содержит вывод команды до следующей встретившейся последовательности СИМВОЛОВ "<КАТАЛОГ>" И Т. Д.

Команда, содержащаяся в псевдолитерале, выполняется всякий раз, как вычисляется этот псевдолитерал. Встроенная переменная $? содержит числовое значение состояния выполненной команды.

(Об интерпретации значений встроенной переменной $ ? см. часть 14 щелкни здесь.)

Хотим обратить внимание читателя еще раз на тот факт, что операция ввода команды возвращает вывод на стандартное устройство вывода операционной системы. При выполнении команды можно направить ее вывод на другое устройство, например, в файл. Для этого в строке после имени команды и всех необходимых для ее выполнения параметров следует задать символ ">", после которого ввести имя файла. В этом случае на экран монитора ничего выводиться не будет, а следовательно и ничего не будет возвращаться в псевдолитерал, т. е. после выполнения такой команды псевдолитерал будет содержать неопределенное значение (пример 6.3).

#! peri -w - $/ = "<КАТАЛОГ>";

$list = 'dir >file.dat~; # Вывод осуществляется в файл file.dat print $list; # Оператор ничего не напечатает!

Замечание
 Обобщенная форма операции заключения в обратные кавычки qx {} работает точно так же, как и операция заключения в обратные кавычки " * '".


Глава 1

Операция ()

Для нашего читателя эта операция не является совсем уж новой. Несколько слов о ней было сказано в части 4; в некоторых примерах мы ее использовали для ввода пользователем данных в программу Perl. Основное ее назначение — прочитать строку из файла, дескриптор которого является операндом этой операции. (Операнд операции о расположен внутри угловых скобок.) Мы не будем сейчас объяснять, что такое дескриптор файла, зачем он нужен и какую функцию выполняет в программах Perl. Эти вопросы будут нами подробно рассмотрены в следующей главе, посвященной работе с файлами. Здесь же мы остановимся на специальном случае использования этой операции — операции с пустым операндом 0. В этом случае ввод осуществляется или из стандартного файла ввода, или из каждого файла, перечисленного в командной строке при запуске программы Perl. Но прежде чем перейти к описанию функционирования операции ввода с пустым операндом, остановимся на некоторых понятиях, необходимых для понимания дальнейшего.

Для обеспечения единообразия работы программ Perl в разных операционных системах при их запуске открывается несколько стандартных файлов. Один из них предназначен для ввода данных в программу и связан со стандартным устройством ввода — клавиатурой. Этот файл и называется стандартным файлом ввода и имеет дескриптор STDIN. Для вывода информации из программы создается стандартный файл вывода, также связанный со стандартным устройством вывода операционной системы, которым является экран монитора компьютера. Этому стандартному файлу назначается дескриптор STDOUT. Для отображения разнообразных сообщений о возникающих в процессе выполнения программы ошибках создается стандартный файл ошибок, который связан со стандартным устройством вывода. Этот файл имеет дескриптор STDERR. Эти файлы не надо создавать и открывать — они уже существуют, когда наша программа начинает выполняться. Иногда их называют предопределенными файлами ввода/вывода. Таким образом, если мы, например, говорим о том, что ввод осуществляется из стандартного файла (или стандартного устройства), мы имеем в виду стандартный файл ввода с дескриптором STDIN.

При запуске программы Perl в системе UNIX или из командной строки Windows ей можно передать параметры. Эти параметры задаются после имени файла, содержащего программу Perl, и отделяются от него и друг от друга пробелами:

peri program.pl parl par2 рагЗ

Параметрами могут быть ключи (обычно символ с лидирующим дефисом, например, -v), которые устанавливают определенные режимы работы программы, или имена файлов, содержимое которых должна обработать программа. Все передаваемые в программу параметры сохраняются в специальном встроенном массиве @ARGV. Если не передается ни одного параметра, то этот массив пуст.

Операция о без операнда, употребленная в циклах while и for, при первом своем вычислении проверяет, пуст ли массив @ARGV. Если он пуст, то в первый элемент этого массива $ARGV[O] заносится символ "-" и операция ожидает ввода пользователя из стандартного файла ввода STDIN. Если массив @ARGV не пуст, то он содержит параметры, переданные программе при ее запуске. Операция о трактует каждый из них как имя файла и в цикле передает в программу последовательно все строки всех файлов, указанных в командной строке. Рассмотрим простейшую программу (пример 6.4), состоящую из одного цикла while с операцией <>, и рассмотрим ее поведение при разных способах запуска.

i! peri -w

while ($line = <>) {

print $line; }

При ее запуске без параметров она будет ожидать ввода пользователя с клавиатуры и в цикле распечатывать вводимые им строки, пока пользователь не завершит ввод комбинацией клавиш <Ctrl>+<Z>, интерпретируемой как конец файла.

Если при запуске передать ей имя файла, например, файла, содержащего саму же программу,

peri examp6_4.pi examp6_4.pi

то программа примера 6.4 распечатает его содержимое:

f! peri -w

while ($line = <>) {

print $line; }

Замечание
Предполагается, что программа примера 6.4 сохранена в файле с именем examp6_4.pl.

Если эту же программу запустить, задав в командной строке дважды имя файла программы,

peri examp6_4.pl examp6_4.pl examp6_4.pl

то программа дважды распечатает свой собственный текст.

Замечание
В операционной системе Windows в именах файлов можно использовать пробелы. Для передачи в программу Perl файла с таким именем его следует заключать в двойные кавычки: "Name with blanks.dat".

При выполнении операции ввода из файла встроенная переменная $. на каждом шаге цикла хранит номер прочитанной строки файла. В случае задания нескольких имен файлов в командной строке при последовательном вводе их строк операцией о эта переменная продолжает увеличивать свое значение при переходе на чтение строк очередного файла, т. е. она рассматривает содержимое всех файлов как один-единственный файл.

Операцию о и массив @ARGV можно совместно использовать для ввода в программу содержимого нескольких файлов, не связывая их с заданием имен файлов в командной строке. В любом месте программы перед первым использованием в цикле операции ввода <> можно в массив SARGV занести имена файлов, содержимое которых необходимо обработать:

@ARGV = ("filel.dat", "file2.dat", "file3.dat"); for (;<>;) {

Операторы обработки строк файлов }

Этот фрагмент программы в цикле for последовательно обработает строки трех файлов filel.dat, file2.dat и file3.dat. Здесь же продемонстрирована еще одна интересная особенность операции ввода о. Обычно прочитанная этой операцией строка присваивается скалярной переменной, как это происходило в примере 6.4, но если эта операция одна представляет выражение условия цикла, то результат ее выполнения сохраняется в специальной встроенной переменной $_. Цикл while программы примера 6.4 можно записать и так:

while (<>) ( print;

}

Здесь также используется то обстоятельство, что функция print без параметров по умолчанию выводит содержимое переменной $_.

Если мы хотим передать в программу некоторые ключи, устанавливающие режим ее работы, то в начале программы следует поместить цикл, который проверяет содержимое массива @ARGV на наличие ключей в командной строке вызова программы. Один из способов подобной проверки приводится в примере 6.5, где предполагается, что программе могут быть переданы ключи

-d, -s и -е.

f! peri -w

while ($_ = $ARGV[0], / ^ -/) {

if(/ ^ -d/) { print $ARGV[0],"\n";}

if(/ ^ -s/) { print $ARGV[0]-, "\n"; }

1£(/ ^ -е/) { print $ARGV[0],"\n"; }

shift; }

При вычислении выражения условия цикла while осуществляется присваивание переменной $_ значения первого элемента массива @ARGV и проверка присутствия дефиса "-" в качестве первого символа содержимого этой переменной (операция / Л -/). Операторы if проверяют содержимое переменной $_ на соответствие известным ключам и отображают их. (В реальных программах в этих операторах обычно определяют некоторые переменные, которые в дальнейшем используются для выполнения действий, присущих соответствующим ключам.) Функция shift удаляет из массива @ARGV первое значение, сдвигая оставшиеся в нем элементы на одну позицию влево: второй становится первым, третий вторым и т. д. Цикл повторяется до тех пор, пока переданные через командную строку параметры начинаются с дефиса. Еще одно применение операции <> связано с получением в программе имен файлов определенного каталога, удовлетворяющих заданному шаблону. Если в качестве операнда этой операции используется шаблон имен файлов, то в скалярном контексте она возвращает первое найденное имя файла в текущем каталоге, в списковом контексте — список имен файлов, удовлетворяющих заданному шаблону. (В шаблоне можно использовать метасимволы: * для произвольной цепочки символов, ? для произвольного одиночного символа.) Если в каталоге не найдены файлы с именами, удовлетворяющими шаблону, то операция возвращает неопределенное значение. Например, выполнение следующей операции

$first = <*.pl>;

приведет к сохранению в переменной $ first имени первого файла из списка всех файлов текущего каталога с расширением pi, если таковые файлы в каталоге есть, иначе эта переменная будет иметь неопределенное значение. В списке файлы упорядочены в алфавитном порядке. Эта же операция в списковом контексте

gfiles = <*.pl>;

возвращает список всех файлов с расширением pi. После выполнения этой операции элементы массива @files содержат имена всех файлов с расширением pi.

Замечание
Имена подкаталогов текущего каталога считаются файлами без расширения. Например, в возвращаемом операцией <*. *> списке файлов будут содержаться и имена подкаталогов текущего каталога.

Если при задании шаблона файла явно указать каталог, то эта операция возвратит список файлов из указанного каталога, имена которых удовлетворяют заданному шаблону. Например, операция

@files = </perl/*.pl>;

сохранит в массиве @flies имена всех файлов каталога /peri с расширением pi.

Замечание
В системе Windows эта операция найдет все файлы с расширением р! в каталоге /peri текущего диска. Для задания конкретного диска следует использовать принятый в Windows синтаксис для полного имени файла: <d: /peri/*. *>. Эта операция возвратит список всех файлов каталога /peri, расположенного на диске d:.

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

while ($file = <*.pl>) (

print "$file\n"; }

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

while (<*.pl>) {

print $_, "\п"; }

Операция получения имен файлов, соответствующих заданному шаблону, реализуется с помощью внутренней функции glob, единственным параметром которой является шаблон имен файлов. Эту функцию можно использовать самостоятельно для получения соответствующих имен файлов:

• @scripts = glob "*.pl";

В скалярном контексте она возвращает имя первого файла, удовлетворяющего заданному шаблону, в списковом — список имен всех файлов. Употребленная без параметра, она использует в качестве параметра специальную переменную $_.


Глава 1

Функция print

Функция print наиболее часто используемая функция для вывода информации из сценария Perl. Ее синтаксис имеет следующий вид:

print ДЕСКРИПТОР СПИСОК;

Здесь ДЕСКРИПТОР представляет дескриптор файла, в который функция выводит строковые данные, представленные списком вывода список. Он может состоять из переменных, элементов массивов и выражений, вычисляемых как строковые данные. Дескриптор файла создается функцией open 0,0 которой мы поговорим в следующей главе. Он может быть опущен, и в этом случае вывод осуществляется в стандартный файл вывода STDOUT, если только функцией select о не выбран другой файл вывода по умолчанию. Как уже отмечалось ранее, обычно стандартное устройство вывода — экран монитора компьютера.

Функция print при выводе своего списка не завершает его символом новой строки "\п". Это означает, что следующая функция print начнет вывод на экран непосредственно после последнего выведенного предыдущей функцией print символа. Если такое поведение не желательно, то следует список вывода каждой функции print явно завершать строкой, содержащей символ новой строки, или включать его последним символом последнего элемента списка вывода. Пример 6.6 демонстрирует вывод с помощью функции print.

#! peri -w

print "String 1:";

print "String 2:\n";

print "String 3:", "\n";

print STDOUT "String 4:\n";

print FILEOUT "String 4:\n";

Вывод первых четырех функций print примера 6.6 представлен ниже:

String I:String 2: String 3: String 4:

Вторая функция print начинает свой вывод на той же строке, на которой завершила вывод первая функция, в которой в списке вывода нет символа перехода на новую строку. В четвертой функции явно указан дескриптор стандартного файла вывода STDOUT. Относительно пятой функции скажем, что она ничего ни в какой файл, определенный дескриптором FILEOUT, не выведет, так как с этим дескриптором не связан никакой файл. Для этого следовало бы до выполнения последней функции print открыть файл функцией open о и связать с ним дескриптор FILEOUT. Мы отложим эти вопросы до следующей главы.

Функция print, как и большинство других функций, определенных в языке Perl, является списковой операцией, и все элементы списка вывода вычисляются в списковом контексте. Это обстоятельство следует учитывать при использовании в качестве элементов списка вывода выражений с вызовами подпрограмм.

Все, что было сказано относительно списковых операций и их использования в качестве термов выражений в главе 4, относится, естественно, и к функции print. Если ее параметры, включая дескриптор файла, заключены в круглые скобки, то такая синтаксическая конструкция считается термом и в выражении имеет наивысший приоритет вычисления. Например, следующий оператор

print ($m + $n) ** 2;

напечатает сумму значений переменных $т и $п, а не их сумму, возведенную в квадрат. Компилятор peri, обнаружив после лексемы print левую круглую скобку, найдет правую круглую скобку и будет рассматривать их содержимое как список параметров функции print. А так как такая конструкция есть терм, то сначала будет выполнена операция печати суммы значений переменных, а потом результат этой операции (Истина =1) будет возведен в квадрат. Добавление необязательного дескриптора стандартного файла вывода STDOUT исправит подобную ошибку:

print STDOUT ($m + $n) ** 2; # Выведет ($m + $n) ** 2

Если в функции печати print не задан список вывода, то она по умолчанию выводит содержимое специальной переменной $_ в файл, определенный параметром ДЕСКРИПТОР:

print; # Выводится содержимое переменной $_ на экран монитора, print STDOUT; # Эквивалентен предыдущему оператору, print FILEOUT; # Выводится содержимое переменной $_ в файл # с дескриптором FILEOUT

В этой главе мы познакомились с основными возможностями ввода/вывода, предоставляемыми языком Perl. Узнали, как легко и просто можно выполнить команду операционной системы и получить результаты ее вывода на экран монитора непосредственно в программу Perl. Операция <> позволяет не только считывать записи внешних файлов, но и автоматически обрабатывать содержимое нескольких файлов, заданных в командной строке при запуске сценария Perl. Эта же операция позволяет осуществлять поиск файлов, чьи имена удовлетворяют заданному шаблону. Для вывода информации из программы используется функция print о, которая может выводить ее не только на экран монитора (стандартное устройство вывода), но также и во внешний файл.


Глава 1

Вопросы и Упражнения

Вопросы для самоконтроля

  1. Каким образом можно получить результаты выполнения команды операционной системы в программе Perl?
  2. Какая операция позволяет прочитать содержимое всех файлов, переданных сценарию Perl через командную строку?
  3. Где хранятся имена параметров, переданных сценарию Perl через командную строку?
  4. Можно ли получить в программе Perl имена файлов определенного каталога, удовлетворяющих заданному шаблону?
  5. Какая списковая операция осуществляет вывод на экран монитора?
  6. Какая списковая операция осуществляет вывод во внешний файл?

Упражнения

  1. Напишите программу, которая копирует один файл в другой. Имена файлов передаются в программу при ее запуске как параметры командной строки. (Подсказка: используйте системную команду сору.)
  2. Напишите программу, которая отображает на экране содержимое файлов, имена которых задаются в командной строке. Отображение содержимого каждого файла должно предваряться строкой, содержащей имя файла. (Подсказка: использовать операцию <>.)
  3. Напишите программу Perl, которая удаляет файлы определенного каталога. Имена файлов задаются шаблоном, который вместе с именем каталога передается в программу через командную строку при ее запуске.


Глава 1

Часть 7. Работа с файлами


Глава 1

Дескрипторы файлов

Когда в программе мы создаем переменные и храним в них разнообразные данные, мы теряем их по завершении работы программы. Если нам необходимо сохранить данные и использовать их в разрабатываемых программах, мы создаем файл, записываем в него данные и сохраняем его на диске. Практически любой язык программирования предоставляет программисту средства манипулирования файлами и хранимыми в них данными.

Доступ к файлам в программе Perl осуществляется через специально создаваемые дескрипторы, которые можно рассматривать как некоторый особый вид переменных. Один дескриптор в каждый момент времени может быть связан с одним и только одним файлом, хотя на протяжении всей программы один и тот же дескриптор можно последовательно связывать с разными файлами.

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

Дескриптор — это символическое имя, которое используется в программе Perl для представления файла, устройства, сокета или программного канала. При создании дескриптора он "присоединяется" к соответствующему объекту данных и представляет его в операциях ввода/вывода. Мы дали наиболее полное определение дескриптора, чтобы читатель понимал, что дескриптор позволяет работать не только с данными файлов, но и с данными других специальных программных объектов, реализующих специфические задачи получения и передачи данных. Когда дескриптор присоединен к файлу, мы будем называть его дескриптором файла.

Замечание
При открытии файла в системе UNIX ему также назначается файловый деск-, риптор, или дескриптор файла, который ничего общего не имеет с файловым дескриптором Perl. В UNIX дескриптор файла является целым числом, тогда как в Perl это символическое имя, по которому мы можем ссылаться на файл. Чтобы получить числовой файловый дескриптор в программе Perl, можно воспользоваться функцией f ileno ().

В программе дескриптор файла чаще всего создается при открытии файла функцией open (), которой передаются два параметра — имя дескриптора и строка с именем файла и режимом доступа:

open( LOGFILE, "> /temp/logfile.log");

Этот оператор создает дескриптор с именем LOGFILE и присоединяет его к файлу с указанным именем, который открывается в режиме записи (строка второго параметра начинается с символа ">"). В этом разделе мы не будем касаться вопросов, связанных с режимом открытия файла, а сконцентрируем наше внимание на дескрипторах. В следующем разделе режимы открытия файла будут рассмотрены нами подробнее.

Дескриптор, как указывалось, является символическим именем файла и представляет собой правильный идентификатор, который не может совпадать с зарезервированными словами Perl. В нашем примере создается дескриптор LOGFILE, "замещающий" в операциях ввода/вывода файл, к которому он присоединен (/temp/logfile.log). Например, известной нам функцией print о мы можем теперь записать в этот файл значение какой-либо переменной:

print LOGFILE $var;

Любой созданный дескриптор попадает в символьную таблицу имен Perl, в которой находятся также имена всех переменных и функций. Однако дескриптор не является переменной, хотя некоторые авторы и называют его файловой переменной. В имени дескриптора не содержится никакого префикса, присущего переменным Perl ($, @ или %). Поэтому его нельзя непосредственно использовать в операции присваивания и сохранить в переменной или передать в качестве параметра в функцию. Для подобных целей приходится использовать перед его именем префикс *, который дает ссылку на глобальный тип данных. Например, предыдущий оператор печати в файл, определенный дескриптором LOGFILE, можно осуществить с помощью следующих операторов, предварительно сохранив ссылку на дескриптор в переменной $iogf:

$logf = *LOGFILE; print $logf $var;

В операции print первая переменная $iogf замещает дескриптор файла LOGFILE, в который выводится значение второй переменной $var.

(Ссылки на глобальные имена более подробно рассматриваются в части 9)

Замечание
В программах Perl принято в именах дескрипторов использовать прописные буквы. Подобная практика позволяет легко обнаруживать их в программе и не приводит к конфликтам с зарезервированными именами функций, которые обычно определены строчными буквами.

В любой программе Perl всегда существуют три предопределенные дескриптора (STDIN, STDOUT и STDERR), которые связаны со стандартными устройствами ввода/вывода и используются некоторыми функциями Perl в качестве умалчиваемых дескрипторов файлов ввода или вывода. Как мы уже знаем, дескриптор STDIN связан со стандартным устройством ввода (обычно клавиатура), STDOUT и STDERR — со стандартным устройством вывода (обычно экран монитора). Стандартное устройство ввода используется операцией о, если в командной строке вызова сценария Perl не задан список файлов. Дескриптор STDOUT ПО уМОЛЧаНИЮ ИСПОЛЬЗуеТСЯ ФУНКЦИЯМИ print И die, а

STDERR — функцией warn. Другие функции также используют предопределенные дескрипторы файлов для вывода своей информации.

При вызове программ в среде Unix и DOS можно перенаправлять стандартный ввод и вывод в другие файлы, задавая в командной строке их имена с префиксами > для файла вывода и < для файла ввода:

peri program.pl <in.dat >out.dat

При выполнении программы program.pi все исходные данные должны быть подготовлены в файле in.dat. Вывод будет сохранен в файле out.dat, а не отображаться на экране монитора.

Перенаправление стандартного ввода и вывода, а также стандартного отображения ошибок, можно осуществлять непосредственно в программе Perl. Для этого следует функцией ореп() связать соответствующий предопределенный дескриптор с некоторым дисковым файлом:

open(STDIN, "in.dat"); open(STDOUT, ">out.dat"); open(STDERR, ">err.dat");

Теперь весь стандартный ввод/вывод будет осуществляться через указанные в операторах open о файлы. Обратите внимание, что при переопределении стандартных файлов вывода и ошибок перед именами файлов стоит префикс ">", указывающий на то, что файлы открываются в режиме записи.

Замечание
Перенаправление стандартного ввода/вывода в программе можно производить только один раз. Это переназначение действует с момента перенаправления ввода/вывода и до конца программы, причем функцией open () нельзя вернуть первоначальные установки для дескрипторов STDIN, STDOUT и STDERR.


Глава 1

Доступ к файлам

Как мы уже знаем, для доступа к файлу из программы Perl необходим дескриптор. Дескриптор файла создается функцией open (), которая является списковой операцией Perl:

open ДЕСКРИПТОР, ИМЯ_ФАЙЛА; open ДЕСКРИПТОР;

При выполнении операции open с заданным в параметрах именем файла открывается соответствующий файл и создается дескриптор этого файла. В качестве дескриптора файла в функции open () можно использовать выражение — его значение и будет именем дескриптора. Имя файла задается непосредственно в виде строкового литерала или выражения, значением которого является строка. Операция open без имени файла открывает файл, имя которого содержится в скалярной переменной $ДЕСКРИПТОР, которая не может быть лексической переменной, определенной функцией ту(). Пример 7.1 демонстрирует использование операции open () для открытия файлов.

#! peri -w

$var = "out.dat";

$FILE4 = "file4.dat";

open FILE1, "in.dat"; # Имя файла задано строкой

open FILE2, $var; # Имя файла'задано переменной

open FILE3, "/perlourbook/01/".$var; # Имя файла вычисляется в выражении

open FILE4; # Имя файла в переменной $FILE4

Замечание
Если задано не полное имя файла, то открывается файл с указанным именем и расположенный в том же каталоге, что и программа Perl. Можно задавать полное имя файла (см. третий оператор open примера 7.1), однако следует иметь в виду, что оно зависит от используемой операционной системы. Например, в Windows следует обязательно задавать имя диска: d: /perlourbook/01/Chapterl. doc.

Замечание
В системе UNIX можно открыть достаточно много файлов, тогда как в DOS и Windows количество открытых файлов зависит от установленного значения переменной окружения FILE и варьируется от 20 до 50 одновременно открытых файлов.

Любой файл можно открыть в одном из следующих режимов: чтения, записи или добавления в конец файла. Это осуществляется присоединением соответствующего префикса к имени файла: < (чтение), > (запись), » (добавление). Если префикс опущен, то по умолчанию файл открывается в режиме чтения. Запись информации в файл, открытый в режиме записи (префикс >), осуществляется в начало файла, что приводит к уничтожению содержащейся в нем до его открытия информации. Информация, содержащаяся в файле, открытом в режиме добавления (префикс »), не уничтожается, новые записи добавляются в конец файла. Если при открытии файла в режиме записи или добавления не существует файла с указанным именем, то он создается, что отличает эти режимы открытия файла от режима чтения, при котором файл должен существовать. В противном случае операция открытия завершается с ошибкой и соответствующий дескриптор не создается.

Perl позволяет открыть файл еще в одном режиме — режиме чтения/записи. Для этого перед префиксом чтения <, записи > или добавления » следует поставить знак плюс +. Отметим различия между тремя режимами чтения/записи +<, +> и +». Первый и третий режимы сохраняют содержимое открываемого файла, тогда как открытие файла с использованием второго режима (+>) сначала очищает содержимое открываемого файла. Третий режим отличается от первых двух тем, что запись в файл всегда осуществляется в конец содержимого файла.

Замечание
Некоторые операционные системы требуют устанавливать указатель чтения/записи файла при переключении с операций чтения на операции записи. В Perl для этого предназначена функция seek (), описание которой будет дано несколько позже в этом же параграфе.

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

sysopen ДЕСКРИПТОР, ИМЯ_ФАЙЛА, ФЛАГ [, РАЗРЕШЕНИЕ];

Здесь параметр ИМЯ_ФАЙЛА представляет имя файла без префиксов функции open (), определяющих режим открытия файла. Последний задается третьим параметром ФЛАГ — числом, представляющим результат операции побитового ИЛИ (|) над константами режимов, определенными в модуле Fcnti. Состав доступных констант зависит от операционной системы. В табл. 7.1 перечислены константы режима, встречающиеся практически во всех операционных системах.

Таблица 7.1. Константы режима доступа к файлу

Константа | Значение
0_RDONLY | Только чтение
0_WRONLY | Только запись
O_RDWR | Чтение и запись
O_CREAT | Создание файла, если он не существует
О EXCL | Завершение с ошибкой, если файл уже существует
0_APPEND | Добавление в конец файла %

Права доступа (необязательный параметр РАЗРЕШЕНИЕ) задаются в восьмеричной системе и при их определении учитывается текущее значение маски доступа к процессу, задаваемого функцией umasko. Если этот параметр не задан, то Perl использует значение 0666.

(О правах доступа читайте документацию Perl для установленной на вашем компьютере операционной системе.)

Совет
Если возникают затруднения с установкой прав доступа, то придерживайтесь следующего правила: для обычных файлов передавайте 0666, а для каталогов и исполняемых файлов 0777.

В примере 7.2 собраны операции открытия файлов функцией open (} и эквивалентные ИМ ОТКРЫТИЯ С ПОМОЩЬЮ фуНКЦИИ sysopen () .

use Fcnti;

# Только чтение

open FF, "< file.txt";

sysopen FF, "file.txt", O_RDONLY;

# Только запись (создается, если не существует,

# и очищается содержимое, если существует)

open FF, "> file.txt";

sysopen FF, "file.txt", 0_WRONLY | 0_CREAT | OJTRUNC;

# Добавление в конец (создается, если не существует)

open FF, "» file.txt";

sysopen FF, "file.txt", OJJRONLY I 0_CREAT I O_APPEND;

# Чтение/запись (файл должен существовать) open FF, "+< file.txt"; sysopen FF, "file.txt", O_RDWR;

# Чтение/запись (файл очищается)

open FF, "+> file.txt";

sysopen FF, "file.txt", O_RDWR | 0_CREAT I OJTRUNC;

При открытии файла функции open о и sysopen о возвращают значение о, если открытие файла с заданным режимом произошло успешно, и неопределенное значение undef в противном случае. Всегда следует проверять успешность выполнения операции открытия файла, прекращая выполнение программы функцией die (). Эта функция отображает список передаваемых ей параметров и завершает выполнение сценария Perl:

open(FF, "+< $file") or'die "Нельзя открыть файл $file: $!";

Обратите внимание, в сообщении функции die () используется специальная переменная $!, в которой хранится системное сообщение или код ошибки. Эта информация помогает обнаружить и исправить ошибки в программе. Например, если переменная $fiie содержит имя не существующего файла, то при выполнении предыдущего оператора пользователь может увидеть сообщение следующего вида:

Нельзя открыть файл file.txt: No such file or directory at D:\PERL\EX2.PL line 4.

Английский текст этого сообщения представляет информацию, содержащуюся в Переменной $!.

Для полноты описания работы с функцией open о следует сказать, что если имя файла представляет строку "-", то открываемый файл соответствует стандартному вводу STDIN. Это означает, что ввод с помощью созданного дескриптора файла осуществляется со стандартного устройства ввода. Если имя файла задано в виде строки ">-", то это соответствует выводу на стандартное устройство вывода, представленное в программе дескриптором STDOUT.

Замечание
Если стандартный ввод или вывод были перенаправлены, то ввод/вывод с помощью дескрипторов, соответствующих файлам "-" и ">-", будет осуществляться в файл, определенный в операции перенаправления стандартного ввода или вывода.

Последнее, что нам хотелось бы осветить в связи с дескрипторами файлов, — это создание дескриптора-дубликата. Если в строке имени файла после префикса режима открытия следует амперсанд "&", то ее оставшаяся часть рассматривается как имя дескриптора файла, а не как имя открываемого файла. В этом случае создается независимая копия этого дескриптора с именем, заданным первым параметром функции open <). Оба дескриптора имеют общий указатель текущей позиции файла, но разные буферы ввода/вывода. Закрытие одного из дескрипторов не влияет на работу другого. В программах Perl возможность создания копии дескриптора в основном применяется для восстановления стандартных файлов ввода/вывода после их перенаправления на другие файлы (пример 7.3).

#! peri -w

# Создание копии дескриптора STDOUT open(OLDOUT, ">&STDOUT");

# Перенаправление стандартного вывода

open(STDOUT, "> file.out") or die "Невозможно перенаправить STDOUT: $!";

# Печать в файл file.out

print "Информация в перенаправленный STDOUTXn";

# Закрытие перенаправленного дескриптора стандартного вывода close(STDOUT) or die "Невозможно закрыть STDOUT: $!";

# Восстановить файл стандартного вывода

open(STDOUT, ">&OLDOUT") or die "Невозможно восстановить STDOUT: $!";

# Закрыть копию дескриптора стандартного вывода STDOUT close(OLDOUT) or die "Невозможно закрыть OLDOUT: $!";

# Печать в восстановленный файл стандартного вывода print "Информация в восстановленный STDOUTXn";

Замечание
В программах следует избегать работу с одним файлом через несколько дескрипторов-копий.

По завершении работы с файлом он закрывается функцией close 0. Единственным необязательным параметром этой функции является дескриптор, ассоциированный с файлом:

close ДЕСКРИПТОР;

Эта функция возвращает значение Истина, если успешно очищен буфер ввода/вывода и закрыт системный дескриптор файла. Вызванная без параметра, функция close закрывает файл, связанный с текущим дескриптором, установленным функцией select 0.

Следует отметить, что закрывать файлы в программе функцией close о не обязательно. Дело в том, что открытие нового файла с дескриптором, уже связанным с каким-либо файлом, закрывает этот старый файл. Более того, при завершении программы все открытые в ней файлы закрываются. Однако такое неявное закрытие файлов таит в себе потенциальные ошибки из-за невозможности определить, завершилась ли эта операция корректно. Может оказаться, что при записи в файл переполнится диск, или будет разорвана связь с удаленным устройством вывода. Подобные ошибки можно "отловить", если использовать явное закрытие файла и проверять содержимое специальной переменной $!:

closet FILEIO ) or die "Ошибка закрытия файла: $!";

Существует еще один нюанс, связанный с явным закрытием файлов. При чтении из файла специальная переменная $. (если ее значение не изменено явным образом в программе) хранит номер последней прочитанной записи файла. При явном закрытии файла функцией close о значение этой переменной обнуляется, тогда как при неявном закрытии оно остается равным номеру последней прочитанной записи старого файла и продолжает увеличиваться при операциях чтения из нового файла.

Чтение информации из файла осуществляется операцией о, операндом которой является дескриптор файла. В скалярном контексте при первом выполнении эта операция читает первую запись файла, устанавливая специальную переменную $., отслеживающую количество прочитанных записей, равной 1. Последующие обращения к операции чтения из файла с тем же дескриптором приводят к последовательному чтению следующих записей. В списковом контексте эта операция читает все оставшиеся записи файла и возвращает список, элементами которого являются записи файла. Разделитель записей хранится в специальной переменной $/, и по умолчанию им является символ новой строки "\n". Perl позволяет задать и другой разделитель записей обычной операцией присваивания переменной $/ нового символа разделителя записей. В примере 7.4 демонстрируются некоторые приемы чтения из файла.

#! peri -w

open(Fl, "in.dat") or die "Ошибка открытия файла: $!";

open(F2, "out.dat") or die "Ошибка открытия файла: $!";

$linel = <F1>; # Первая запись файла in.dat $line2 = <F1>; # Вторая запись файла in.dat

@rest =• <F1>; # Оставшиеся записи файла in.dat

$/=":"; I Задание другого разделителя записей файла @f2 = <F2>;

# Печать прочитанных записей файла out.dat for($i=0; $i<=$#f2; $i++) { print "$f2[$i]\n";

}

$/ = "\n"; # Восстановление умалчиваемого разделителя записей

close(Fl) or die $!; close(F2) or die $!;

open(F3, "out.dat") or die "Ошибка открытия файла: $!"; print <F3>; # Печать всего файла close(F3) or die $!;

Несколько комментариев к программе примера 7.4. В переменные $iinel и $iine2 читаются соответственно первая и вторая строка файла in.dat, так как используется умалчиваемый разделитель записей "\п". Элементы массива @rest хранят строки с третьей по последнюю этого же файла: в операторе присваивания операция чтения <FI> выполняется в списковом контексте.

Перед чтением записей файла out.dat устанавливается новый разделитель записей — символ ":". Если файл out.dat, например, содержит только одну строку

111: 222: 333: Конец

то элементы массива @ f г будут содержать следующие значения:

$f2[0] = "111:" $f2[l] = "222:" $f2[2] = "333:" $f2[3] = "Конец"

Замечание
Если при создании файла out.dat его единственная строка завершена переходом на новую строку (нажата клавиша <Enter>), то $f2[3], будет содержать строку "конец\п".

При достижении конца файла операция о возвращает неопределенное значение, которое трактуется как Ложь. Это обстоятельство обычно используется для организации чтения записей файла в цикле:

while($line = <F1>) {

print $line; f Печать очередной строки связанного

# с дескриптором F1 файла } •

Запись в файл, открытый в режиме записи или добавления, осуществляется функцией print () с первым параметром, являющимся дескриптором файла:

print ДЕСКРИПТОР СПИСОК_ВЫВОДД;

Эта операция записывает содержимое элементов списка в том порядке, в котором они определены в вызове функции, и не добавляет в конец списка разделителя записей. Об этом должен позаботиться сам программист:

$/=":"; # Разделитель записей

print Fl @recll, $/; # Запись в файл первой записи

print Fl @rec!2, $/; tt Запись в файл второй записи

Замечание

Между дескриптором и первым элементом списка вывода не должно быть запятой. Если такое случится, то компилятор peri выдаст ошибку:

No comma allowed after filehandle

Если в функции print не указан дескриптор файла, то по умолчанию вывод осуществляется в стандартный файл вывода с дескриптором STDOUT. Эту установку можно изменить функцией select (). Вызванная без параметров, она возвращает текущий умалчиваемый дескриптор для вывода функциями print () и write (). Если ей передается единственный параметр, то этот параметр должен быть дескриптором файла. В этом случае она также возвращает текущий умалчиваемый дескриптор и меняет его на дескриптор, определенный переданным ей параметром.

$oldfilehandle = select(Fl); I Сохранение текущего дескриптора по

# умолчанию и назначение нового F1

print $line; # Вывод в дескриптор F1 select($oldfilehandle); # Восстановление старого дескриптора

# по умолчанию print $line; # Вывод в старый дескриптор

Файлы в Perl интерпретируются как неструктурированные потоки байтов. При работе с файлом через дескриптор отслеживается его текущая позиция. Операции чтения/записи выполняются с текущей позиции файла. Если, например, была прочитана запись длиной 80 байт, то следующая операция чтения или записи начнется с 81 байта файла. Для определения текущей позиции в файле используется функция tell (), единственным параметром которой может быть дескриптор файла. Она возвращает текущую позицию в связанном с дескриптором файле. Эта же функция без параметра возвращает текущую позицию в файле, для которого была в программе выполнена последняя операция чтения.

Текущая позиция в файле автоматически изменяется в соответствии с выполненными операциями чтения/записи. Ее можно изменить с помощью функции seek о, которой передаются в качестве параметров дескриптор файла, смещение и точка отсчета. Для связанного с дескриптором файла устанавливается новая текущая позиция, смещенная на заданное параметром СМЕЩЕНИЕ число байт относительно точки отсчета:

seek ДЕСКРИПТОР, СМЕЩЕНИЕ, TO4KAJDTC4ETA;

Параметр ТОЧКА_ОТСЧЕТА может принимать одно из трех значений: о — начало файла, 1 — текущая позиция, 2 — конец файла. Смещение может быть как положительным, так и отрицательным. Обычно оно отрицательно для смещения относительно конца файла и положительно для смещения относительно начала файла. Для задания точки отсчета можно воспользоваться константами SEEK_SET, SEEK_CUR и SEEK_END из модуля ю: :Seekabie, которые соответствуют началу файла, текущей позиции и концу файла. Естественно, необходимо подключить этот модуль к программе с помощью ключевого слова use. Например, следующие операторы устанавливают одинаковые текущие позиции в файлах:

use 10::Seekable; seek FILE1, 5, 0; seek FILE2, 5, SEEK_SET;

Для перехода в начало или в конец файла следует использовать нулевое смещение относительно соответствующих точек отсчета при обращении к функции seek ():

seek FILE1, 0, 0; # Переход в начало файла seek FILE1, 0, 2; § Переход в конец файла

Кроме операции чтения записей файла о, Perl предоставляет еще два способа чтения информации из файла: функции getc () и read (). Первая читает один байт из файла, тогда как вторая читает записи фиксированной длины.

Функция getc возвращает символ в текущей позиции файла, дескриптор которого передан ей в качестве параметра, или неопределенное значение в случае достижения конца файла или возникновении ошибки. Если функция вызывается без параметра, то она читает символ из стандартного файла ввода STDIN.

getc; t Чтение символа из STDIN

getc Fl; # Чтение символа в текущей позиции файла с дескриптором F1

Функции read () передаются три или четыре параметра и ее синтаксис имеет вид:

read ДЕСКРИПТОР, ПЕРЕМЕННАЯ, ДЛИНА [,СМЕЩЕНИЕ] ;

Она читает количество байтов, определенное значением параметра ДЛИНА, в скалярную переменную, определяемую параметром ПЕРЕМЕННАЯ, из файла с дескриптором, заданным первым параметром ДЕСКРИПТОР. Возвращаемое значение — действительное количество прочитанных байтов, о при попытке чтения в позиции конца файла и неопределенное значение в случае возникновения ошибки. Параметр СМЕЩЕНИЕ определяет количество сохраняемых байтов из содержимого переменной ПЕРЕМЕННАЯ, т. е. запись прочитанных из файла данных будет добавлена к содержимому переменной после байта, определяемого значением параметра СМЕЩЕНИЕ. Отрицательное значение смещения -п (п — целое число) означает, что из содержимого переменной ПЕРЕМЕННАЯ отбрасываются последние п байтов и к оставшейся строке добавляется запись, прочитанная из файла. Пример 7.5 демонстрирует чтение записей фиксированной длины в предположении, что файл in.dat содержит три строки данных:

One Two Three

#! peri -w

open(Fl, "in.dat") or die "Ошибка открытия файла: $!";

$string = "1234567890";

read Fl, $string, 6; I Чтение шести байт в переменную без смещения

print $string,"\n"; # $string = "OneXnTw"

read Fl, $string, 6, length($string);

print $string,"\n"; # $string = "One\nTwo\nThre"

Функция length о возвращает количество символов (байтов) в строковых данных, хранящихся в скалярной переменной, переданной ей в качестве параметра. После выполнения первой операции чтения содержимое переменной $string было уничтожено, так как эта функция read о вызывалась без смещения. Тогда как при втором чтении хранившиеся данные в переменной $string были полностью сохранены.

Операции о, print, read, seek и tell относятся к операциям буферизованного ввода/вывода, т. е. они для повышения скорости выполнения используют буферы. Perl для выполнения операций чтения из файла и записи в файл предлагает также аналоги перечисленных функций, не использующие буферы при выполнении соответствующих операций с содержимым файла.

Функции sysread и syswrite являются не буферизованной заменой операции

о И ФУНКЦИИ print, а ФУНКЦИЯ sysseek Заменяет ФУНКЦИИ seek И tell.

Функции не буферизованного чтения и записи получают одинаковые параметры, которые соответствуют параметрам функции read:

sysread ДЕСКРИПТОР, ПЕРЕМЕННАЯ, ДЛИНА [,СМЕЩЕНИЕ]; syswrite ДЕСКРИПТОР, ПЕРЕМЕННАЯ, ДЛИНА [.СМЕЩЕНИЕ];

Смысл всех параметров аналогичен параметрам функции read (). Возвращаемым значением этих функций является истинное количество прочитанных/записанных байт, о в случае достижения конца файла или undef при возникновении ошибки.

Параметры функции sysseek о полностью соответствуют параметрам функции seek():

sysseek ДЕСКРИПТОР, СМЕЩЕНИЕ, ТОЧКА_ОТСЧЕТА;

Все, сказанное относительно использования функции seek о, полностью переносится и на ее не буферизованный аналог.

Функциональность буферизованной операции tell () реализуется следующим вызовом функции sysseek:

$position = sysseek Fl, 0, 1; # Текущая позиция указателя файла

Пример 7.6 демонстрирует использование не буферизованных функций, ввода/вывода для обработки содержимого файла.

#! peri -w use Fcntl;

# Открытие файла в режиме чтение/запись sysopen Fl, "in.dat", OJRDWR;

# Чтение блока в 14 байт

$read = sysread Fl, $string, 14;

warn "Прочитано $read байт вместо 14\n" if $read != 14;

# Установка текущей позиции (на 15 байт). $position = sysseek Fl, 0, 1; die "Ошибка позиционирования: $!\п" unless defined $position;

# Запись строки в текущей позиции $string = "Новое значение"; $written = syswrite Fl, $string, length($string);

die "Ошибка записи: $!\n" if $written != length($string); # Закрытие файла

close Fl or die $!;

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

Совет
При работе с одним и тем же файлом не следует смешивать вызовы буферизованных и не буферизованных функций ввода/вывода. Подобная практика может приводить к непредсказуемым коллизиям.


Глава 1

Операции с файлами

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

Работа пользователя в UNIX начинается с процедуры регистрации в системе, во время которой он вводит свое регистрационное имя и пароль. Регистрационное имя назначается администратором системы и хранится в специальном учетном файле. Пароль задает сам пользователь.

Регистрационное имя легко запоминается пользователем, но для системы удобнее вести учет пользователей, идентифицируя их не по символическим регистрационным именам, а по числовым идентификаторам. Поэтому каждому пользователю системы UNIX помимо мнемонического регистрационного имени присваивается также числовой идентификатор пользователя (uid — User IDentifier) и идентификатор группы (gid — Group IDentifier), к которой он относится. Значения uid и gid приписываются процессу, в котором выполняется командный интерпретатор shell, запускаемый при входе пользователя в систему. Эти же идентификаторы передаются и любому другому процессу, запускаемому пользователем во время его сеанса работы в UNIX.

Файловая система UNIX представляет собой дерево, промежуточные вершины которого соответствуют каталогам, а листья файлам или пустым каталогам. Каждый файл идентифицируется своим уникальным полным именем, которое включает в себя полный путь (pathname) от корня файловой системы через промежуточные вершины (каталоги) непосредственно к файлу. Корневой каталог имеет предопределенное имя, представляемое символом "/". Этот же символ используется и для разделения имен каталогов в цепочке полного имени файла, например /bin/prog.exe.

Каждый файл в файловой системе UNIX характеризуется значительно большим объемом информации, чем, например, файл в файловой системе FAT. Эта информация включает, в частности, данные о владельце файла, группе, к которой принадлежит владелец файла, о том, кто имеет право на чтение файла, запись в файл, на выполнение файла и т. д. Эта информация позволяет задавать разные права доступа к файлу для следующих категорий пользователей: владелец файла, члены группы владельца, прочие пользователи. Вся существенная информация о файле хранится в специальной структуре данных, называемой индексным дескриптором (mode). Индексные дескрипторы размещаются в специальной области диска, формируемой при его форматировании в системе UNIX.

При запуске процесса с ним связываются два идентификатора пользователя: действительный (real) и эффективный (effective) и два аналогичных идентификатора группы пользователей. Действительные идентификаторы пользователя и группы — это постоянные идентификаторы, связываемые со всеми процессами, запускаемыми пользователем. Эффективные идентификаторы — это временные идентификаторы, которые могут устанавливаться для выполнения определенных действий. Например, при изменении пользователем пароля программа passwd автоматически устанавливает эффективные идентификаторы процесса таким образом, чтобы обеспечить права записи в файл паролей.

Как только с процессом связаны соответствующие идентификаторы, для него начинают действовать ограничения доступа к файлам. Процесс может получить доступ к файлу только в случае, если это позволяют хранящиеся при файле ограничения доступа.

Для каждого зарегистрированного пользователя системы создается так называемый "домашний" (home) каталог пользователя, к которому он имеет неограниченный доступ, а также и ко всем каталогам и файлам, содержащимся в нем. Пользователь может создавать, удалять и модифицировать каталоги и файлы из своего домашнего каталога. Потенциально возможен доступ и ко всем другим файлам, однако он может быть ограничен, если пользователь не имеет достаточных привилегий.

Любой пользователь, создавший собственный файл, считается его владельцем. Изменить владельца файла из сценария Perl можно функцией chown (). Параметром этой функции является список, первые два элемента которого должны представлять новые .числовые идентификаторы uid и gid. Остальные элементы списка являются именами файлов, для которых изменяется владелец. Эта функция возвращает количество файлов, для которых операция изменения владельца и группы прошла успешно.

@list = ( 234, 3, "filel.dat", "file2.dat");

$number = chown(@list);

warn "Изменился владелец не у всех файлов!" if $number != @list-2;

Замечание

Изменить владельца файла может только сам владелец или суперпользователь (обычно системный администратор) системы UNIX. В операционных системах с файловой системой отличной от UNIX (DOS, Windows) эта функция отрабатывает, но ее установки не влияют на доступ к файлу.

Функция chmodo изменяет права доступа для файлов, представленных в списке, передаваемом ей в качестве параметра. Первым элементом этого списка должно быть трехзначное восьмеричное число, задающее права доступа для владельца, пользователей из группы, в которую входит владелец, и прочих пользователей. Каждая восьмеричная цифра определяет право на чтение файла, запись в файл и его выполнение (в случае если файл представляет выполняемую программу) для указанных выше групп пользователей. Установленные биты ее двоичного представления отражают соответствующие права доступа к файлу. Например, если установлены все три бита (восьмеричное число 7), то соответствующая группа пользователей обладает всеми перечисленными правами: может читать из файла, записывать в файл и выполнять его. Значение равное 6 определяет право на чтение и запись, 5 позволяет читать из файла, выполнять его, но не позволяет записывать в этот файл и т. д. Обычно не выполняемый файл создается с режимом доступа 0666 — все пользователи могут читать и записывать информацию в файл, выполняемый файл — с режимом 0777. Если владелец файла желает ограничить запись в файл пользователей не его группы, то следует выполнить следующий оператор:

chmod 0664, "file.dat";

Возвращаемым значением функции chmodo, как и функции chowno, является количество файлов из списка, для которых операция изменения прав доступа завершилась успешно.

Замечание

В операционных системах DOS и Windows имеет значение только установка режимов доступа владельца.

В структуре индексного дескриптора файла существует три поля, в которых хранится время последнего обращения (atime) к файлу, его изменения (mtime) файла и изменения индексного дескриптора Xctime): Функцией utimeO можно изменить время последнего обращения и модификации файла. Ее параметром является список, содержащий имена обрабатываемых файлов, причем первые два элемента списка — числовые значения нового времени последнего доступа и модификации:

gfiles = ("filel.dat", "file2.dat");

$now = time;

utime $now, $now, 6files;

В этом фрагменте кода время последнего доступа и модификации файлов из списка @files изменяется на текущее время, полученное с помощью функции time.

Отметим, что при выполнении функции utime о изменяется и время последней модификации индексного дескриптора (ctime) — оно устанавливается равным текущему времени. Возвращаемым значением является количе- . ство файлов, для которых операция изменения времени последнего доступа и модификации прошла успешно.

Файловая система UNIX позволяет создавать ссылки на один и тот же файл. Это реализуется простым указанием одного и того же индексного дескриптора для двух элементов каталога. Такие ссылки называются жесткими (hard) ссылками, и операционная система не различает элемент каталога, созданный при создании файла, и ссылок на этот файл. При обращении к файлу по ссылке и по имени изменяются поля индексного дескриптора. Физически файл уничтожается только тогда, когда уничтожается последняя жесткая ссылка на файл.

В UNIX существует еще один тип ссылок на файл — символические ссылки. Эти ссылки отличаются от жестких тем, что они косвенно ссылаются на файл, имя которого хранится в блоке данных символической ссылки.

Жесткие ссылки создаются в Perl функцией linko, а символические — функцией symiinko. Синтаксис этих функций одинаков — их два параметра представляют имя файла, для которого создается ссылка, и новое имя файла-ссылки:

link СТАРЫЙ_ФАЙЛ, НОВЫЙ_ФАЙЛ; symlink СТАРЫЙ_ФАЙД, НОВЫЙ_ФАЙЛ;

При успешном создании жесткой ссылки функция link о возвращает Истина, иначе Ложь. Создание символической ссылки функцией symiinko сопровождается возвратом ею числа i в случае успешного выполнения операции иов противном случае.

Замечание

В версиях Peri для DOS эти функции не реализованы, и при попытке их вызова интерпретатор выдает фатальную ошибку:

The Unsupported function link function is unimplemented at D:\EX2.PL line 2.

The symlink function is unimplemented at D:\EX2.PL line 2.

Удалить существующие ссылки на файл можно функцией unlink о. Эта функция удаляет одну ссылку на каждый файл, заданный в списке ее параметров. Если ссылок на файл не существует, то удаляется сам файл. Функция возвращает количество файлов, для которых успешно прошла операция удаления. Вызов функции unlink без списка параметров использует содержимое специальной переменной $_ в качестве списка параметров. Следующий фрагмент кода удаляет все резервные копии файлов текущего каталога:

unlink <*.bak>;

В структуре индексного дескриптора поле nlink содержит количество жестких ссылок на файл. Его можно использовать совместно с функцией unlink о для удаления всех ссылок на файл. Если ссылок нет, то это поле имеет значение 1 (только имя файла, определенное при его создании, ссылается на индексный дескриптор файла).

Замечание

Каталоги в UNIX являются файлами специального вида. Однако их нельзя удалить функцией unlink, если только вы не суперпользователь или при запуске peri не используется флаг -и. Для удаления каталогов рекомендуется использовать функцию rmdir ().

Две последние операции, связанные с файлами, — это переименование и усечение файла. Функция rename о меняет имя файла, заданного первым параметром, на имя, определяемое вторым параметром этой функции:

rename "old.dat", "new.dat";

Этот оператор переименует файл old.dat в файл new.dat. Функция переименования файла возвращает i при успешном выполнении этой операции и о в противном случае.

Функция truncated усекает файл до заданной длины. Для задания файла можно использовать как имя файла, так и дескриптор открытого файла:

truncate ДЕСКРИПТОР, ДЛИНА; truncate ИМЯ_ФАЙЛА, ДЛИНА;

Функция возвращает значение Истина, если длина файла успешно усечена до количества байт, определенных в параметре ДЛИНА, или неопределенное значение undef в противном случае. Под усечением файла понимается не только уменьшение его длины, но и увеличение. Это означает, что значение второго параметра функции truncate о может быть больше истинной длины файла, что позволяет делать "дыры" в содержимом файла, которые в дальнейшем можно использовать для записи необходимой информации, не уничтожая уже записанную в файл (пример 7.7).

#! peri -w

# Создание файла с "дырами" for($i=l;$i<=3;$i++H

open(F, "»out.dat") or die $!; print F "Запись".$i;

close F;

open(F, "»out.dat") or die $!; truncate F, 19*$i;

close F;

} ' tt Запись информации в "дыры" open(F, "+<out.dat") or die $!; for($i=l;$i<=3;$i++){

seek F, 0,1;

read F,$reel,7;

seek F,0,l;

print F "<CONTENTS:".$i.">"; } close F;

На каждом шаге первого цикла for примера 7.7 в конец файла out.dat записывается информация длиной 7 байтов, а потом его длина увеличивается на 12 байтов, образуя пустое пространство в файле. Следующий цикл for заносит в эти созданные "дыры" информацию длиной 12 байтов, не затирая хранящуюся в файле информацию. Обратите внимание, что для изменения длины файла функцией truncate приходится закрывать его и снова открывать. Это связано с тем обстоятельством, что функция truncate о добавляет пустое пространство в начало файла, сдвигая в конец его содержимое, если применять ее, не закрывая файл. Можете поэкспериментировать с программой примера 7.7, открыв файл перед выполнением первого цикла for, и закрыв его после завершения цикла. Содержимое файла даст вам наглядное представление о работе функции truncate в этом случае. У нас же после выполнения первого цикла for содержимое файла out.dat выглядит так:

Запись1 Запись2 Запись3

\

По завершении всей программы файл будет содержать следующую строку:

Запись<CONTENTS:1>Запись2<CONTENTS:2>ЗаписьЗ<CONTENTS:3>


Глава 1

Получение информации о файле

Мы знаем, что в файловой системе UNIX информация о файле хранится в его индексном дескрипторе (inode). Структура индексного дескриптора состоит из 13 полей, для которых используются специальные обозначения. Все они перечислены в табл. 7.2.

Таблица 7.2. Структура индексного дескриптора

Поле | Описание
dev | Номер устройства в файловой системе
ino | Номер индексного дескриптора
mode | Режим файла (тип и права доступа)
nlink | Количество жестких ссылок на файл (в отсутствии ссылок равно 1)
uid | Числовой идентификатор владельца файла
gid | Числовой идентификатор группы владельца файла
rdev | Идентификатор устройства (только для специальных файлов)
size | Размер файла в байтах
a time | Время последнего обращения к файлу с начала эпохи
mtime | Время последнего изменения файла с начала эпохи
с time | Время изменения индексного дескриптора с начала эпохи
blksize | Предпочтительный размер блока для операций ввода/вывода
blocks | Фактическое количество выделенных блоков для размещения файла

Замечание

Начало эпохи датируется 1 января 1970 года 0 часов 0 минут.

Замечание

Не все перечисленные в табл. 7.2 поля структуры индексного дескриптора поддерживаются всеми файловыми системами.

Для получения значений полей структуры индексного дескриптора файла в Perl предназначена функция stato. Ее единственным параметром может быть либо имя файла, либо дескриптор открытого в программе файла. Она возвращает список из 13 элементов, содержащих значения полей структуры индексного дескриптора файла в том порядке, как они перечислены в табл. 7.2. Типичное использование в программе Perl представлено ниже

($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks) = stat($filename);

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

@inode = stat($filename);

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

Если при обращении к функции stato не указан параметр, то она возвращает структуру индексного дескриптора файла, чье имя содержится в специальной переменной $_.

Функция получения информации о файле при успешном выполнении в списковом контексте возвращает список значений полей структуры индексного дескриптора файла или пустой список в случае неудачного завершения. В скалярном контексте она возвращает булево значение Истина или Ложь в зависимости от результатов своего выполнения.

Для удобства использования информации о файле функция stato при успешном выполнении кэширует полученные значения полей. Если вызвать эту функцию со специальным дескриптором файла _ (символ подчеркивания), то она возвратит информацию, хранящуюся в кэше от предыдущего ее вызова. Это позволяет проверять различные атрибуты файла без повторного вызова функции stat () или сохранения результатов ее выполнения в переменных программы.

Функцию stato можно использовать для получения структуры индексного дескриптора не только файла, но и жестких ссылок на него, а также каталогов, так как они являются также файлами, блоки данных которых содержат имена файлов каталога и их числовых индексных дескрипторов. Для получения информации о символических ссылках следует использовать функцию is tat о, которая возвращает список значений полей структуры индексного дескриптора самой ссылки, а не файла, на который она ссылается. Эта функция работает аналогично функции stat (), включая использование специального дескриптора _.

Замечание

Если операционная система не поддерживает символические ссылки, то обращение к функции Istat {} заменяется обращением к функции stat ().

Кроме двух этих функций, позволяющих получать информацию о файлах системы, в Perl предусмотрен набор унарных операций, возвращающих значение только одного поля структуры индексного дескриптора. Эти операции в документации называются "операциями -х", так как их названия состоят из дефиса с последующим единственным символом. Все они являются унарными именованными операциями и имеют свой приоритет в сложных выражениях, о котором мы рассказывали в гл. 3. Полный перечень унарных операций проверки атрибутов файлов представлен в табл. 7.3.

Таблица 7.3. Унарные именованные операции проверки файлов

Операция | Проверяемый атрибут
-r | Файл может читаться эффективным uid/gid
-W | Записывать в файл может эффективный uid/gid
-х | Файл может выполняться эффективным uid/gid
-о | Владельцем файла является эффективный uid
-R | Файл может читаться действительным uid/gid
-W | Записывать в файл может действительный uid/gid
-X | Файл может выполняться действительный uid/gid
-0 | Владельцем файла является действительный uid
-е | Файл существует
-Z | Размер файла равен нулю
-S | Размер файла отличен от нуля (возвращается размер)
-f | Файл является обычным (plain) файлом
-d | Файл является каталогом
-1 | Файл является символической ссылкой
-Р | Файл является именованным программным каналом (FIFO) или проверяемый дескриптор связан с программным каналом
-S | Файл является сокетом
-b | Файл является специальным блочным файлом
™ С | Файл является специальным символьным файлом
-t | Дескриптор файла связан с терминалом
-и | У файла установлен бит setuid
-g | У файла установлен бит setgid
-k | У файла установлен бит запрета (sticky bit)
-k | У файла установлен бит запрета (sticky bit)
-т | Файл является текстовым файлом.
-b | Файл является двоичным (противоположным текстовому)
-м | Возраст файла в днях на момент выполнения программы
-А | То же для времени последнего обращения к файлу
-С | То же для времени последней модификации индексного дескриптора файла

Унарные операции применяются к строке, содержащей имя файла, к выражению, вычисляемым значением которого является имя файла, или к файловому дескриптору Perl. Если параметр операции не задан, то она тестирует файл, чье имя содержится в специальной переменной $_. Каждая операция проверки атрибута файла возвращает 1, если файл обладает соответствующим атрибутом, пустую строку "" в противном случае и неопределенное значение undef, если указанный в параметре файл не существует.

Несколько слов об алгоритме определения текстовых и двоичных файлов (операции -т и -в). Эти операции анализируют содержимое первого блока файла на наличие "странных" символов — необычных управляющих последовательностей или байтов с установленными старшими битами. Если обнаружено достаточно большое количество подобных символов (больше 30%), то файл считается двоичным, иначе текстовым. Любой файл с пустым первым блоком рассматривается как двоичный.

Если эти операции применяются к файловым дескрипторам Perl, то проверяется содержимое буфера ввода/вывода, а не первого блока файла. Обе эти операции, примененные к файловым дескрипторам, возвращают булево значение Истина, если ввязанный с дескриптором файл пуст или установлен на конец файла.

При выполнении унарных именованных операций проверки файла на самом деле неявно вызывается функция statо, причем результаты ее вычисления кэшируются, что позволяет использовать специальный файловый дескриптор _ для ускорения множественных проверок файла:

if( -s("filename") && -Т J {

^ Что-то делаем для текстовых файлов не нулевого размера.

}


Глава 1

Операции с каталогами

Как мы отмечали ранее, в UNIX каталоги являются файлами специального формата, помеченными в структурах своих индексных дескрипторов как каталоги (поле rdev). Содержимым блоков данных каталогов является множество пар, состоящих из объекта, содержащегося в каталоге, и числового значения его индексного дескриптора.

Для работы с каталогами в Perl предусмотрены функции открытия, закрытия и чтения содержимого каталога, синтаксис и семантика которых аналогичны синтаксису и семантики соответствующих операций с файлами:

opendir ДЕСКРИПТОР, ИМЯ_КАТАЛОГА; closedir ДЕСКРИПТОР; readdir ДЕСКРИПТОР;

Доступ к содержимому каталога осуществляется, как и в случае с файлом, через создаваемый функцией opendir о дескриптор каталога. Отметим, что для дескрипторов каталогов в таблице символов Perl создается собственное пространство имен. Это означает, что в программе могут существовать, совершенно не конфликтуя между собой, дескрипторы файла и каталога с одинаковыми именами:

open FF, "/usr/out.dat" # Дескриптор файла opendir FF, Vusr" # Дескриптор каталога

Замечание
Использование одинаковых имен для дескрипторов файла и каталога может запутать самого пользователя. Однако для peri такой проблемы не существует: интерпретатор всегда точно знает, какой дескриптор следует использовать.

Функция readdir о для открытого каталога в списковом контексте возвращает список имен всех файлов каталога или пустой список, если все имена уже были прочитаны. Эта же функция в скалярном контексте возвращает следующее имя файла каталога или неопределенное значение undef, если были прочитаны все имена файлов.

Функцией rewinddiro текущая позиция в каталоге устанавливается на начало, что позволяет осуществлять повторное чтение имен файлов каталога, не закрывая его. Единственным параметром этой функции является дескриптор открытого каталога.

Программа примера 7.8 проверяет, являются все файлы каталога двоичными (содержимое вложенных каталогов не проверяется).

#! peri -w

opendir FDIR, "/usr/prog"; while( $name = readdir FDIR) { next if -d $name; # Каталог

print("$name: двоичный\п") if -B $name; tt Двоичный файл } closedir FDIR;

Функция readdir о возвращает относительное имя файла. Для получения полного имени файла следует создать его в программе самостоятельно. Например, добавить имя проверяемого каталога в примере 7.8:

print("/usr/prog/$name: двоичныйХп") if -В $name; # Двоичный файл

Для создания нового каталога следует воспользоваться функцией mkdiro, параметрами которой являются имя каталога и режим доступа (восьмеричное число):

mkdir ИМЯ_КАТАЛОГА, РЕЖИМ;

Если задается не полное имя каталога, то он создается в текущем каталоге, устанавливаемом функцией chdirf). Возвращаемым значением, функции создания нового каталога mkdir о является Истина, если каталог создан, и Ложь, если произошла какая-то ошибка. В последнем случае в специальной переменной $! хранится объяснение не выполнения операции создания каталога.

Совет

Для каталогов рекомендуется задавать режим доступа равным 0777.

Удалить каталог можно функцией rmdirf) с параметром, содержащим строку с именем каталога. Если параметр не задан, то используется специальная переменная $_. Как и функция создания каталога, эта функция возвращает значение Истина в случае успешного удаления каталога и Ложь в противном случае, записывая в переменную $! объяснение возникшей ошибки.

Замечание

Функция rmdir () удаляет только пустой" каталог. Если он содержит другие пустые каталоги, их надо удалить ранее.

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


Глава 1

Вопросы и Упражнения

Вопросы для самоконтроля

1. Как можно получить доступ к файлу из программы Perl?

2. Перечислите операции, позволяющие читать содержимое файла и записывать в него информацию:

3. Какие существуют режимы открытия файла и чем они отличаются?

4. Что такое режим доступа файла и как его можно изменить в программе Perl?

5. Что такое дескриптор каталога и зачем он нужен?

6. Как можно получить имена всех файлов определённого каталога?

Упражнения

1. Найдите ошибки в программе:

#1 peri -w

$var = (stat "filel.dat")[7];

open FILE1 ">filel.dat";

print FILE1, "Длина файла: " . $var . "\n";

2. Напишите программу, которая удаляет каталог, имя которого передается через командную строку. Если сценарий запущен без параметров, то предусмотрите отображение сообщения о правильном синтаксисе его вызова. (Указание: сначала следует удалить все файлы из нижележащих каталогов, если таковые имеются.)

3. Напишите программу копирования одного файла в другой. Предусмотрите ввод имен файлов как через командную строку, так и с экрана монитора.

4. Напишите программу копирования содержимого одного каталога в другой каталог. Предусмотрите ввод имен каталогов как через командную строку, так и с экрана монитора.

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


Глава 1

Часть 8. Форматы


Глава 1

Объявление формата

Как мы помним, дословный перевод аббревиатуры языка Perl включает в себя слова "язык отчетов", т. е. язык Perl предназначен не только для извлечения и обработки информации из текстовых файлов, но и для генерирования отчетов на основе этой информации. Пока что мы для вывода информации использовали функцию print (), которая не очень-то удобна для создания отчетов — определенным образом отформатированной выходной информации. (Можно было бы воспользоваться функцией форматированного вывода printf о, но мы решили не нагружать нашего читателя изучением языковых средств, которыми он редко будет пользоваться, тем более что всегда можно обратиться к документации Perl.)

Для создания простых отчетов в Perl предусмотрены форматы, которые позволяют в тексте программы практически визуализировать внешний вид выводимого отчета, так как определение формата в Perl очень близко к тому, что отображается при выводе. Форматы позволяют задавать верхний колонтитул каждой страницы отчета, куда можно поместить название документа, номер страницы и другую полезную информацию. Perl может отслеживать количество строк на странице отчета и автоматически переходить на новую страницу по заполнению всех строк предыдущей.

Использование форматов для создания отчетов очень просто. Первое, что необходимо сделать, — определить формат и переменные, которые в нем используются. Далее следует инициализировать в программе эти переменные и осуществить форматированный вывод в файл, определенный своим дескриптором, с помощью функции write ().

Эти и другие, связанные с форматами, вопросы и являются предметом изучения в этой главе. Начнем мы с основного вопроса — объявление форматов в программе Perl.

Формат — это одна из двух языковых единиц (вторая — подпрограмма sub), которая требует обязательного объявления в программе Perl. Он используется в качестве "руководства" функцией write о, которая выводит на экран монитора, принтер или в файл информацию из программы в соответствии с записанными в формате "инструкциями" форматирования строк вывода. При объявлении формата определяется, как должна быть отформатирована каждая его строка при отображении на устройстве вывода.

Формат объявляется в программе с помощью ключевого слова format, после которого следуют "инструкции" по форматированию определенных в нем строк. Завершается объявление формата строкой, первым символом которой является точка ".". Общий синтаксис конструкции объявления формата следующий: /

format ИМЯ_ФОРМАТА •= ФОРМАТЫ_СТРОК

Параметр ИМЯ_ФОРМАТА представляет собой правильный идентификатор Perl. Он должен в точности соответствовать имени дескриптора файла, который используется в качестве единственного параметра в функции вывода write о. Например, если форматированный отчет выводится в файл, определенный в программе дескриптором FILE, то и имя формата должно быть также FILE. Функцию write о можно вызывать без параметра. В этом случае вывод осуществляется на стандартное устройство вывода (STDOUT), и имя формата в этом случае должно быть равным STDOUT. Если функцией select о установлен дескриптор файла вывода по умолчанию, то вывод функцией write о без параметра будет осуществляться в этот файл, причем имя формата вывода должно быть изменено на имя дескриптора файла.

Замечание

Так как операция format не является вычисляемой операцией Perl, то объявление формата может осуществляться в любом месте программы. Обычно все объявления форматов задают либо в начале, либо в конце программы.

Замечание

Имя формата, как отмечалось, может быть любым правильным идентификатором Perl, однако обычно его определяют прописными буквами, что способствует лучшей читаемости программы.

В теле формата (до завершающей строки с точкой) определяются форматы для каждой строки вывода. Формат строки состоит из двух строк: первая, называемая строкой шаблонов, определяет, как отображается информация, вторая, называемая строкой переменных, задает переменные, содержащие выводимую информацию. Вместе эти две строки определяют формат и содержимое одной строки вывода функцией write о.

Строка шаблонов печатается точно так, как она выглядит в тексте программы (включая пробельные символы), за исключением некоторых полей, в которые подставляются значения переменных из строки переменных. Эти поля (иногда их называют шаблоны, что и дало название соответствующей строке формата) начинаются с символа "в" или " ^ ", за которым следуют символы форматирования (табл. 8.1), определяющие ширину поля вывода значения переменной в символах и выравнивание выводимого значения внутри поля. Количество символов форматирования определяет ширину поля вывода, причем для одного поля все символы должны быть одинакового типа.

Переменные, определяющие значения для полей строки шаблонов, задаются через запятую в строке переменных. Порядок их задания соответствует порядку задания полей вывода в строке шаблонов: значение первой переменной выводится в первое поле, второй — во второе и т. д. Все переменные в строке переменных вычисляются в списковом контексте. Это позволяет задавать выводимые значения в элементах массива скаляров.

Замечание

Если строка шаблонов не содержит полей, то для нее не надо задавать строку переменных. Она отображается в точности так, как она задана в формате.

Таблица 8.1. Символы форматирования

Символ | Описание
> | Определяет символьное поле, в котором выводимое значение выровнено по правому краю
< | Определяет символьное поле, в котором выводимое значение выровнено по левому краю
# | Определяет числовое поле (выводимое значение должно быть числом)
. | Определяет положение десятичной точки в числовом поле (###.##)
I | Определяет символьное поле, в котором выводимое значение выровнено центру

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

В.Долженков Ю.Колесников:Excel 2000:BHV:1999:90

Нам необходимо распечатать отчет о всех продаваемых книгах. Воспользуемся форматами Perl. Программа примера 8.1 реализует поставленную задачу.

#! peri -w . "

open BOOKS, "<books"; # Открытие файла на чтение

while (<BOOKS>) {

{$author, $title, $pub, $year, $price) = split(':'); t Разбиение строки по символу ':'

write; # Форматный вывод строки

} '.....

format STDOUT =

@<«««««««««««« | е»»»»>» i @i 111111 | @#### | @###.##р.

$author, $title, $pub, $year, $price

Результат отображения отчета на экране монитора выглядит следующим образом:

В.Долженков Ю.Колесников |, Excel 2000 | BHV | 1999 | 90.00р.

А.Матросов А.Сергеев М.Чау | HTML 4.0 I BHV | 1999 | 70.00р.

Т.Кристиансен Н.Торкингтон | Perl | Питер | 2000 | 100.00р.

Обратите внимание, что все символы строки шаблонов печатаются именно в тех позициях, в которых они заданы, а в поля этой же строки, определенные символом "@", подставлены значения соответствующих переменных. Эти значения отображаются в соответствии с заданными символами форматирования: для переменной $authqr вывод выровнен по левому краю (<), для $ title по правому краю (>), для $pub пр центру (|) соответствующего поля. Значения переменных $уеаг и $price форматируются в соответствии с числовым форматом. Если бы эти переменные не содержали числовые значения, то интерпретатор peri вывел бы предупреждающее сообщение.

Замечание

Символ начала поля в строке шаблонов ("@" или " ^ ") учитывается при подсчете ширины поля вывода.

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

Как поступать в таких случаях? Можно увеличить ширину поля, если позволяют параметры выводного устройства, а можно воспользоваться еще одним символом форматирования, который как раз и предназначен для решения подобных проблем. Прежде всего следует для задания начала поля использовать символ " ^ ". Его отличие от символа "е" заключается в том, что до начала вывода строковых данных в поле Perl в промежуточном буфере аккумулирует слова из выводимых данных, формируя строку, длина которой не превышает ширину поля. После этого сформированные данные выводятся в строке, а значение переменной вывода модифицируется: она будет содержать оставшиеся слова. При последующем использовании этой переменной в другой строке переменных того же формата будут выводиться сохраненные в ней отсеченные данные. Это позволяет выводить длинные данные в нескольких строках в одном вертикальном блоке, если задавать для вывода оставшихся данных точно такое же поле, что и при выводе первой порции.

Заменим формат STDOUT программы примера 8.1 на следующий:

format STDOUT =

-<«««««««««««« | @»»»»>» | @||||||| | @##it f @###.##р.

$author, $title, $pub, $year, $price

Л ««««««««««<«« | | I | ~

$author

Теперь вывод нашей программы будет выглядеть так:

В.Долженков Ю.Колесников I Excel 2000 | BHV | 1999 | 90.00р.

А.Матросов А.Сергеев | ' HTML 4.О Г BHV | 1999 | 70.00р.

М.Чаунин I I II

Т.Кристиансен Н.Торкингтон I Perl | Питер | 20.00 | 100.00р.

Символ тильда "~" в конце строки Шаблона подавляет вывод пустых строк. Если не поставить его, то между первой и второй книгой в нашем отчете появится дополнительная строка, как если бы была выведена вторая строка шаблона с пустым значением переменной $author. Символ подавления вывода пустых строк можно задавать в любом месте строки шаблона, помня, что при выводе он отображается, как пробел.

В нашем примере мы знали, что данные в переменной $author He займут более двух строк при выводе. Поэтому в формате мы использовали эту информацию, добавив еще одну строку шаблона с переменной $author. А что делать, если не известно количество строк продолжения в которых будут выводиться данные? Можно воспользоваться двумя идущими подряд символами тильда вместо одного. В этщ случае алгоритм буферизации данных по словам будет продолжаться до завершения вывода всех данных переменной. Если наш формат изменить на следующий

format STDOUT = . _....,..,... . ; ,

Л <«««««««««««« | @»»»»>» ,| @| | | | || | | в#### I @###.-##р.

$author, $title, $pub, $year, $price

$author

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

А.Матросов А.Сергеев | HTML 4.0 I BHV | 1999 | 70.00р.

М.Чаунин В.Долженков I III

Ю.Колесников I III

Таким способом можно организовать вывод длинных данных в вертикальные текстовые блоки с переносом по словам.

Немного отвлечемся от создания отчета по книгам, чтобы познакомить читателя с еще одним символом форматирования — символом "*", который позволяет выводить длинные строковые данные в нескольких строках, длина которых равна максимальной ширине вывода устройства отображения (экрана монитора или принтера). Например, если переменная $ record содержит строковые данные длиной более 80 символов и вывод осуществляется на экран монитора, то следующий фрагмент кода Perl

write;

format STDOUT =

$*

$record

отобразит на экране ее содержимое следующим образом:

В.Долженков Ю.Колесников:Excel 2000:BHV:1999:90:Книга является справочным пособием по MS Excel 2000. В ней рассматриваются следующие основные темы - настройка интерфейса и его основные элементы.

Замечание

Используемый в нашей книге шрифт для представления вывода сценария Perl не позволяет нам отобразить истинный вывод в соответствии с заданным форматом, так как внимательный читатель обнаружит, что длина строки вывода в нашем представлении составляет 73 символа, а не 80, как при выводе на экран монитора.

Вернемся к разработке формата для вывода нашего отчета. Пока что отчет был достаточно маленьким и помещался ha одной странице. Реальные отчеты, конечно, на одной странице не поместятся. Наша программа напечатает и несколько страниц отчета. Дело в том, что создание отчетов в Perl предполагает их вывод на принтер, а поэтому после вывода определенного количества строк оператором write () Perl автоматически выведет символ перехода на новую страницу и печать продолжится на следующей странице. По умолчанию количество строк на странице установлено равным 60. Эта величина хранится в специальной переменной $=, значение которой может быть изменено в любое время.

Итак, мы теперь знаем, что переход на новую страницу происходит автоматически, но нам хотелось бы, чтобы на каждой странице печатался верхний колонтитул, в котором отображалось бы наименование отчета и печатались номера страниц. И это возможно в Perl. Следует только задать формат со специальным именем, добавив к имени формата, по которому мы выводим информацию (в нашей программе STDOUT), суффикс _ТОР. Этот формат будет выводиться каждый раз, как начинается печать новой страницы.

Добавим в программу примера 8.1 следующее объявление формата

format STDOUTJTOP =

Книги на складе @>»>»

"стр. ".$% Автор Название Издатель Год Цена

и явно зададим количество строк на странице, добавив перед циклом while оператор

$= = б;

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

Книги на складе стр. 1 Автор - Название Издатель Год Цена

В.Долженков Ю.Колесников | Excel 2000 I BHV | 1999 | 90.00р. А.Матросов А.Сергеев | HTML 4.0 I BHV | 1999 | 70.00р. М.Чаунин ,| | II

———разрыв страницы———

Книги на складе стр. 2 Автор Название Издатель Год Цена

Т.Кристиансен Н.Торкингтон | Perl I Питер | 2000 | 100.00р.

Вернемся к объявлению формата для колонтитула. Во-первых, при его задании мы использовали выражение "стр. ".$% в строке переменных. Действительно, хотя формат и не вычисляется, но во время выполнения программы вычисляются значения переменных и все выражения строки переменных

формата. Во-вторых, мы использовали специальную переменную $%, которая хранит текущий номер выводимой страницы. Это позволило нам в колонтитуле напечатать номера страниц.


Глава 1

Использование нескольких форматов

Как мы уже знаем, форматы Perl позволяют без каких-либо усилий создавать верхние колонтитулы — следует только объявить формат с суффиксом _ТОР. Для создания полноценного документа не мешало бы еще иметь возможность создавать нижние колонтитулы страницы и печатать, например, в конце заказа общую стоимость. К сожалению, такой возможности Perl не предоставляет, но он позволяет переключать вывод с одного формата на другой и в специальной переменной хранит строку, которую печатает перед переходом на новую страницу. А это и позволит нам создать и напечатать и нижний колонтитул, и общую стоимость заказа.

Но прежде мы еще немного поговорим о специальных переменных Perl, которые используются для управления форматом. В переменной $~ хранится имя формата, который используется при выводе функцией write о без параметра:

write; # Эквивалентно оператору write STDOUT;

По умолчанию в ней хранится имя формата STDOUT, но и вывод функцией write о без параметра происходит на стандартное устройство вывода STDOUT. (Мы помним, что имя формата должно совпадать с именем дескриптора файла в вызове функции write о, а именно такая ситуация по умолчанию и реализуется.) Если мы изменим значение переменной $~ на имя другого формата, то вывод в стандартный файл функцией write о без параметра будет осуществляться в соответствии с указанным форматом, который, конечно, должен быть объявлен в программе. Например, следующий оператор write выводит на стандартное устройство вывода в соответствии с форматом NEW:

$- = NEW; write/format NEW =

Таким образом, меняя значение переменной $~, можно переключать вывод с одного формата на другой. Этим другим форматом как раз и может быть формат общей стоимости заказа.

Пусть в файле books содержится информация о заказанных книжным магазином книг. В конце отчета по заказу нам теперь необходимо напечатать общую стоимость книг. Решение показано в программе примера 8.2.

#! peri -w

open BOOKS, "<books"; # Открытие файла на чтение

$number = 1;

$total = 0;

while (<BOOKS>) {

($author, $title, $pub, $year, $price) = split(':'); # Разбиение строки

t по символу ':'

write; # Форматный вывод строки $total += $price; t Подсчет общей суммы

}

$~ = TOTAL; # Переключение формата

write; # Вывод по формату итоговой строки

format STDOUTJTOP =

Заказ № @#

$ number Автор Название Издатель Год Цена

format STDOUT = '

Л <«««««««««««« | @»»»»>» | @М!11М I @t### I @###.##р.

$author, $title, $pub, $year, $price

A «««««««««<««« | | | | ~~

$author

format TOTAL =

Итого: @###.##р. $total

В этой программе после форматной печати содержимого файла books осуществляется переключение на другой формат, по которому выводится строка с общей суммой заказа, подсчитанной в переменной $totai. Полученный с помощью этой программы заказ показан ниже

Заказ № I Автор Название Издатель Год Цена

В.Долженков Ю.Колесников I Excel 2000 I BHV | 1999 I 90.00р.

А.Матросов А.Сергеев I HTML 4.0 I BHV | 1999 | 70.00р.

М.Чаунин I I II

Т.Кристиансен Н.Торкингтон I Perl I Питер | 2000 | 100.00р.

Итого: 260.00р.

В завершение разговора о создании отчетов в Perl мы модифицируем программу примера 8.1, приспособив ее для печати отчета на основании информации о книгах из файла books, в котором в записи о книгах добавлено еще одно поле, содержащее краткую аннотацию книги:

В.Долженков Ю.Колесников:Excel 2000:BHV:1999:90:Аннотация книги

Отчет, формируемый этой программой (пример 8.3), также печатает нижний колонтитул на каждой странице. Для этого мы воспользуемся специальной переменной $ л ь, содержимое которой Perl печатает перед переходом на новую страницу во время форматного вывода. При этом следует уменьшить на количество строк, заданных в этой переменной, количество строк на странице, хранящееся в специальной переменной $=, иначе строки из переменной $ л ь попадут не в конец текущей страницы, а будут напечатаны на следующей странице, не создав никакого нижнего колонтитула.

#! peri -w

open FILE, "<books" or die $!;

open REPORT, "xreport" or die $!;

select REPORT; $~ •= STDOUT; $= = 24;

Sltime = localtime;

$-L = •("=" x 73) . "\n". "Книготорговая база \"БЕСЫ\"". (" " x 24)."$ltime\n\f";

$count = 0;

while(<FILE>) { .

($author, $title, $pub, $year, $price, $annot) = split(':');

$count++;

write ;

}

close(REPORT); format STDOUT_TOP =

Книги на складе @»»>»

; "стр. ".$%

Автор Название Издатель Год Цена Аннотация ________

format STDOUT =

@| Л «««««« /ч «««««« @<««« @### @##.##р. Л «««««««««

$count.".", $author, $title, $pub, $year, $price, $annot

Л «««««« л «««««« А «««««««««

$author, $title, $annot

Вывод отчета осуществляется в файл с именем report. Обратите внимание на задание переменной $ /4 L. В ней используется переменная $itime, в которой хранится текущая дата, полученная обращением к функции localtime. Одна страница отчета будет выглядеть следующим образом:

Книги на складе . стр. t Автор _______ Название ____ Издатель Год Цена Аннотация ________

1. В.Долженков Excel 2000 BHV 1999 90.00р. Книга является

Ю.Колесников справочным пособием

по MS Excel 2000. В ней рассматриваются следующие основные темы - настройка интерфейса и его основные элементы.

2. А.Матросов HTML 4.0 BHV 1999 70.00р. Представлен весь А.Сергеев спектр технологий М.Чаунин создания Web-документов (начиная от простейших -

статических - и до документов на основе

динамического HTML), включая форматирование текста, создание списков. Книготорговая база "БЕСЫ" Sat Mar 18 19:01:37 2000

Замечание

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

Отчеты в Perl создаются с помощью форматов, определяющих внешний вид строк вывода и содержащуюся в них информацию. Печатаются отчеты функцией write ( ), которая осуществляет вывод как на стандартное устройство вывода, так и в файл, открытый в программе. В процессе печати отчета можно переключаться между существующими форматами.


Глава 1

Вопросы и Упражнения

Вопросы для самоконтроля

1. Опишите процедуру создания отчетов в Perl.

2. Какой синтаксис имеет оператор format?

3. Какая функция используется для активизации форматного вывода?

4. Как создается верхний колонтитул для страниц отчета?

5. Как создается нижний колонтитул для страниц отчета?

6. Каким образом осуществляется переключение между форматами?

7. Перечислите специальные переменные Perl, которые используются для управления форматным выводом.


Глава 1

Часть 9. Ссылки


Глава 1

Виды ссылок

Данные, используемые программой, размещаются в оперативной памяти компьютера. Каждая переменная имеет свой адрес и свое значение, которое хранится по этому адресу. Адрес переменной является информацией, которую также можно использовать в программе.

Ссылка на некоторую переменную содержит адрес этой переменной в оперативной памяти. Говорят, что ссылка указывает на переменную. Ссылки широко используются в современных языках программирования, таких как Pascal, C/C++. Вместо слова "ссылка" для обозначения термина может применяться слово "указатель". Основной областью применения ссылок является создание сложных структур данных, способных изменяться во время выполнения программы. Для ссылок используются специальные обозначения. В языке С это символ "*" перед именем переменной. В языке Pascal существует специальный тип данных для описания ссылок-переменных. Признаком этого типа является символ "^ " перед идентификатором, описывающим базовый тип данных. Ссылка может быть переменной или константой.

Ссылка в языке Perl — это обычная скалярная величина, в которой хранится некоторый адрес в оперативной памяти.

Ссылка в языке Perl может указывать на любой фрагмент данных. Фрагментом данных здесь мы называем любую переменную, константу или часть кода программы. Тип ссылки определяется типом данных, на которые она указывает. Таким образом, существуют следующие типы ссылок: ссылка на скалярную величину, ссылка на массив, ссылка на хеш, ссылка на функцию. Нельзя использовать ссылку одного типа там, где контекст выражения требует присутствия ссылки другого типа, например, использовать ссылку на массив вместо ссылки на хеш-массив. Поскольку сама ссылка является скалярной величиной, то, естественно, существует ссылка на ссылку. Имеется еще один вид ссылок, который мы в свое время рассмотрим подробнее. Это ссылки на данные типа typegiob. Тип typeglob является внутренним типом языка Perl, который служит для обозначения переменных разных типов, имеющих общее имя. Принадлежность к типу typeglob, обозначается префиксом "*". Например, запись *abc обозначает всю совокупность, а также любую из следующих переменных: скаляр $abc, массив @аЬс, хеш %abc. В данной главе мы не будем рассматривать этот вид ссылок. Отметим только, что он лежит в основе механизма экспорта/импорта модулей.

(Работа с модулями обсуждается в части 12.)

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

REF ссылка на ссылку;

SCALAR ссылка на скаляр;

ARRAY ссылка на массив;

HASH ссылка на ассоциативный массив;

CODE ссылка на подпрограмму;

GLOB ссылка на переменную типа typegiob.

Ссылки Е языке Perl бывают жесткие и символические. Понятия "жесткая ссылка" и "символическая ссылка" вместе с названиями проникли в Perl из мира UNIX, где они используются применительно к файловой системе. Разберем их применение в UNIX, чтобы лучше понимать, для чего они нужны в Perl.

Каждому файлу в UNIX соответствует индексный дескриптор - структура данных, имеющая определенный формат, расположенная в специально отведенной области диска и содержащая важнейшую информацию о файле: тип файла, его расположение на диске, права доступа и т. д. Каждый дескриптор имеет числовой номер, соответствующий его положению в таблице индексных дескрипторов. Этот номер и является внутренним именем файла для операционной системы. Для нее сущность файла заключается в его индексном дескрипторе, а не в его содержимом. Для пользователя, напротив, важно содержимое файла, а о существовании индексного дескриптора он может даже не подозревать. Кроме того, пользователю удобнее работать с именем файла, а не с числовым номером. Для удобства пользователя создается ссылка на файл, которая ставит в соответствие индексному дескриптору имя файла. Ссылка представляет собой запись в каталоге, который является тоже файлом, выполняющим специальную функцию регистрации других файлов. В простейшем случае эта запись содержит два поля: имя файла и номер индексного дескриптора. Можно создать несколько ссылок с разными именами в одном или нескольких каталогах, указывающих на один файл. Ссылка указывает на индексный дескриптор, но для краткости говорят о ссылке на файл. Следует подчеркнуть, что все ссылки равноправны. Ссылка, созданная первой, не имеет никакого преимущества перед ссылками, созданными позднее. В индексном дескрипторе среди другой важной информации содержится счетчик ссылок. Удаление из каталога ссылки на файл уменьшает значение счетчика ссылок на единицу. Когда значение счетчика ссылок становится равным нулю, файл удаляется, а его индексный дескриптор освобождается для использования новым файлом.

Рассмотренные ссылки называются жесткими ссылками. Кроме них, существуют символические ссылки. Символическая ссылка является не просто записью в каталоге, а файлом особого типа, содержащим символьное имя другого файла. В качестве файла символьная ссылка имеет свой индексный дескриптор. Поскольку символическая ссылка является самостоятельным файлом, ее существование никак не отмечается в дескрипторе того файла, на который она указывает. В частности, если удалить все символические ссылки на файл, то это не будет означать его удаление.

Жесткие и символические ссылки в языке Perl напоминают одноименные понятия в файловой системе UNIX. Жесткая ссылка в Perl — это скалярная величина, которая содержит адрес некоторой области памяти, являющейся носителем данных. Сами данные будем называть субъектом ссылки. Символическая ссылка — это скалярная переменная, которая содержит имя другой скалярной переменной. "Истинной" ссылкой является жесткая ссылка. Она создается одним из способов, перечисленных в разделе 9.1. Внутренняя организация жестких ссылок такова, что для каждого субъекта ссылки поддерживается счетчик ссылок. Область памяти, занимаемая субъектом ссылки, освобождается, когда значение счетчика ссылок становится равным нулю. В большинстве случаев мы имеем дело с жесткими ссылками, а использование символических ссылок будем специально оговаривать.

Главным применением ссылок в языке Perl является создание сложных структур данных. Мы знаем, что основными типами данных в Perl являются скаляры, массивы и хеш-массивы. Многомерные массивы или более сложные структуры данных, аналогичные записям языка Pascal или структурам языка С, в языке Perl отсутствуют. В более ранних версиях языка отсутствовала и возможность создания сложных структур данных на основе имеющихся типов. Такая возможность появилась в версии Perl 5.0 вместе с появлением ссылок. В практике программирования часто встречаются данные, которые удобно представлять, например, в виде двумерных массивов, реже трехмерных массивов или других подобных структур. Двумерный массив можно рассматривать как одномерный массив, элементами которого являются также одномерные массивы. Возможность такого представления есть во многих языках программирования. В языке Perl невозможно создать массив с массивами в качестве элементов. То же самое относится и к хешам. Элементом массива или хеша может быть только скалярная величина. Поскольку ссылка является скалярной величиной, можно создать массив или хеш, элементами которого являются ссылки на массивы или хеши, и таким образом получить структуру, которую можно использовать как массив маесивов (соответственно массив хешей, хеш массивов, хеш хешей). Благодаря ссылкам можно на основе массивов и хешей конструировать структуры данных произвольной сложности.

Помимо создания сложных структур данных, ссылки активно применяются для работы с объектами. Слово "объект" здесь обозначает основное понятие объектно-ориентированного подхода к программированию. (Объекты рассматриваются в части 13.)

В этой главе мы рассмотрим основное применение ссылок как средства для конструирования структур данных. Другие применения будут рассмотрены в соответствующих главах.


Глава 1

Создание ссылок

Существует несколько способов порождения ссылок. Рассмотрим их в порядке следования от чаще употребляемых синтаксических конструкций к более редким.

Операция ссылки "\"

Операция "\", примененная к единственному аргументу, создает ссылку на этот аргумент. В качестве последнего может выступать переменная любого типа или константа. Примеры:

$а=\5;

$scal_ref=$a; $arr_ref=\@myarray; $hash_ref=\%myhash; $funC_ref=\ anyfunc ;

В данном примере скалярной переменной $а присваивается значение ссылки на константу 5, т. е. адрес ячейки памяти, в которой хранится число 5. Адрес самой переменной $а хранится в переменной $scai_ref. Переменные $arr_ref, $hash_ref, $func_ref хранят адреса ячеек памяти, являющихся начальными точками размещения соответственно массива gmyarray, хеш-массива %myhash и кода функции myfunc. К переменным, содержащим ссылки, можно применять все операции допустимые для скалярных величин. Их можно присваивать другим переменным, складывать, умножать, делить, выводить на экран и т. д. За исключением присваивания применение подобных операций к ссылкам, как правило, смысла не имеет. Например, вывод рассмотренных выше переменных

print $scal_ref, "\n", $arr_ref,"\n", $hash_ref, "\n", $func_ref, "\n"; будет состоять иа строк, подобных следующим:

SCALAR(Ox9b8994) ARRAY(Ох9Ь8а18) HASH(Ox9b8a60) CODE(Ox9b3dl4)

Здесь каждая строка содержит слово, обозначающее тип ссылки и ее значение — адрес в виде шестнадцатеричного числа.

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

Конструктор анонимного массива

В рассмотренном выше примере операция "\" применялась к переменным, обладающим именами. Perl позволяет создавать ссылки на анонимные массивы при помощи специальной конструкции, использующей квадратные скобки:

$arr_ref = [1,2,3];

В результате данной операции присваивания будет создан анонимный массив с элементами (1,2,3), а переменной $arr_ref будет присвоено значение ссылки на этот массив.

Компилятор различает случаи использования квадратных скобок для создания ссылки на анонимный массив и для обращения к отдельным элементам массива, как, например, в операции присваивания $а = $myarray[2].

Замечание

Свободный синтаксис языка Perl допускает существование конструкций, смысл которых не очевиден. К рассматриваемой теме имеет отношение следующий пример. Формально выражение \($а, $Ь, $с) представляет собой анонимный массив из трех элементов ($а, $ь, $с), к которому применяется операция ссылки "\". Означает ли это, что значением выражения является ссылка на анонимный массив? Нет, это просто сокращенная запись массива, состоящего из трех элементов-ссылок (\$а, \$Ь, \$с), а для создания ссылки на анонимный массив существует единственный способ, рассмотренный выше.

Конструктор анонимного ассоциативного массива

По аналогии с массивами можно создавать ссылки на анонимные ассоциативные массивы, используя фигурные скобки. Операция присваивания

%hash_ref = {

'Опе'=>1,

'Two'=>2,

"Three'=>3 };

создаст анонимный хеш-массив cone'=>i, 'Two'=>2, "rhree'=>3) и присвоит переменной %hash_ref значение ссылки на этот хеш.

Фигурные скобки используются во многих конструкциях, например, для обращения к индивидуальному элементу хеш-массива

$а = $myhash{"first"}

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

(Желающие могут предварительно прочитать начало части 11, в которой рассказывается о подпрограммах и функциях.)

Предположим, что необходимо определить функцию, которая создает анонимный хеш и возвращает ссылку на него. Возвращаемое значение можно задать при помощи встроенной функции return. Если конструкция return отсутствует, то в качестве возвращаемого значения по умолчанию принимается значение последнего выражения, вычисленного внутри функции. Таким образом, синтаксически допустимо следующее определение функции

sub get_hash_ref { {@_} }

В данном примере внутренняя конструкция в фигурных скобках интерпретируется как блок. Для того чтобы она интерпретировалась как ссылка на анонимный хеш, необходимо использовать функцию return или поставить перед внутренней конструкцией знак "+":

sub get_hash_ref { return {(?_} } sub get_hash_ref { +{@_} }

Другие способы

В предыдущих разделах рассмотрены основные способы создания ссылок: . О применение операции " \ " к объекту ссылки;

  • специальные конструкции [ ] и { }, создающие в определенном контексте ссылку соответственно на анонимный массив и анонимный ассоциативный массив.

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

Конструктор анонимной подпрограммы

Мы уже использовали в примерах подпрограммы, не дожидаясь их систематического изучения. Поэтому можем рассмотреть в этой главе такой вид ссылки, как ссылка на анонимную подпрограмму.

Ссылка на анонимную подпрограмму может быть создана при помощи ключевого слова sub, за которым следует блок — последовательность операторов, заключенная в фигурные скобки:

$sub_ref = sub { print "Привет!\n");

В результате операции присваивания в переменную $sub_ref заносится адрес, по которому размещается код анонимной подпрограммы. В данном примере подпрограмма состоит из единственного обращения к функции print, выводящей строку "Привет!".

Пример, иллюстрирующий данный вид ссылки, будет рассмотрен далее в этой главе.

Ссылка, создаваемая конструктором объекта

В версию 5.0 языка Perl была добавлена поддержка объектно-ориентированного программирования. Основой объектно-ориентированного подхода являются понятия класс и объект.

(Классы и объекты рассматриваются в части 13.)

Понятие "объект" реализовано в языке Perl таким образом, что объект становится доступным в программе только через ссылку на него. Для создания объекта используется специальная подпрограмма — конструктор, которая, в свою очередь, применяет для этого встроенную функцию bless о. Конструктор возвращает ссылку на объект. Таким образом, это еще один способ порождения ссылок, без которого не обойтись тем, кто использует объектно-ориентированный подход в Perl.

Ссылки на данные типа typeglob

Компилятор Perl хранит имена всех переменных программы в таблице символов. Отдельная таблица символов существует для каждого пакета, образуя собственное пространство имен.

(О пакетах и таблицах символов рассказывается в части 12.)

Каждый идентификатор, встречающийся в пакете, заносится в таблицу символов. Одинаковые идентификаторы, соответствующие переменным разных типов, образуют гнездо, в котором каждому типу соответствует свой элемент, содержащий адрес переменной данного типа. Если, например, в программе имеются следующие строки

$а=5;

@а=(1,2,3,4,5);

%а=("опе"=>1,"two"=>2,"three"=>3);

sub a {return "Hello, Mike!";};

то таблица символов содержит гнездо для идентификатора "а", состоящее из четырех элементов, хранящих адреса: скалярной переменной $а, массива @а, ассоциативного массива %а и кода подпрограммы &а.

В языке Perl существует внутренний тип данных typeglob. Признаком этого типа является наличие префикса "*" в имени переменной. Тип typeglob служит для ссылки на все переменные разных типов с одинаковыми именами. Например, переменная *а обозначает ссылку на гнездо "а" в таблице символов. Используя специальную запись, можно при помощи переменной typeglob получить ссылки на отдельные элементы гнезда: $scalarref = *a{SCALAR}; # эквивалентно $scalarref = \$a; $arrayref = *a{ARRAY}; # эквивалентно $arrayref = \@а; $hashref = *a{HASH}; # эквивалентно $hashref = \%a; $coderef = *a{CODE}; # эквивалентно $coderef = \&a; $globref = *a{GLOB}; # эквивалентно $globref = \*a;

Неявное создание ссылок

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


Глава 1

Разыменование ссылок

Разыменованием ссылки называется получение объекта, на который указывает эта ссылка. Для разыменования, как и для создания ссылки, применяются различные синтаксические конструкции, подчас достаточно сложные для визуального восприятия. К ним нужно привыкнуть. Вид конструкции зависит от типа ссылки, к которой применяется разыменование. Рассмотрим их по степени возрастания сложности.

Разыменование простой скалярной переменной

Если ссылка на некоторый объект: скалярную переменную, массив, ассоциативный массив и т. д., является простой скалярной переменной без индексов, то для обращения к самому объекту применяется правило: вместо имени объекта подставить в выражение простую скалярную переменную, содержащую ссылку. Например:

1 $а = $$scal_ref;

2 @b = @$arr_ref;

3 %с = %$hash_ref;

4 &f - &$code_ref;

5 $$d[0] = 7;

6 $$h{"one"} = 1;

Здесь предполагается, что переменная $scal_ref содержит ссылку на скалярную величину, $arr_ref - ссылку на массив, $hash_ref - ссылку на ассоциативный массив, $code_ref — ссылку на подпрограмму.

Рассмотрим подробно пятую строку.

Во-первых, следует определить, что является ссылкой: скалярная переменная $d, указывающая на анонимный массив, или элемент $d[0] массива @d. Ответ содержится в сформулированном выше правиле разыменования. Поскольку в строке 5 применяется именно оно, то индексированная переменная $d[0] ссылкой быть не может. Ссылкой является простая скалярная переменная $d, которая используется в качестве имени. Из контекста видно, что на ее месте должно стоять имя массива, следовательно, $d является ссылкой на анонимный массив

Во-вторых, здесь мы имеем пример неявного создания ссылки, о котором говорилось в предыдущем разделе. Ссылка $d не была ранее создана явным образом, но ее существование предполагается в операции присваивания. Поэтому компилятор создаст ссылку $d на анонимный массив, поместит в нее адрес массива и по этому адресу сохранит значение первого элемента, равное 7.

Все сказанное можно отнести к шестой строке с единственным отличием: вместо ссылки на анонимный массив здесь фигурирует ссылка $ь на анонимный хеш-массив.

Блоки в операциях разыменования ссылок

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

${$d[0]} = 7; ${$h{"one"}} = 1; ${&f()}[l] = 3;

Разберем первую строку. Начальный символ $ является признаком скалярной переменной, за которым должно следовать ее имя. Вместо имени используется блок, следовательно, выражение внутри блока интерретируется как ссылка. В данном случае осуществляется разыменование ссылки $d[0], являющейся элементом массива @а. Аналогично, во второй строке осуществляется обращение к скалярной переменной, на которую указывает ссылка $h{"one"}, являющаяся элементом ассоциативного массива %ь. В третьей строке блок, возвращающий ссылку, состоит из одного обращения к функции f (). Ее значение интерпретируется как ссылка на массив, и второму элементу этого массива присваивается значение 3.

Операция разыменования "->"

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

$<$а[0]'}[1] = 17; , ${$Ь[0]}{"опе"} = 1;

В первой строке осуществляется обращение к отдельному элементу массива массивов, во второй — к отдельному элементу массива хеш-массивов.

Замечание

В действительности речь идет соответственно о массиве, элементами которого являются ссылки на анонимные массивы и о массиве, элементами которого являются ссылки на анонимные хеш-массивы. Но для краткости в подобных случаях мы будем употреблять сочетания "массив массивов", "массив хеш-массивов" и т. д.

Несколько упростить запись и улучшить наглядность можно, используя операцию "->" ("стрелка").

Аргумент слева от стрелки может быть любым выражением, возвращающим ссылку на массив или хеш-массив.

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

Если левосторонний аргумент является ссылкой на хеш-массив, то аргумент справа от стрелки — значение ключа, помещенное в фигурные скобки и определяющее элемент этого хеш-массива.

Результатом операции "->" является соответственно значение элемента массива или хеш-массива. Предыдущий пример можно более компактно записать в виде

$а[0]->[1] = 17; $Ь[0]->{"опе"} = 1;

Конструкция $а[0]->[1] обозначает второй элемент массива, определяемого ссылкой $а[0]. Конструкция $ь[0]->{"опе"} обозначает элемент, соответствующий ключу "one" хеш-массива, задаваемого ссылкой $Ь[0].

Вообще, если $arr_ref — ссылка на массив, то $arr_ref->[$i] обозначает i-й элемент этого массива. Если $hash_ref — ссылка на хеш-массив, то $hash_ref->{"key"} обозначает элемент этого хеш-массива, соответствующий ключу "key".

Если бы в последнем примере вместо именованных массивов @а и @ь использовались ссылки на массив, например, $ref_a и $ref_b, то соответствующие операции присваивания имели вид

$ref_a->[0]->[l] = 17; $ref_b->[0]->{"one"} = 1;

Здесь мы снова сталкиваемся с неявным созданием ссылок. По контексту элемент массива $ref_a->[0] должен быть ссылкой на массив, а $ref_b->[0] — ссылкой на хеш-массив. Обе ссылки ранее не были определены, но их существование предполагается в контексте выражения. Данные ссылки будут созданы автоматически.

Операция "->" позволяет для обращения к отдельному элементу составного массива или хеш-массива использовать более простые выражения, например,

$a[$i]->[$j]->[$k] вместо ${${$a[$i]}[$j]}[$k], $b[$i]->{"key"}->[$j] вместо ${${$b[0]}{"key"}}[$j]

и т. д.

Дальнейшее упрощение связано с тем, что при обращении к элементам гожных структур, представляющих собой комбинации вложенных массивов

и хеш-массивов, можно опустить символы "->" между квадратными и/или фигурными скобками, содержащими индексы или ключи элементов. Предыдущие выражения примут еще более простой вид: $a[$i][$j][.$k] и $b[$i] {"key"} [$j]соответственно.


Глава 1

Символические ссылки

Из предыдущего раздела мы знаем, что если ссылка не определена, но ее присутствие требуется контекстом, то она создается автоматически. Если же определенная ранее скалярная величина не является ссылкой, но используется в качестве ссылки, то ее называют символической ссылкой. Значение символической ссылки интерпретируется как имя некоторой переменной. Над этой переменной будут выполняться все операции, применяемые к символической ссылке. Вспомним, что значением жесткой ссылки является адрес. В следующем примере переменная $name_a используется как символическая ссылка на переменную $а.

1 $name_a = "a";

2 $$name_a = 17;

3 @$name_a = (1,2,3);

4 $name_a->[3] = 4;

5 %$name_a = ("one"=>l, "two"=>2, "three"=>3);

6 &$name_a () ;

В строке 2 переменной $а присваивается значение 17. В строке 3 определяется и инициализируется массив @а с элементами (1,2,3). В строке 4 к массиву @а добавляется четвертый элемент со значением 4. В строке 5 инициализируется хеш-массив %а. В строке 6 осуществляется вызов функции а о (предположим, что такая функция существует).

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

(О пакетах и таблицах символов описано в части 12)

Лексические переменные, определяемые при помощи функции ту (), в таблицу символов не входят, поэтому их имена невидимы для механизма, реализующего символические ссылки.

(О лексических переменных и применении функции ту () рассказывается в части 11.) Для иллюстрации рассмотрим следующий пример:

$name_a="a"; { my $a="Hello!";

print $$name_a; };

Здесь переменная $name_a используется в качестве символической ссылки на переменную $а, и можно предположить, что результатом выполнения этой последовательности будет вывод строки "Hello!". В действительности переменная $а является невидимой для символической ссылки, поскольку она определена как лексическая переменная внутри блока {...}. Поэтому в результате выполнения данного фрагмента будет напечатана пустая строка.

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

1 $а[0]="b";

2 #..............

3 $b[0]=2;

4 $b[1]=2;

5 #..............

6 $а[0] [0]=0;

7 #..............

8 $prod = $Ь[0]*Ь[1];

переменная $prod получит значение 4. Но это не так. В строке 6 мы осуществляем присваивание, рассчитывая на то, что будет применен известный механизм неявного создания жесткой ссылки $а [0]. Мы "забыли" о том, что значение $а[0] уже использовалось в строке 1 и, следовательно, в строке 6 элемент массива $а[0] является символической ссылкой, указывающей на переменную с именем "Ь". Это имя будет подставлено вместо символической ссылки, в результате чего элемент массива ь[0] получит новое значение 0. В итоге значение переменной $prod будет равно 0.

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

use strict 'refs';

Это ограничение, если требуется, можно отменить для внутреннего блока при помощи другой директивы

no strict 'refs 1 ;

(Директивы use, no рассматриваются в части 12.)

Еще одно замечание, касающееся символических ссылок. В версии 5.001 появилась новая возможность: если переменную, являющуюся символической ссылкой, заключить в фигурные скобки, то такая конструкция интерпретируется не как символическая ссылка, а как значение переменной, подобно тому, как аналогичная конструкция интерпретируется командной оболочкой shell операционной системы UNIX. В следующем фрагменте

1 use strict 'refs ';

2 ${name};

3 ${"name"};

вторая строка представляет собой просто значение переменной $name, а третья строка интерпретируется как символическая ссылка, указывающая на переменную $name и вследствие применения директивы use strict 'refs 1 вызывает сообщение об ошибке вида

Can't use string ("name") as a SCALAR ref while "strict refs" in use


Глава 1

Использование ссылок

В данном разделе мы рассмотрим некоторые примеры, связанные с основным применением ссылок — конструированием структур данных. В качестве первой структуры построим массив массивов или двумерный массив. Для примера рассмотрим массив @calendar, содержащий календарь, например, на 2000 год. Значением элемента $caiendar[$i] [$j] является название дня недели, приходящегося на (j+l)-ft день (i+l)-ro месяца, i=(0..11),j=(0..30)(p H c. 9.1).

Рис 9.1. Структура массива @calendar

Замыкания

Для заполнения массива ecaiendar нам потребуется функция, которая по заданному году, месяцу и дню месяца вычисляет соответствующий день недели. Читатель может сам написать свой вариант функции, может быть, более изящный. Мы предлагаем наш вариант (пример 9.1) с основной целью: рассказать об одном интересном свойстве анонимных подпрограмм. Кроме того, он правильно работает для любого года нашей эры.

Вычисление дня недели основано на том, что:

  • 1 января 1 года нашей эры было понедельником;
  • каждый год, номер которого делится на 4, является високосным, за исключением тех номеров, которые делятся на 100 и не делятся на 4.

(Вопросы создания функций пользователем рассмотрены в части 11.)

sub GetDay (

my $year = shift;

my @days = (Q,31,59,90,120,151,181,212,243,273,304,334);

my @week = ("Monday","Tuesday","Wednesday","Thursday",

"Friday","Saturday","Sunday"); my $previous_years_days = ($year -1 ).*365 + int (($year-l) /4)

- int(($year-l)/100) + int(($year-l)/400); return sub { my ($month, $day)=@_;

my $n « $previous_years_days + $days[$month-l] + $day -1; $n++ if ($year%4 == 0 and $year%100 != 0 or

$year%400 == 0 and $month > 2) ; return $week[$n%7]; } };.

Аргументами функции GetDay о являются номер года, номер месяца и номер дня месяца. Внутри тела функции им соответствуют переменные $уеаг, $month и $day. Функция подсчитывает число дней $п, прошедших с 1 января 1 года. Остаток от деления этого числа на 7 — $п%7 — определяет день недели как элемент массива $week[$n%7].

Необходимые пояснения к тексту

Для передачи параметров в подпрограмму используется предопределенный массив @_. Встроенная функция shift•() без параметров, вызванная внутри подпрограммы, возвращает первый элемент массива @_ и осуществляет сдвиг всего массива влево, так, что первый элемент пропадает, второй становится первым и т.д. Элемент массива $days[$l] равен суммарному числу дней в первых i месяцах не високосного года, i = (0..11). В переменной $previous_years_days запоминается вычисленное значение общего количества дней, прошедших с 1 января 1 года до начала заданного года.

Обратите внимание на то, что значением функции GetDayO является не название дня недели, а ссылка на анонимную функцию, которая возвращает название дня недели. Объясним, зачем это сделано.

Если бы функция GetDay (} возвращала день недели, то для заполнения календаря на 2000 год, к ней необходимо было бы сделать 366 обращений, вычисляя каждый раз значение переменной $previous_years_days. Для каждого года это значение постоянно, поэтому его достаточно вычислить всего один, а не 366 раз.

На время вычисления функции формируется ее вычислительное окружение, включающее совокупность действующих переменных с их значениями. После завершения вычисления функции ее вычислительное окружение пропадает, и на него невозможно сослаться позже. Часто бывает полезным, чтобы функция для продолжения вычислений могла запомнить свое вычислительное окружение. В нашем примере полезно было бы запомнить значение переменной $previous_years_days, чтобы не вычислять его повторно. В языках программирования существует понятие замыкание, пришедшее из языка Lisp. Это понятие обозначает совокупность, состоящую из самой функции как описания процесса вычислений и ее вычислительного окружения в момент определения функции.

Анонимные процедуры в Perl обладают тем свойством, что по отношению к лексическим переменным, объявленным при помощи функции ту (), выступают в роли замыканий. Иными словами, если определить анонимную функцию в некоторый момент времени при некоторых значениях лексических переменных, то в дальнейшем при вызове этой функции ей будут доступны значения этих лексических переменных, существовавшие на момент ее определения.

В нашем примере указанное свойство анонимных функций используем следующим образом. Чтобы анонимной функцией можно было воспользоваться в дальнейшем, присвоим ссылку на нее скалярной переменной:

$f = GetDay(2000,1,1);

Во время обращения к GetDay о было сформировано вычислительное окружение анонимной функции, на которую сейчас указывает переменная $f. Вычислительное окружение включает, в том числе, и переменную $previous_years_days с ее значением. Обратите внимание, что внутри анонимной функции значение этой переменной не вычисляется. В дальнейшем для заполнения календаря мы будем вызывать анонимную функцию через ссылку $f.

Массив массивов

Сформируем массив @caiendar, используя результаты предыдущего раздела.

for $i (1,3..12) { . '

for $j (1..30) {

$calendar[$i-l][$j-l] =&$f($i, $j);

}

}; • . for $i (1,3,5,7,8,10,12) {

$calendar[$i-l][30] = &$f($i, 31); }; for $j (1..28) {

$calendar[l][$j-l] = &$f(2, $j); };

# Если год високосный, то добавляется еще один элемент массива $calendar[l][28] = &$f(2,29);

Массив @caiendar состоит из 12 элементов по числу месяцев в году. Каждый элемент массива является ссылкой на другой массив, имеющий столько элементов, сколько дней в соответствующем месяце. Значениями элементов вложенных массивов являются английские названия соответствующих дней недели: "Monday", "Tuesday" и т. д.

Обращаем внимание на то, что при формировании массива ^calendar осуществляется неявное создание ссылок $caiendar [$i] и применяется компактная запись $calendar[$i] [$j] для обозначения индивидуального элемента двумерного массива, обсуждавшаяся ранее.

Содержимое массива @calendar можно вывести для просмотра при помощи следующих операторов:

for $i (0..11) {

for $j (0..$#{$calendar[$i]}) {

print $j+l,".",$i+l," is $calendar[$i][$j]\n";

} };

Напомним, что запись $#array обозначает верхнее значение индекса массива @аггау. В результате выполнения данного цикла будет выведена длинная последовательность строк вида

1.1 is Saturday 2.1 is Sunday

Другие структуры данных

На основе массива Ocaiendar, содержащего календарь на 2000 год, покажем, как можно строить более сложные структуры данных. Структура двумерного массива не очень удобна для представления содержащихся в ней данных в привычном виде настенного календаря. Перегруппируем данные, объединяя их в группы по дням недели. Для этого построим новую структуру, которую для краткости назовем "массив хешей массивов", отдавая себе отчет в том, что такое словосочетание не только далеко не изящно, но и по существу неточно.

Новая структура представляет собой массив @months, состоящий из 12 элементов по числу месяцев в году. Каждый элемент содержит ссылку на анонимный хеш-массив. Каждый вложенный хеш-массив содержит набор ключей, имеющих имена, совпадающие с английскими названиями дней недели: "Monday", "Tuesday" и т. д. Каждому ключу соответствует значение, являющееся, в свою очередь, ссылкой на анонимный массив, содержащий все числа данного месяца, приходящиеся на день недели, соответствующий ключу: все понедельники, все вторники и т. д. Структура массива @months представлена на рис. 9.2.

Рис 9.2. Структура массива @months

for $i (0..11) { . for $j (0..$#{$calendar[$i.]}> {

push @{$months[$i]{$calendar[$i][$j]}}, $j+l; } };

Замечание

Функция push @array, list помещает список list в конец массива garray.

Первым аргументом встроенной функции push является массив, в который попадают все дни (i+l)-ro месяца, приходящиеся на один и тот же день недели: все понедельники, все вторники и т. д. На этот массив указывает ссылка $months[$i] {"key"}, где ключ "key" принимает значения "Monday", "Tuesday" и т. д. Для обращения к самому массиву ссылку следует разымено-вать, заключив в фигурные скобки: @{$months[$i] ("key"}}. Если вместо ключа "key" подставить нужное значение из $caiendar[$i] [$j], то получим аргумент функции push.

Вновь сформированную структуру удобно использовать для вывода календаря в традиционном виде. Последовательность операторов

for $i (0..11) {

print "month # ", $i+l, "\n";

for $DayName (keys %{$months[$i]}) {

print " ${DayName}: @{$months[$i]{$DayName}}\n";

} };

распечатает календарь в виде

month #1

Monday 3 10 17 24 31

Thursday 6 13 20 27

Wednesday 5 12 19 26

Sunday 2 9 16 23 30

Saturday 1 8 15 22 29

Friday 7 14 21 28

Tuesday 4 11 18 25

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

Для вывода ключей в порядке следования дней недели воспользуемся встроенной функцией сортировки

sort SUBNAME LIST

Замечание

Функция sort () сортирует список LIST и возвращает отсортированный список значений. По умолчанию используется обычный лексикографический (словарный) порядок сортировки. Его можно изменить при помощи аргумента SUBNAME, представляющего собой имя подпрограммы. Подпрограмма SUBNAME возвращает целое число, определяющее порядок следования элементов списка. Любая процедура сортировки состоит из последовательности сравнений двух величин. Для того чтобы правильно задать порядок сортировки, надо представить себе SUBNAME как функцию двух аргументов. В данном случае аргументы в подпрограмму SUBNAME передаются не общим для Perl способом - через массив @_, а через переменные $а и $ь, обозначающие внутри подпрограммы соответственно первый и второй аргумент. Подпрограмму SUBNAME надо составить таким образом, чтобы она возвращала положительное целое, нуль, отрицательное целое, когда при сравнении аргумент $а назначается меньшим аргумента $ь, равным аргументу $b, большим аргумента $ь соответственно. Для этого внутри подпрограммы удобно использовать операции числового (<=>) и строкового (стр) сравнения, возвращающие значения -1,0, 1, если первый аргумент соответственно меньше второго, равен второму, больше второго.

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

Зададим функцию weekOrder, определяющую порядок сортировки

sub WeekOrder {

my %week=("Monday"=>0,

"Tuesday"=>1,

"Wednesday"=>2,

"Thursday"=>3,

"Friday"=>4,

"Saturday"=>5,

"Sunday"=>6) ; $week{$a}<=>$week{$b} };

Используя функцию sort () с заданным порядком сортировки

for $i (0..11) {

print "month # ", $1+1, "\n";

for $DayName (sort WeekOrder keys %{$months[$i]}) { print " $DayName @{$months[$i]{$DayName}}\n";

} • ' ' };

получим структурированный вывод календаря в виде, упорядоченном по месяцам и дням недели:

month f 1

Monday 3 10 17 24 31

Tuesday 4 11 18 25

Wednesday 5 12 19 26

Thursday 6 13 20 27

Friday 7 14 21 28

Saturday 1 8 15 22 29

Sunday 2 9 16 23 30

В качестве следующего примера построим на основе массива gmonths новую структуру, которую можно было бы назвать "хеш-массив хеш-массивов массивов", если бы такое название имело право на существование. В действительности, все просто. Речь идет о том, чтобы заменить в массиве @months числовые индексы ключами, совпадающими с названиями месяцев, и таким образом получить ассоциативный массив %months со сложной внутренней структурой (см. рис. 9.3).

Рис 9.3. Ассоциативный массив %months со сложной внутренней структурой

При построении хеш-массива %months воспользуемся вспомогательным хеш-массивом %OrderedMonths, который будем использовать для задания порядка сортировки:

# вспомогательный массив %OrderedMonths %OrderedMonths =( "January"=>0,

"February"=>l,

"March"=>2,

"April"=>3,

"Мау"=>4, "June"=>5,
"July"=>6, "August"=>7, "September"=>8,
"October"=>9, "November"=>10, "December"=>ll
); # формирование структуры for $month (sort {$OrderedMonths{$a}<=>$OrderedMonths{$b}}

keys %OrderedMonths) { $i = $OrderedMonths{$month}; $months{$month}=$months[$ i];' };

# Вывод элементов хеш-массива %months for $month (sort {$OrderedMonths{$a}<=>$OrderedMonths{$b}}

keys %OrderedMonths) { print "$month\n";
$i = $OrderedMonths{$month); for $DayName (sort WeekOrder keys %{$months{$month}})
{

print " $DayName @{$months[$i]{$DayName}}\n"; } };

В результате выполнения примера 9.3 будет распечатан календарь на 2000 год в виде:

January

Monday 3 10 17 24 31

Tuesday 4 11 18 25

Wednesday 5 12 19.26

Thursday 6 13 20 27

Friday 7 14 21 28

Saturday 1 8 15 22 29

Sunday 2 9 16 23 30

Рассмотренные примеры иллюстрируют подход, используемый в Perl для построения сложных структур данных. Читатель может сравнить возможности, предоставляемые языком Perl, с возможностями распространенных языков программирования, таких как Pascal или С. Любая сложная структура в Perl на "верхнем" уровне представляет собой массив или ассоциативный массив, в который вложены ссылки на массивы или хеш-массивы следующего уровня и т. д. В этой иерархии ссылки на массивы и хеш-массивы могут чередоваться в произвольном порядке. При помощи такого подхода средствами Perl можно представить любую структуру С или запись языка Pascal. Perl позволяет с легкостью создавать структуры, которые в других языках создать трудно или невозможно, например, структуру, эквивалентную массиву, состоящему из элементов разных типов:

@аггау = (1, 2 ,3, ("опе"=>1, "two"=>2}, \sfunc, 4, 5};

Читатель может поупражняться в построении таких структур и открыть для себя новые нюансы применения этого гибкого и мощного подхода.

В заключение несколько слов о фрагментах массивов. Для доступа к элементам массива мы имеем специальную нотацию, состоящую из префикса $, имени массива и индекса элемента в квадратных скобках, например, $аггау[7]. Если здесь вместо индекса поместить список индексов, а префикс $ заменить префиксом @, то такая запись будет обозначать фрагмент массива, состоящий из элементов с индексами из заданного списка. Подобную нотацию можно использовать в выражениях, например,

Ssubarrayl = @array[7..12]; @subarray2 = @array[3,5,7];

Массив @subarrayi является фрагментом массива garray, состоящим из элементов со значениями индекса от 7 до 12. Массив @subarray2 является фрагментом массива @аггау, состоящим из элементов со значениями индекса 3, 5 и 7. В первом случае список индексов задан при помощи операции "диапазон", во втором случае - перечислением.

Для многомерного массива понятие "фрагмент" обобщается и означает подмножество элементов, получающееся, если для некоторых индексов из диапазона их изменения выделить список допустимых значений. Для выделения одномерных фрагментов можно воспользоваться приведенной выше нотацией. Например, для выделения из массива @caiendar фрагмента, содержащего календарь на первую неделю апреля, можно использовать запись

@april_first_week = @{'$calendar [3] } [0. . 6];

Если выделяемый фрагмент является многомерным, то для его обозначения специальной нотации не существует. В этом случае следует сформировать новый массив, являющийся фрагментом исходного массива. Например, для выделения из массива @caiendar календаря на первый квартал можно воспользоваться циклом

for $i (0..2) { .

for $j (0..$#{$calendar[$i]}) {

$quarter.l[$i] [$j] = $ calendar [$i] [$j ] ; } ' };


Глава 1

Вопросы и Упражнения

Вопросы для самоконтроля

1. Что такое ссылка?

2. Объясните разницу между жесткой и символической ссылкой.

3. Все ли корректно в следующем фрагменте

$href = \%hash; $$href[0] = 17;

4. Каким будет значение переменной $b после выполнения следующих операторов:

$а = 1;

$b = ref $a;

5. Что обозначает каждое из выражений:

$$а[0]; ${$а[0]}; $а->[0]; '$а[0];

6. Приведите пример неявного создания ссылки.

7. $arr_ref — ссылка на анонимный массив. Как с ее помощью обратиться к третьему элементу этого массива? Напишите выражение.

8. Что такое "замыкание"?

Упражнения

1. Добавьте текст, содержащий последовательность операций, которые надо применить к переменной $b, чтобы получить значение переменной $а

$а = 7;

$b = \\\\$а;

В упражнениях 2-4 используйте результаты, полученные в примерах 9.1-9.3.

2. Вывести на экран все дни 2000 года, приходящиеся на воскресенья. Вывод должен содержать строку-заголовок, например, "All 2000 1 Sundays

are: ", И ПО ОДНОЙ строке на каждый месяц года В виде: <название месяца^ <дни месяцах

3. Вывести на экран календарь на второй квартал года в виде

<название месяца> <Monday> <дни месяца>

<Sunday> <дни месяца>

4. Вывести на экран календарь на первую неделю любого месяца. Вывод должен содержать строку-заголовок и по одной строке на каждый день недели в виде

<название месяца> <день месяца> <название дня недели>

5. Треугольником Паскаля называется следующая бесконечная таблица чисел:

Рис 9.4. Треугольник Паскаля

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


Глава 1

Предисловие

Предисловие, на наш взгляд, должно дать читателю информацию, на основании которой он решает, нужен ли ему этот диск.

О чем наш диск. Он, естественно, о языке Perl, потому что так заявлено в названии. Кому он нужен, этот Perl? Тем, кто создает CGI-сценарии, занимается администрированием системы при помощи написания скриптов, а не щелкая левой кнопкой мыши, обрабатывает тексты, решает многие другие задачи из смежных областей и при этом нуждается в мощном, но простом в применении средстве, позволяющем создавать большие программы и маленькие программки и быстро их опробовать. Тем, кто преподает программирование, тоже полезно иметь представление об этом языке, так как он обладает интересными свойствами, отсутствующими в традиционных языках программирования, используемых в процессе обучения.

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

Язык Perl создан системным программистом Ларри Уоллом (Larry Wall) как средство UNIX, позволяющее "склеивать" из программок, выполняющих отдельные функции, большие сценарии для решения комплекса задач, связанных с администрированием, обработкой текста и т. д. В дальнейшем он вышел за эти рамки, превратился в настоящий язык программирования, в котором нашли отражение многие тенденции, обозначившиеся в технологии программирования за последнее десятилетие, и получил широкое распространение в связи с развитием Inernet. Perl является основным средством создания приложений CGI, удобен для решения задач администрирования Web-серверов, электронной почты и других систем. Благодаря быстроте и легкости написания сценариев на этом языке он распространился и на другие платформы: DOS, Windows, OS/2, Mac, VMS и пр. Одно из основных достоинств языка Perl — его открытость и доступность. В сети Internet можно получить совершенно бесплатно исходные тексты интерпретатора perl (язык Perl — интерпретируемый, что в некоторых случаях является преимуществом) и модулей его расширения.

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

При описании языка мы придерживались следующей схемы: сначала познакомить читателя с типами данных, затем с допустимыми операциями языка, далее с операторами и потом уже со специальными синтаксическими конструкциями (ссылки, регулярные выражения, подпрограммы, пакеты, модули и объектно-ориентированное программирование), т. е. мы старались идти от простых понятий к более сложным, что, как нам кажется, позволяет читателю получить целостное представление о языке программирования. Завершается изучение языка описанием того, как его использовать для создания CGI-сценариев — области Web-программирования, где он широко распространен.

Что осталось за рамками нашего диска? Очень многое, так как мир Perl велик. Это вопросы взаимодействия с базовой операционной системой, межпроцессорное взаимодействие в UNIX, средства организации сетей, совместное использование Perl и других языков программирования, особенности работы на платформах, отличных от UNIX, работа с базами данных и еще многое другое. В главе о применении Perl в CGI-программировании неплохо было бы рассказать о протоколе HTTP и конфигурировании Web-сервера. По этим темам можно написать еще одну книгу, как минимум, такого же объема. Мы сознательно исключили данные вопросы из рассмотрения, так как они являются достаточно специфическими.