Аргументы
Несомненно, подпрограммы, выполняющие одно определенное действие, полезны, однако перед вами откроются совершенно новые горизонты, когда вы сможете передавать в подпрограмму аргументы.
В Perl после вызова подпрограммы следует список, заключенный в круглые скобки, которые обеспечивают автоматическое присваивание элементов данного списка на период выполнения этой подпрограммы специальной переменной с именем @_. Подпрограмма может обратиться к этой переменной и получить число аргументов и их значения. Например:
sub say_hello_to (
print "hello, $_[0]!\n" # первый параметр }
Здесь мы видим ссылку на $_[0] — первый элемент массива @_. Обратите внимание: несмотря на внешнее сходство, значение $_ [ 0 ] (первый элемент массива @_) не имеет ничего общего с переменной $_ (самостоятельной скалярной переменной). Не путайте их! Из этого кода видно, что подпрограмма приветствует того, чье имя мы указываем в качестве первого параметра. Это значит, что ее можно вызвать так:
say_hello_to("world"); # выдает hello, world $х = "somebody";
say_hello_to($x); # выдает hello, somebody say_hello_to("me")+say_hello_to("you"); # а теперь приветствует себя и вас
В последней строке возвращаемые значения явно использованы не были, однако для определения суммы Perl сначала должен вычислить все ее слагаемые, поэтому подпрограмма была вызвана дважды.
Вот пример с использованием более одного параметра:
sub say (
print "$_[0], $_[!]!\n";
}
say("hello","world"); # опять hello world
say ("goodbye", "cruel world"); # goodbye cruel world - популярная фраза из фильмов
Избыточные параметры игнорируются, т.е. если вы никогда не заглядываете в $_ [ 3 ], языку Perl это абсолютно все равно. Недостающие параметры также игнорируются, и если вы попытаетесь обратиться за пределы массива @_, как и любого другого массива, то просто получите в ответ undef.
Переменная @_ является локальной для подпрограммы. Если для @_ установлено глобальное значение, то оно перед вызовом подпрограммы сохраняется, а после возврата из подпрограммы восстанавливается. Это также означает, что подпрограмма может передавать аргументы в другую подпрограмму, не боясь "потерять" собственную переменную @_; вложенный вызов подпрограммы точно так же получает собственную переменную @_.
Давайте вернемся к программе сложения а и b из предыдущего раздела. Вот подпрограмма, которая складывает любые два значения, а именно два значения, передаваемые в нее как параметры:
sub add_two {
return $_[0] + $_[!];
1
print add_two(3,4); # выводит значение 7
$с = add_two(5,6); # $с получает значение 11
Давайте обобщим эту подпрограмму. Что, если нам нужно сложить 3, 4 или 100 значений? Это можно было бы сделать с помощью цикла, например:
sub add (
$sum = 0; # инициализировать сумму
foreach $_ (@_) {
$sum += $_; # прибавить все элементы
}
return $sum # последнее вычисленное выражение: сумма всех элементов }
$а = add(4,5,6); # складывает 4+5+6=15 и присваивает переменной $а print add(1,2,3,4,5); # выводит 15 print add (1..5); # тоже выводит 15, потому что список 1..5 раскрывается
Что, если бы переменная с именем $sum использовалась у нас раньше? При вызове подпрограммы add мы просто потеряли бы ее значение. В следующем разделе мы увидим, как избежать этого.
Базы данных произвольного доступа с записями фиксированной длины
Еще одна форма хранения данных — файл на диске, предназначенный для записей фиксированной длины. В зтой схеме данные состоят из ряда записей одинаковой длины. Нумерация зтих записей либо не имеет значення, либо определяется по какой-нибудь схеме индексации.
Например, у нас может быть ряд записей со следующими данными:
40 символов — имя, один символ — инициал, 40 символов — фамилия и двухбайтовое целое — возраст. Таким образом, длина каждой записи состав-ляет 83 байта. Если бы мы читали все зти данные в базе данных, то делали бы зто порциями по 83 байта до тех пор, пока не добрались до конца. Если бы мы хотели перейти к пятой записи, то мы пропустили бы четыре раза по 83 байта (332 байта) и прочитали бы непосредственно пятую запись.
Perl поддерживает программы, которые используют файл с подобными записями. Помимо того, что вы уже знаєте, понадобятся еще несколько операций:
1. Открытие файла на диске для чтения и записи.
2. Переход в зтом файле на произвольную позицию.
3. Выборка данных фиксированной длины, а не до следующего символа новой строки.
4. Запись данных блоками фиксированной длины.
В функции open перед спецификацией, задающей способ открытия файла (для чтения или записи), необходимо записать знак плюс, указав таким образом, что данный файл в действительности открывается и для чтения, и для записи. Например:
open (А, "+<Ь"); # открьеть файл b для чтения-записи (ошибка, если файл отсутствует)
open(C, "+>d"); # создать файл d с доступом для чтения-записи
open(Е, "+”f"); # открить или создать файл f с доступом для чтения-записи
Отметим, что все, что мы сделали — зто добавили знак плюс к специфи-кации, задающей направление ввода-вывода данных в файл.
Открыв файл, мн должны перейти на определенную позицию в нем. Зто делается с помощью функции seek, которая принимает те же три параметра, что и библиотечная програм ма./yeeA^.?/ Первый параметр — зто дескриптор файла, а второй параметр задает смещение, которое интерпретируется в совокупности с третьим параметром. Как правило, в качестве третього параметра ставится нуль, чтобы второй параметр задавал абсолютную позицию для следующего чтения из файла или записи в файл. Например, чтобы перейти к пятой записи в дескрипторе файла names (как описано выше), можно сделать так:
seek(NAMES,4*83,0) ;
После перемещения указателя в файле на нужную позицию следующая операция ввода или вывода будет начинаться с зтой позиции. Для вывода используйте функцию print, но не забудьте, что записываемые данные должны иметь строго определенную длину. Чтобы сформировать запись правильной длины, можно воспользоваться функцией pack::
print NAMES pack("A40 A A40 s", $first, $middle, $last, $age);
В данном случае pack задает 40 символов для $ first, один символ — для $middle, еще 40 символов — для $last и короткеє целое (два байта) для $аде. Определенная таким образом запись будет иметь в длину 83 байта и начинаться с текущей позиции в файле.
Наконец, нам нужно узнать, как выбрать конкретную запись. Конструк-ция <names> возвращает все данные, начиная с текущей позиции і до следующего символа новой строки, однако в нашем случае предполагасгея, что данные занимают 83 байта й, вероятно, символ новой строки непосред-ственно в записи отсутствует. Позтому вместо нее мы используем функцию read, которая по внешнему виду и принципу работы очень похожа на свою UNIX-коллегу:
$count = read(NAMES, $buf, 83);
Первый параметр функции read — дескриптор файла. Второй параметр — зто скалярная переменная, в которую будут записаны прочитанные данные. Третий параметр задает количество байтов, которые нужно прочитать. Возвращает функция read количество фактически прочитанных байтов; как правило, оно равно затребованному количеству байтов, если только дескриптор файла открыт и если вы не находитесь слишком близко к концу файла.
Получив зти 83-символьные данные, разбейте их на компоненты с помощью функции unpack:
($first, $middle, $last, $age) = unpack("A40 A A40 s", $buf);
Как видно, строки, определяющие формат, в функциях pack и unpack — одинаковы. В большинстве программ зту строку заносят в переменную, указы-ваемую в начале программы, и даже вычисляют с помощью функции pack длину записей, а не используют везде константу 83:
$names = "А40 А А40 s";
$names_length = length(pack($names)); # вероятно, 83
Базы данных с записями переменной длины (текстовые)
Многие системные базы данных ОС UNIX (й довольно большое число пользовательских баз данных) представляют собой набори понятных чело-веку текстовых строк, каждая из которых образует одну запись. Например, каждая строка файла паролей соответствует одному пользователю системы, а строка файла хостов — одному хост-имени.
Корректируются зти базы данных в основном с помощью простих текстовых редакторов. Процедура обновлення базы данных состоит из чтения ее в какую-то временную область (память или другой дисковий файл), внесення необходимых изменений и либо записи результата обратно в исходный файл, либо создания нового файла с тем же именем, с одновре-менным удалением или переименованием старой версии. Зтот процесе можно рассматривать как разновидность копирования: данные копируются из исходной базы данных в новую ее версию с внесением изменений в процессе копирования.
Perl поддерживает редактирование такого типа в строчно-ориентирован-ных базах данных методом редактирования на месте. Редактирование на месте — зто модификация способа, посредством которого операция "ромб" (<>) считывает данные из списка файлов, указанного в командной строке. Чаще всего зтот режим редактирования включается путем установки аргу-мента командной строки -і, но его можно запустить и прямо из программы, как показано в приведенных ниже примерах.
Чтобы запустить режим редактирования на месте, присвойте значение скалярной переменной $ л
і. Оно играет важную роль и будет сейчас рассмот-рено.
Когда используется конструкция о и переменная $ЛI имеет значение, отличное от undef, к списку неявних действий, которые выполняет операция "ромб", добавляются шаги, отмеченные в приведенном ниже коде комментарием ## inplace ##:
$ARGV = shift 6ARGV;
open(ARGV,"<$ARGV") ;
rename($ARGV,"$ARGV$AI"); ## INPLACE ## unlink($ARGV); ## INPLACE ##
open(ARGVOUT,">$ARGV"); ## INPLACE ## select(ARGVOUT) ,- ## INPLACE ##
В результате в операции "ромб" при чтении используется старый файл, а запись в дескриптор файла по умолчанию осуществляется в новую копию зтого файла. Старый файл остается в резервной копии, суффикс имени файла которой равен значеним переменной $AI. (При зтом биты прав доступа копируются из старого файла в новый.) Зти шаги повторяются каждый раз, когда новый файл берется из массива @argv.
Типичные значення переменной $ЛI — .bak или ~, т.е. резервные файлы создаются почти так же, как зто делается в текстовом редакторе. Странное и полезное значение $ЛI — пустая строка (""), благодаря которой старый файл после редактирования аккуратно удаляется. К сожалению, если система при выполнении вашей программы откажет, то вы потеряете все свои старые данные, позтому значение "" рекомендуется использовать только храбрецам, дуракам и излишне доверчивым.
Вот как можно путем редактирования файла паролей заменить регистра-ционный shell всех пользователей на /bin/sh'.
8ARGV = ("/etc/passwd"); # снабдить информацией операцию "ромб"
$"1
== ".bak"; # для надежности записать /etc/passwd.bak
while (о) { # основной цикл, по разу для каждой строки файла
# /etc/passwd s#: (л: ] *$#:/bin/sh#; # заменить shell на /bin/sh print; # послать выходную информацию в ARGVOUT: новий
# /etc/passwd
Как видите, зта программа довольно проста. Однако ее можно заменить всего лишь одной командой с несколькими аргументами командной строки, например:
perl -р -і.bak -е 's#: [л:]*$#:/bin/sh#' /etc/passwd
Ключ -р охватывает вашу программу циклом while, который включает оператор print. Ключ -і устанавливает значение переменной $^1. Ключ -е определяет следующий аргумент как фрагмент Perl-кода для тела цикла, а последний аргумент задает начальнеє значение массива @argv.
Более подробно аргументы командной строки рассматриваются в книге Programming Perl и на man-странице perlrun.
Библиотеки и модули
Для простых программ вы уже теперь можете свободно писать собственные Perl-подпрограммы. Когда же задачи, для решения которых вы применяете Perl, станут более сложными, вам иногда будет приходить в голову мысль: "Кто-то, должно быть, это уже делал". И в подавляющем большинстве случаев вы окажетесь правы.
Действительно, другие люди уже написали коды для решения большинства распространенных задач. Более того, они поместили их либо в стандартный дистрибутив Perl, либо в бесплатно загружаемый архив CPAN. Чтобы использовать этот код (и сэкономить немного времени), вам придется разобраться в том, как пользоваться Perl-библиотекой. Этот вопрос вкратце освещался в главе 19.
Одно из преимуществ использования модулей из стандартного дистрибутива состоит в том, что потом вы можете предоставлять свою программу другим пользователям, при этом не придется предпринимать никаких специальных мер. Это объясняется тем, что одна и та же стандартная библиотека доступна Perl-программам практически везде.
Если вы решите обратиться к стандартной библиотеке, то в конечном итоге сэкономите свое время. Нет никакого смысла вновь изобретать велосипед. Следует понимать, однако, что эта библиотека содержит очень много материала. Одни модули могут быть исключительно полезны, тогда как другие совершенно не подходят для решения ваших задач. Например, некоторые модули полезны лишь в том случае, если вы создаете дополнения к языку Perl.
Чтобы прочитать документацию, относящуюся к стандартному модулю, воспользуйтесь программой man или perldoc (если они у вас есть) либо своим Web-броузером, если речь идет о HTML-версиях этой документации. Если ничего не получается, поищите в файлах самого модуля: документация включена в состав каждого модуля (в pod-формате). Чтобы найти модуль у себя в системе, попробуйте выполнить из командной строки следующую Perl-программу:
# для (большинства) Unix-подобных shell peri -e 'print "@INC\n"'
# для (некоторых) других интерпретаторов команд
peri -e "print join(' ',"@INC),\n"
Вы должны найти модуль в одном из каталогов, перечисленных этой командой.
Терминология
Перед тем как дать перечень всех стандартных модулей, давайте разберемся в терминах.
Пакет
Пакет — это простое устройство управления пространством имен, позволяющее в каждой из двух разных частей Perl-программы иметь свою переменную с именем $fred. Этими пространствами имен управляет объявление package, описанное в главе 5 книги Programming Perl.
Библиотека
Библиотека — это набор подпрограмм определенного назначения. Часто библиотека объявляет себя отдельным пакетом; это позволяет держать в одном месте соответствующие переменные и подпрограммы, чтобы они не мешали другим переменным в вашей программе. Как правило, библиотека старого стиля размещалась в отдельном файле, часто под именем с расширением р1. Библиотечные программы включались в основную программу посредством функции require.
He так давно этот подход был заменен использованием модулей (см. следующий абзац), и термин библиотека
теперь часто обозначает всю систему модулей, которые поставляются с Perl.
Модуль
Модуль — это библиотека, соответствующая конкретным соглашениям, которая позволяет включать библиотечные подпрограммы в основную программу во время компиляции с помощью директивы use. Имена файлов модулей имеют расширение рт, потому что это необходимо для корректного использования директивы use. Подробно Perl-модули описаны в главе 5 книги Programming Perl.
Прагма
Прагма — это модуль, который воздействует не только на фазу выполнения программы, но и на фазу ее компиляции. Считайте, что прагма содержит подсказки компилятору. В отличие от других модулей, прагмы часто (но не всегда) ограничивают сферу своего влияния самым внутренним охватывающим блоком вашей программы (т.е. блоком, охватывающим вызов прагмы). По соглашению имена прагм состоят из символов нижнего регистра.
Стандартные модули
Ниже приведен перечень всех Perl-прагм и модулей, входящих в текущий дистрибутив языка (версия 5.004). Классификация модулей произвольная.
Таблица Б. 1. Общее программирование: разное
Модуль | Функция |
autouse | Задерживает загрузку модуля до его использования |
constant | Создает константы периода компиляции |
Benchmark | Проверяет и сравнивает временные параметры выполне |
ния кода | |
Config | Позволяет получить информацию о конфигурации Perl |
Env | Импортирует переменные среды |
English | Для пунктуационных переменных использует английские имена или имена на языке awk |
FindBin | Находит путь к выполняемой в данный момент программе |
Getopt::Long | Осуществляет расширенную обработку опций командной |
строки | |
Getopt::Std | Обрабатывает односимвольные ключи и осуществляет их |
кластеризацию | |
lib | Манипулирует массивом @INC во время компиляции |
Shell | Запускает команды shell прозрачно для Perl |
strict | Ограничивает использование небезопасных конструкций |
Symbol | Генерирует анонимное развертывание (glob); уточняет |
имена переменных | |
subs | Предопределяет имена подпрограмм |
vars | Предопределяет имена глобальных переменных |
Модуль | Функция |
Сагр diagnostics sigtrap Sys::Syslog |
Выдает сообщения об ошибках Включает режим диагностики с выдачей предупреждений Разрешает обратное прослеживание стека для неожиданных сигналов Perl-интерфейс к UNIX-вызовам syslog(3) |
Таблица Б.З. Общее программирование: доступ к файлам и их обработка | |
Модуль | Функция |
Cwd DirHandle Fcnti File::Basename File::CheckTree File:: Copy File::Find File::Path FileCache FileHandle SelectSaver |
Получает путевое имя текущего рабочего каталога Выдает методы объектов для работы с дескрипторами каталогов Загружает С-определения Fcntl.h Разбирает спецификации файлов Выполняет всевозможные проверки для набора файлов Копирует файлы или дескрипторы файлов Обеспечивает просмотр дерева файлов Создает и удаляет ряд каталогов Позволяет одновременно открывать больше файлов, чем разрешает система Выдает методы объектов для работы с дескрипторами файлов Сохраняет и восстанавливает выбранный дескриптор файла |
Таблица Б. 4. Общее программирование: классы для операций ввода-вывода | |
Модуль | Функция |
10 IO::File IO::Handle IO::Pipe IO::Seekable IO::Select IO::Socket | Интерфейс верхнего уровня к классам 10::* Методы объектов для работы с дескрипторами файлов Методы объектов для дескрипторов ввода-вывода Методы объектов для каналов Методы для объектов ввода-вывода на базе поиска Объектный интерфейс для выбора Объектный интерфейс для портов |
Таблица Б. 5. Общее программирование: обработка текста и экранные интерфейсы | |
Модуль | Функция |
locale Pod::HTML Pod::Text Search::Dict Term::Cap |
Использует локализацию POSIX для встроенных операций Конвертирует pod-данные в HTML Конвертирует pod-данные в форматированный ASCII-текст Ищет ключ в файле словаря Интерфейс termcap |
Модуль | Функция |
Term::Complete Text::Abbrev Text::ParseWords Text::Soundex Text::Tabs Text::Wrap | Модуль завершения слов Создает из списка таблицу сокращений Разбирает текст на лексемы и создает из них массив Реализует алгоритм Soundex, разработанный Кнутом Раскрывает и сворачивает знаки табуляции Выделяет текст в абзац |
Таблица Б. 6. Интерфейсы к базам данных | |
Модуль | Функция |
AnyDBMFile DBFile GDBMFile NDBMFile ODBMFile SDBM File | Создает основу для множества DBM Доступ к Berkeley DB Связанный доступ к библиотеке GDBM Связанный доступ к файлам NDBM Связанный доступ к файлам ODBM Связанный доступ к файлам SDBM |
Таблица Б. 7. Математика | |
Модуль | Функция |
Integer Math::BigFloat Math::BigInt Math::Complex |
Выполняет арифметические операции в целочисленном формате, а не в формате с двойной точностью Пакет математических операций для чисел с плавающей запятой произвольной длины Пакет математических операций для целых чисел произвольной длины Пакет для комплексных чисел |
Таблица Б. 8. World Wide Web | |
Модуль | Функция |
CGI CGI::Apache CGI::Carp CGI::Fast CGI::Push CGI:: Switch |
Интерфейс Web-сервера (Common Gateway Interface) Поддержка Perl-модуля сервера Apache Ошибки сервера регистрации с полезной информацией Поддержка FastCGI (устойчивый серверный процесс) Поддержка "выталкивания" со стороны сервера Простой интерфейс для многих типов серверов |
Таблица Б. 9. Сети и межпроцессное взаимодействие | |
Модуль | Функция |
1РС::Ореп2 | Открывает процесс для чтения и записи |
IPC::Open3 | Открывает процесс для чтения, записи и обработки ошибок |
Net::Ping | Проверяет, есть ли данный хост в сети |
Socket | Загружает С-определения socket.h и манипуляторы структур |
Sys::Hostname | Пытается получить хост-имя всеми возможными способами |
Таблица Б. 10. Автоматизированный доступ к Comprehensive Perl Archive Network | |
Модуль | Функция |
CPAN | Простой интерфейс к CPAN |
CPAN::FirstTime | Утилита для создания файла конфигурации CPAN |
CPAN::Nox | Запускает CPAN, избегая компилированных расширений |
Таблица Б. 11. Время и локализация | |
Модуль | Функция |
Time:: Local | Эффективно определяет местное и среднее гринвичское |
время | |
I18N::Collate | Сравнивает восьмибитовые скалярные данные |
Таблица Б. 12. Объектные интерфейсы к встроенным функциям | |
Модуль | Функция |
Class::Struct | Объявляет struct-подобные типы данных как Perl-классы |
File::stat | Объектный интерфейс к функции stat |
Net::hostent | Объектный интерфейс к функциям gethost* |
Net::netent | Объектный интерфейс к функциям getnet* |
Net::protoent | Объектный интерфейс к функциям getproto* |
Net::servent | Объектный интерфейс к функциям getserv* |
Time::gmtime | Объектный интерфейс к функции gmtime |
Time::localtime | Объектный интерфейс к функции localtime |
Time::tm | Внутренний объект для Time::{gm,local}time |
User::grent | Объектный интерфейс к функциям getgr* |
User::pwent | Объектный интерфейс к функциям getpw* |
Таблица Б. 13. Для разработчиков: автозагрузка и динамическая загрузка | |
Модуль | Функция |
Autoloader | Загружает функции только по требованию |
AutoSplit | Разбивает пакет для автозагрузки |
Devel::SelfStubber | Генерирует заглушки для модуля SelfLoading |
DynaLoader | Автоматическая динамическая загрузка Perl-модулей |
SelfLoader | Загружает функции только по требованию |
Таблица Б. 14. Для разработчиков: расширения языка и поддержка разработки платформ | |
Модуль | Функция |
blib |
Определяет структуру каталогов blib во время построения модулей |
ExtUtils::Embed | Утилиты для встраивания Perl в С-программы |
ExtUtils::Install | Инсталлирует файлы |
ExtUtils::Liblist | Определяет библиотеки для использования и по |
рядок их использования | |
ExtUtils:: MakeMaker | Создает Makefile для расширения Perl |
ExtUtils: '.Manifest | Утилиты для написания и проверки файла MANIFEST |
ExtUtils::Miniperl | Создает С-код для perlmain.c |
ExtUtils: :Mkbootstrap | Создает файл самозагрузки для использования модулем DynaLoader |
ExtUtils: :Mksymlists | Пишет файлы опций компоновщика для динами |
ческого расширения | |
ExtUtils: :MMOS2 | Методы для отмены UNIX-режима в ExtUtils::MakeMaker |
ExtUtils: :MMUnix | Методы, используемые модулем ExtUtils::MakeMaker |
ExtUtils::MMVMS | Методы для отмены UNIX-режима в ExtUtils::MakeMaker |
ExtUtils: :testlib | Исправляет @INC для использования только что |
созданного расширения | |
Opcode | Блокирует коды операций при компиляции Perl- |
кода | |
ops | Прагма для использования с модулем Opcode |
POSIX | Интерфейс к стандарту IEEE 1003.1 |
Safe | Создает защищенные пространства имен для оценки Perl-кода |
Test:: Harness | Выполняет стандартные тестовые Perl-сценарии со сбором статистических данных |
vmsish | Обеспечивает возможности, характерные для VMS |
Таблица Б. 15. Для разработчиков: поддержка объектно-ориентированного программирования | |
Модуль | Функция |
Exporter | Стандартный метод импорта для модулей |
overload | Перегружает математические операции Perl |
Tie::RefHash | Базовый класс для связанных хешей со ссылками в |
качестве ключей | |
Tie:: Hash | Содержит определения базового класса для связанных хешей |
Tie:: Scalar | Содержит определения базового класса для связанных |
скаляров | |
Tie::StdHash | Содержит определения базового класса для связанных хешей |
Tie::StdScalar | Содержит определения базового класса для связанных |
скаляров | |
Tie::SubstrHash | Обеспечивает хеширование с фиксированным размером таблицы и фиксированной длиной ключей |
UNIVERSAL | Базовый класс для всех классов |
CPAN: не только стандартная библиотека
Если вы не можете найти в стандартной библиотеке модуль, соответствующий вашим потребностям, все равно существует вероятность, что кто-то уже написал код, который будет вам полезен. Есть много превосходных библиотечных модулей, которые не включены в стандартный дистрибутив — по различным причинам практического, политического и вздорного характера. Чтобы выяснить, что есть в наличии, можно заглянуть в Comprehensive Perl Archive Network (CPAN). 0 CPAN мы говорили в предисловии.
Вот основные категории модулей, которые можно получить из CPAN:
• Модуль формата листинга.
• Базовые модули Perl, расширения языка и средства документирования.
• Модули, обеспечивающие поддержку разработки.
• Интерфейсы операционных систем.
• Организация сетей, управление устройствами (модемами) и межпроцес-сное взаимодействие.
• Типы данных и утилиты для типов данных.
• Интерфейсы баз данных.
• Пользовательские интерфейсы.
• Интерфейсы к другим языкам программирования и средства эмуляции этих языков.
• Имена файлов, файловые системы и блокировки файлов (см. также дескрипторы файлов).
• Обработка строк, обработка текстов, синтаксический анализ и поиск.
• Обработка опций, аргументов, параметров и файлов конфигурации.
• Интернационализация и локализация.
• Аутентификация, защита и шифрование.
• World Wide Web, HTML, HTTP, CGI, MIME.
• Серверные утилиты и демоны.
• Архивирование, сжатие и преобразование.
• Изображения, манипулирование картами пикселей и растрами, рисование и построение графиков.
• Электронная почта и телеконференции Usenet.
• Утилиты управления потоком (обратные вызовы и исключительные ситуации).
• Утилиты для работы с дескрипторами файлов, дескрипторами каталогов и потоками ввода-вывода.
• Модули для Microsoft Windows.
• Прочие модули.
| Назад
| Вперед
|
| Содержание | Предисловие | Введение | Ссылки | Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10 | Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19 | Приложение А | Приложение Б | Приложение В | Приложение Г | |
BITFTP
BITFTP — это почтовый сервер для пользователей сети BITNET. Вы посылаете на него сообщения электронной почты с запросами на получение файлов, а сервер посылает по электронной почте указанные вами файлы. Сейчас BITFTP обслуживает только тех пользователей, которые посылают на него почту с узлов, непосредственно включенных в BITNET, EARN или NetNorth. BITFTP — это общедоступный сервис Принстонского университета. Чтобы воспользоваться услугами BITFTP, пошлите почтовое сообщение с необходимыми FTP-командами по адресу BITFTP@PUCC. Если вы хотите получить полный справочный файл, пошлите в теле сообщения слово HELP. Ниже приведено тело сообщения, которое следует послать на BITFTP:
FTP ftp.ora .corn NETDATA USER anonymous PASS ваш электронно-почтовый Internet-адрес (а не BITNET-адрес) CD /published/oreilly/nutshell/perl/learning_perl2 DIR GET README GET examples.tar.gz QUIT
Вопросы, касающиеся самого BITFTP, следует направлять на узел BIT-NET MAINT@PUCC.
Благодарности: первое издание
Во-первых, я от всего сердца благодарю Чика Уэбба и фирму Taos Mountain Software (Кремниевая долина). Ребята из TMS предоставили мне возможность написать для них (при значительном содействии Чика) ввод-ный курс по языку Perl и прочитать этот курс несколько раз. Этот опыт дал мне мотивы и ресурсы для написания и многократного прочтения нового, моего собственного курса, на базе которого и построена эта книга. Не думаю, что без содействия сотрудников TMS я занимался бы этим, и желаю им всем больших успехов в маркетинге их курса. (А если они ищут хороший текст для переработки своего курса, то у меня как раз есть одно предложение...)
Спасибо моим рецензентам: "крестному отцу" языка Perl Ларри Уоллу (естественно), Ларри Кистлеру (руководителю службы подготовки кадров фирмы Pyramid), моему коллеге по преподаванию Per] Тому Кристиансену и слушателям курсов по Perl из фирм Intel и Pyramid, а также сотрудникам издательства O'Reilly & Associates Тане Херлик, Лару Кауфману, Пенни Мюльнеру, Линде Мюи и Энди Ораму.
Эта книга была полностью написана и отредактирована на моем персо-нальном компьютере Apple Macintosh Powerbook (сначала модели 140, теперь 160). В процессе работы я чаще всего находился вне своего кабинета — иногда в парке, иногда в гостинице, иногда в горах, ожидая хорошей погоды для лыжных прогулок, но чаще всего в ресторанах. По сути дела, значительную часть книги я написал в пивном баре Beaverton McMenamin's недалеко от дома. В пабах сети McM's варят и подают самое вкусное пиво, самый лучший творожный пудинг и самые жирные сэндвичи в округе. В этой идеальной для работы обстановке я выпил множество пинт пива и съел гору пудинга, а мой Powerbook поглотил массу киловатт-часов электроэнергии, устраиваясь на тех четырех столах, где есть розетки. Благодарю замечательный персонал бара McM's за эту электроэнергию, а также за гостеприимство, радушие и любезность (и бесплатное место для работы). Кроме того, благодарю ресто-ран Beaverton Chili's, где я начал работать над этой книгой. (Но у них возле стойки не оказалось розеток, поэтому когда я нашел McM's, то перешел туда, чтобы сберечь аккумуляторы.)
Спасибо всем пользователям Internet (особенно подписчикам телеконфе-ренции сотр.lang.perl) за их постоянную поддержку, оказываемую Ларри и мне, и за бесконечные вопросы на тему о том, как заставить Perl работать. Благодарю также Тима 0'Рейли — за даосизм.
Наконец, особая, огромная персональная благодарность — моему другу Стиву Тэлботту, который направлял меня на каждом этапе этого пути (в частности, он предложил сделать "прогулку" по стране Perl, впечатления от которой представлены в конце первой главы). Его редакторская критика всегда была справедливой, а его удивительная способность бить меня по голове с исключительной нежностью позволила мне сделать мою книгу произведением искусства, которым я крайне доволен.
Как всегда, выражаю особую благодарность Лайлу и Джеку за то, что они нашили меня почти всему тому, что я знаю о писательском ремесле.
И, наконец, безмерная благодарность — моему другу и партнеру Ларри Уоллу за то, что он дал всем нам Perl.
Одна "Л" — Рэндал — книгу написала, Вторая — лама — на обложку прискакала. Но кто все это изобрел? То целых три "Л" — Ларри Уолл!
Рэндал
Благодарности: второе издание
Я хотел бы поблагодарить Ларри Уолла за создание Perl, членов группы Perl Porters за их постоянные усилия по сопровождению языка и все Perl-сообщество за готовность помогать друг другу.
Спасибо также Джону Оруонту, Нэйту Торкингтону и Ларри Уоллу за рецензирование главы, посвященной CGI.
Том
Блоки операторов
Блок операторов — это последовательность операторов, заключенная в парные фигурные скобки. Блок операторов выглядит следующим образом:
(
первыи_оператор;
второй_оператор;
третий_оператор;
последний_оператор;
>
Perl выполняет операторы по очереди, начиная с первого и кончая последним. (Позднее вы узнаете о том, как можно изменять порядок выполнения в блоке, но пока достаточно и этого.)
Синтаксически блок операторов принимается вместо любого одиночного оператора, но обратное не верно.
Завершающая точка с запятой, стоящая за последним оператором, не обязательна. Таким образом, вы можете разговаривать на языке Perl с С-акцентом (точка с запятой присутствует) или с паскалевским акцентом (точка с запятой отсутствует). Чтобы облегчить последующее добавление операторов, мы обычно рекомендуем опускать точку с запятой лишь в том случае, если блок занимает целую строку. Сравните примеры этих стилей в следующих двух блоках if:
if ($ready) ( $hungry++ } if ($tired) (
$sleepy = ($hungry + 1) * 2;
}
Чтение дескриптора каталога
Открыв дескриптор каталога, мы можем прочитать список имен с помощью функции readdir, которая принимает единственный параметр — дескриптор каталога. Каждый вызов readdir в скалярном контексте возвращает следующее имя файла (только основное имя: в возвращаемом значении никаких косых нет) в порядке, который на первый взгляд кажется случайным*. Если больше имен нет, readdir возвращает undef**. Вызов readdir в списочном контексте возвращает все оставшиеся имена файлов в виде списка с одним именем, приходящимся на каждый элемент. Вот пример, в котором выдается перечень всех имен файлов, содержащихся в каталоге /etc:
opendir(ETC,"/etc") II die "no etc?: $!";
while ($name = readdir(ETC)) f t скалярный контекст, по одному на цикл
print "$name\n"; #выводит ., .., passwd, group и т.д. 1 closedir(ETC) ;
А вот как можно получить все имена в алфавитном порядке с помощью функции sort:
opendir(ETC,"/etc") || die "no etc?: $!";
foreach $name (sort readdir(ETC)) ( # списочный контекст с сортировкой
print "$name\n"; #выводит ., .., passwd, group и т.д. ) closedir(ETC) ;
В этот список включены имена файлов, которые начинаются с точки. Это не похоже на результат развертывания, выполненного с использованием <*>, при котором имена, начинающиеся с точки, не возвращаются. С другой стороны, это похоже на результат работы команды echo* shell.
* Точнее говоря — это порядок, в котором имена файлов расположены в каталоге, т.е. тот же "беспорядочный порядок", в котором вы получаете файлы в ОС UNIX в результате вызова команды .find или Is -f.
** Это означает, что при работе с опцией-w вам придется использовать цикл while (defined
($name = readdir (...)).
Что такое дескриптор файла
Дескриптор файла в Perl-программе — это имя соединения для ввода-вывода между вашим Perl-процессом и внешним миром. Мы уже видели дескрипторы файлов и пользовались ими, сами того не зная: stdin — это дескриптор, которым именуется соединение между Perl-процессом и стандартным вводом UNIX. Аналогичным образом в Perl существует stdout (для стандартного вывода) и stderr (для стандартного вывода ошибок). Это те же самые имена, что и используемые библиотекой стандартного ввода-вывода в С и C++, которую Perl задействует в большинстве операций ввода-вывода.
Имена дескрипторов файлов похожи на имена помеченных блоков, но они берутся из другого пространства имен (поэтому у вас может быть скаляр $fred, массив Sfred, хеш %fred, подпрограмма sfred, метка fred, а теперь и дескриптор файла fred). Как и метки блоков, дескрипторы файлов используются без специального префиксного символа, поэтому их можно спутать с существующими или возможными в будущем зарезервированными словами (для команд, подпрограмм и др.). Рекомендуем составлять дескрипторы файлов только из прописных букв. Во-первых, они будут хорошо выделяться в тексте программы, и, во-вторых, благодаря этому программа не даст сбой при введении нового зарезервированного слова.
Что такое формат
Помимо всего прочего, Perl — это, как мы уже говорили, "практический язык извлечений и отчетов". Теперь самое время узнать, почему его называют языком отчетов.
В Perl существует понятие шаблона для написания отчета, который называется форматом. В формате определяется постоянная часть (заголовки столбцов, метки, неизменяемый текст и т.д.) и переменная часть (текущие данные, которые вы указываете в отчете). Структура формата близка собственно к структуре вывода и подобна форматированному выводу в Коболе или выводу при помощи клаузы print using в некоторых реализациях Бейсика.
Использование формата предполагает выполнение трех операций:
1. Определение формата.
2. Загрузка данных, подлежащих печати, в переменные части формата (поля).
3. Вызов формата.
Чаще всего первый этап выполняется один раз (в тексте программы, чтобы формат определялся во время компиляции)*, а два остальных этапа — многократно.
* Форматы можно создавать и во время выполнения, пользуясь функцией eval (см. книгу Programming Perl) и man-страницу perlform(l).
Что такое хеш
Хеш* похож на массив, который мы рассматривали выше, тем, что представляет собой набор скалярных данных, отдельные элементы которого выбираются по индексному значению. В отличие от массива, индексные значения хеша — не малые неотрицательные целые, а произвольные скаляры. Эти скаляры (называемые ключами) используются для выборки значений из массива.
Элементы хеша не стоят в каком-то конкретном порядке. Можете рассматривать их как стопку библиографических карточек. Верхняя половина каждой карточки — это ключ, а нижняя — значение. Каждый раз, когда вы помещаете в хеш значение, создается новая карточка. Когда нужно изменить значение, вы указываете ключ, и Perl находит необходимую карточку. Поэтому порядок карточек, по сути дела, роли не играет. Perl хранит все карточки (т.е. пары ключ-значение) в особом внутреннем порядке, который облегчает поиск конкретной карточки, поэтому при поиске не приходится просматривать все пары. Порядок хранения карточек изменять нельзя, так что даже и не пытайтесь**.
Что такое скалярные данные
Скаляр — это простейший вид данных, которыми манипулирует Perl. Скаляр — это либо число (допустим, 4 или 3.25е20), либо строка символов (например, hello или Gettysburg Address). Хотя в общем-то числа и строки — это совершенно разные вещи, в Perl они используются практически как взаимозаменяемые понятия, поэтому мы опишем их в одной главе.
Над скалярной величиной можно производить операции (например, суммирование или конкатенацию), полученный результат, как правило, также является скаляром. Скалярную величину можно сохранять в скалярной переменной. Скаляры можно читать из файлов и с устройств, а также записывать в файлы и на устройства.
Числа
Хотя скаляр — это либо число, либо строка*, в данный момент нам будет полезно рассмотреть их отдельно. Итак, сначала числа, а через минуту — строки.
В Perl для всех чисел используется один и тот же внутренний формат
Как станет ясно из нескольких следующих абзацев, можно задавать и целые (чисто числовые значения, например 17 или 342), и числа с плавающей запятой (действительные числа, например 3,14 или 1,35, умноженное на 1025). При этом
* Или ссылка, но это более сложная тема.
во внутренних вычислениях Perl использует только значения с плавающей запятой двойной точности*. Это значит, что внутренних целых величин в Perl нет; целочисленная константа в программе рассматривается как эквивалентное значение с плавающей запятой**. Вы, вероятно, не заметите этого преобразования (или попросту проигнорируете его), но в любом случае не нужно искать целочисленные операции (как дополнение к операциям с плавающей запятой), ибо их попросту нет.
Литералы с плавающей запятой
Литерал —
это способ представления величины в тексте Perl-программы. В своей программе вы можете называть литерал константой, но мы будем пользоваться термином литерал.
Литералы — это способ представления данных в исходном тексте вашей программы как входной информации для компилятора Perl. (Данные, которые читаются из файлов или записываются в файлы, трактуются похоже, но не совсем так.)
Perl принимает полный набор литералов с плавающей запятой, которыми пользуются программисты, работающие на С. Допускаются числа с десятичными запятыми и без них (включая необязательный префикс "плюс" или "минус"). Кроме того, можно использовать показатель степени числа десять (экспоненциальное представление) с буквой Е. Например:
1.25 # около единицы с четвертью 7.25е45 # 7,25, умноженное на 10 в 45-й степени (большое число)
-6.5е24 # минус 6,5, умноженное на 10 в 24-й степени
# ("большое" отрицательное число)
-12е-24 # минус 12, умноженное на 10 в минус 24-й степени
# (очень маленькое отрицательное число)
-1.2Е-23 # еще одна форма записи этого числа
Целочисленные литералы
Целочисленные литералы также весьма просты, например:
12 15
-2004 3485
Не начинайте целое число с нуля, потому что Perl поддерживает восьмеричные и шестнадцатеричные литералы. Восьмеричные числа начинаются с нуля, а шестнадцатеричные — с Ох или ох***. Шестнадцатеричные цифры
- Значение с плавающей запятой двойной точности — это то, что компилятор С, который компилировал Perl, использовал для объявления double.
-* Если только вы не используете "целочисленный режим", но по умолчанию он не включен.
-** "Начальный нуль" работает только в литералах, но не действует при автоматическом преобразовании строк в числа. Строку данных, выглядящую как восьмеричное или шест-надцатеричное значение, можно преобразовать в число с помощью функций oct или hex.
от А до F (в любом регистре) обозначают обычные цифровые значения от 10 до 15. Например:
0377 t восьмеричное 377, то же самое, что десятичное 255
-Oxff # отрицательное шестнадцатеричное FF, то же самое, что десятичное -255
Строки
Строки — это последовательности символов (например, hello). Каждый символ представляет собой 8-битовое значение из 256-символьного набора (при этом символ NUL ничего особенного, как в некоторых языках, собой не представляет).
Самая короткая из возможных строк не содержит ни одного символа. Самая длинная строка заполняет всю наличную память (но сделать с ней что-либо вы вряд ли сможете). Это соответствует принципу "отсутствия встроенных ограничений", которому Perl следует при каждой возможности. Обычно строки представляют собой последовательности букв, цифр и знаков препинания, коды которых лежат в диапазоне ASCII 32 — 126. Однако возможность наличия в строке любого символа с кодом от 0 до 255 означает, что вы можете создавать, просматривать необработанные двоичные данные и манипулировать ими как строками — то, что вызвало бы серьезные трудности в большинстве других языков. (Например, можно "залатать" свою операционную систему, прочитав нужный фрагмент кода как Perl-строку, внеся изменение и записав результат обратно на диск.)
Как и числа, строки могут иметь литеральное представление (способ представления строкового значения в Perl-программе). Литеральные строки бывают двух видов: в одинарных кавычках и в двойных кавычках*. Есть еще одна похожая форма: строка в обратных кавычках ('вот такая'). Это не столько литеральная строка, сколько способ выполнения внешних команд и получения их результатов. Более подробно этот вопрос рассматривается в главе 14.
Строки в одинарных кавычках
Строка в одинарных кавычках
представляет собой последовательность символов, заключенную в одинарные кавычки. Одинарные кавычки частью самой строки не являются, они служат лишь для того, чтобы Perl мог определить начало и окончание строки. Все символы, стоящие между кавычками (в том числе символы новой строки, если строка разбивается на несколько экранных строк), действительны внутри строки. Два исключения:
чтобы вставить одинарную кавычку в строку, уже заключенную в одинарные кавычки, поставьте перед ней обратную косую черту. Для того чтобы вставить
* Есть также here -строки, похожие на here-локу менты shell. Они рассматриваются в главе 19, Программирование CGI. См. также главу 2 книги Programming РеНи man-страницу pcrldata( I).
в строку в одинарных кавычках обратную косую, поставьте перед ней еще одну обратную косую черту. Примеры:
'hello' # пять символов: h, e, 1, 1, о
'don\'t' # пять символов: d, о, п, одинарная кавычка, t
'' # пустая строка (без символов)
'silly\\me' # silly, обратная косая, те
'hello\n' # hello, обратная косая, п
'hello
there' # hello, новая строка, there (всего 11 символов)
Отметим, что пара символов \п внутри строки в одинарных кавычках интерпретируется не как символ новой строки, а как два символа: обратная косая и п. (Обратная косая имеет специальное значение только в том случае, если за ней следует еще одна обратная косая или одинарная кавычка.)
Строки в двойных кавычках
Строка в двойных кавычках
действует почти так же, как С-строка. Это тоже последовательность символов, но на этот раз — заключенная в двойные кавычки. Теперь, однако, обратная косая приобретает полную силу и может задавать определенные управляющие символы и вообще любой символ в восьмеричном и шестнадцатеричном форматах. Вот некоторые строки в двойных кавычках:
"hello world\n" # hello world и новая строка
"new \177" # new, пробел и символ удаления (восьмеричное 177)
"coke\tsprite" # coke, знак табуляции, sprite
Широко используются сочетания обратной косой черты с различными символами (так называемая управляющая последовательность с обратной косой).
Полный перечень управляющих последовательностей, которые применяются в строках в двойных кавычках, приведен в табл. 2.1.
Таблица 2.1. Управляющие последовательности
Конструкция | Значение |
\п | Переход на новую строку (Newline) |
\г | Возврат к началу строки (Return) |
\t | Табуляция |
\f | Переход к новой странице (Formfeed) |
\b | Возврат на предыдущую позицию с удалением символа (Backspace) |
\а | Сигнал |
\е | Escape |
Конструкция | Значение |
\007 | Восьмеричное ASCII-значение (в данном случае 07 = сигнал) |
\x7f | Шестнадцатеричное ASCII-значение (в данном случае 7f = удалить) |
\сС | Управляющий символ (здесь Ctri+C) |
\\ | Обратная косая |
\" | Двойная кавычка |
\1 | Перевод следующей буквы в нижний регистр |
\L | Перевод в нижний регистр всех последующих букв до \е |
\U | Перевод в верхний регистр следующей буквы |
\U | Перевод в верхний регистр всех следующих букв до \Е |
\Q | Заключить в обратные косые все небуквенные и все нецифровые символы до \Е |
\E | Отменить действие последовательности \L, \u или \Q |
DBM-базы данных и DBM-хети
В большинстве UNIX-систем єсть стандартная библиотека, которая называется DBM. Зта библиотека представляет собой простую систему управления базами данных, которая позволяет программам записывать набор пар ключ-значение в пару файлов. В зтих файлах хранятся значення базы данных в промежутках между вызовами программ, использующих ее, и зти программы могут вводить в базы данных новые значення, обновлять суще-ствующие и удалять старые.
Библиотека DBM довольно проста, но, учитывая ее доступность, неко-торые системные программы активно используют зту библиотеку для своих довольно скромных нужд. Например, sendmail (а также ее варианты и производные) хранит базу данных aliases (соответствие адресов злектронной почты и имен получателей) как DBM-базу данных. Самое популярнеє ПО телеконференций Usenet
использует DBM-базу данных для хранения инфор-мации о текущих и недавно просмотренных статьях. Главные файлы базы данных Sun NTS (урожденной YP) также хранятся в формате DBM.
Per! обеспечивает доступ к такому же механизму DBM довольно умным способом: посредством процесса, похожего на открытие файла, с DBM-базой данных можно связать хеш. Зтот хеш (называемый DBM-массивом) исполь-зуется для доступа к DBM-базе данных и внесення в нее изменений.
Создание нового злемента в зтом массиве влечет за собой немедленное изменение в базе данных. Удаление злемента приводит к удалению значення из DBM-базы данных и т.д.*
Размер, количество и вид ключей и значений в DBM-базе данных ограничены. В зависимости от того, какой версией библиотеки DBM вы пользуетесь, зти же ограничения могут иметь место и для DBM-массива. Подробности см. на man-странице AnyDBM_File. В общем, если вы сумеете сделать так, чтобы и ключи, и значення упаковывались не больше чем в 1000 символов с произвольными двоичными значеннями, то все будет нормально.
Дескрипторы каталогов
Если в вашей конкретной разновидности операционной системы имеется библиотечная функция readdir
или ее функциональный эквивалент, Perl обеспечивает доступ к этой программе (и ее спутницам) с помощью дескрипторов каталогов. Дескриптор каталога — это имя из отдельного пространства имен, и предостережения и рекомендации, которые касаются дескрипторов файлов, касаются также и дескрипторов каталогов (нельзя использовать зарезервированные слова, рекомендуется использовать верхний регистр). Дескриптор файла fred и дескриптор каталога fred не связаны между собой.
Дескриптор каталога представляет собой соединение с конкретным каталогом. Вместо чтения данных (как из дескриптора файла) вы используете дескриптор каталога для чтения списка имен файлов в этом каталоге. Дескрипторы каталогов всегда открываются только для чтения; нельзя использовать дескриптор каталога для изменения имени файла или удаления файла.
Если функции readdir() и ее аналогов в библиотеке нет (и при инсталляции языка Perl никакую замену вы не предусмотрели), то использование любой из этих программ приведет к фатальной ошибке и ваша программа компиляцию не пройдет: она аварийно завершится до выполнения первой строки кода. Perl всегда старается изолировать вас от влияния рабочей среды, но такие чудеса ему не подвластны.
Домашняя страница Perl
Если у вас есть доступ к World Wide Web, посетите домашнюю страницу Perl по адресу http://www.perl.com/perl/. Здесь вы узнаете, что нового произош-ло в мире Perl, сможете получить исходный код и номера портов, докумен-тацию, модули третьих фирм, базу данных ошибок Perl, информацию о списках рассылки и т.д. На этом узле имеется также сервис мультиплек-сирования CPAN, который описан ниже.
Дополнительная литература
Естественно, о модулях, ссылках, объектах и Web-программировании можно рассказать гораздо больше, чем вы узнали из этой маленькой главы. О CGI-программировании можно написать отдельную книгу — и таких книг уже написаны десятки. Приведенный ниже перечень поможет вам продолжить свои исследования в этой области.
• Файлы документации CGI.pm.
• Библиотека LWP из CPAN.
• CGI Programming on the World Wide Web by Shishir Gundavaram (O'Reilly & Associates).
• Web Client Programming with Perl by Clinton Wong (O'Reilly & Associates).
• HTML: The Definitive Guide by Chuck Musciano and Bill Kennedy (O'Reilly & Associates).
• How to Setup and Maintain a Web Site by Lincoln Stein (Addison-Wesley).
• CGI Programming in С and Perl by Thomas Boutell (Addison-Wesley).
• Сборник FAQ no CGI Ника Кью.
• Man-страницы: perltoot, perlref, perlmod, perlobj.
Допустим, вы хотите найти все
if ($name =~ /^Randal/) (
+f да, совпадает ) else ( ## нет, не совпадает
Обратите внимание на то, что регулярное выражение выделяется косой чертой с обеих сторон. Пробелы и другие пробельные символы, заключенные между косыми, имеют значение, поскольку они являются частью строки.
Это почти решает нашу задачу, но не позволяет выбрать randal или отклонить Randall. Чтобы принять randal, мы добавляем опцию игнорирования регистра — прописную букву i после закрывающей косой. Чтобы отклонить Randall, мы вводим в регулярное выражение специальный маркер границы слова (подобно тому как это делается в vi и в некоторых версиях grep) в форме \b. Это гарантирует, что символ, следующий в регулярном выражении за первой буквой 1, не является еще одной буквой. В результате наше регулярное выражение принимает вид /^randal\b/i, что означает "слово randal, стоящее в начале строки, за которым нет ни буквы, ни цифры, при этом регистр не имеет значения". Объединив этот фрагмент с остальной частью программы, получим:
#! /usr/bin/perl %words = qw ( fred camel barney llama betty alpaca wilma alpaca );
print "What is your name? "; $name = <STDIN>; chomp ($name); if ($name =~ /^randal\b/i) {
print "Hello, Randal! How good of you to be here!\n"; } else {
print "Hello, $name! \n"; # обычное приветствие $secretword = $words {$name}; # получить секретное слово if ($secretword eq "") { # не найдено
$secretword = "groucho"; t конечно, можно использовать }
print "What is the secret word? "; $guess = <STDIN>;
chomp ($guess , while ($guess ne Ssecretword) (
print "Wrong, try again. What is the secret word? "; $guess = <STDIN> ; chomp ($guess) ;
Как видите, эта программа уже довольно далека от простенькой Hello, World. Хотя она и очень мала, но вполне работоспособна, причем краткость программы достигается весьма небольшими усилиями. В этом — стиль Perl.
В Perl имеется все, что необходимо для работы с регулярными выражениями, т.е. он предоставляет все возможности, которые обеспечивает любая стандартная утилита UNIX (и даже некоторые нестандартные). Способ сопоставления строк, используемый в Perl, является, чуть ли не самым быстрым сравнительно с другими языками, поэтому производительность системы при выполнении Perl-программ никоим образом не снижается. (Написанная на Perl grep-подобная программа часто превосходит прилагаемую поставщиками программу grep
на С*. Это значит, что grep не выполняет толком даже единственную свою задачу.)
Справедливость для всех
Итак, теперь я могу ввести Randal, randal или Randal L. Schwartz, но как быть с остальными? Барни должен вводить в точности barney (ему нельзя ввести даже пробел после barney).
Чтобы быть справедливыми по отношению к Барни, мы должны перед поиском имени в таблице взять первое слово из того, что введено, а затем заменить все его символы символами нижнего регистра. Это делается с помощью двух операций — операции подстановки, которая находит регулярное выражение и заменяет его строкой, и операции перевода,
которая переводит символы этой строки в нижний регистр.
Сначала — операция подстановки: мы хотим взять содержимое переменной $ name, найти первый специальный (не использующийся в словах) символ и убрать все символы, начиная с того места, где он стоит, и до конца строки. Искомое регулярное выражение имеет вид /\w.*/. Здесь \w обозначает специальный символ (т.е. все кроме буквы, цифры и знака подчеркивания), а . * обозначают любые символы с этого места до конца строки. Чтобы убрать эти символы, нужно взять ту часть строки, которая совпадает с рассматриваемым регулярным выражением, и заменить ее пустой строкой:
$name =~ s/\W.*//;
Мы используем ту же операцию =~, что и раньше, но справа у нас теперь стоит операция подстановки — буква s,
за которой следуют заключенные между двумя косыми регулярное выражение и строка. (Строка в данном
Однако GNU-версия утилиты egrep
выполняет эту операцию гораздо быстрее, чем Perl.
примере — это пустая строка между второй и третьей косыми.) Эта операция выглядит и выполняется во многом так же, как операции подстановки в программах-редакторах.
Теперь для того, чтобы перевести все оставшиеся символы в нижний регистр, мы преобразуем эту строку с помощью операции tr*. Она очень похожа на UNIX-команду tr, т.е. получает список искомых символов и список символов, которыми искомые символы заменяются. В нашем примере мы, чтобы перевести содержимое переменной $name в нижний регистр, используем такую запись:
$name =~ •tr/A-Z/a-z/;
Между косыми заключены списки искомых и заменяющих их символов. Дефис между буквами а и z обозначает все символы, находящиеся между ними, т.е. у нас есть два списка, в каждый из которых включено по 26 символов. Когда tr находит символ из какой-либо строки первого списка, он заменяется соответствующим символом из второго списка. В результате все прописные буквы А, В, С и т.д. становятся строчными**. Объединяя эти строки с остальной частью программы, получаем:'
#!/usr/bin/perl Swords " qw ( fred camel bamey llama betty alpaca wilma alpaca
print "What is your name? "; $name = <STDIN>; chomp ($name);
$original name = $name; # сохранить для приветствия $name =~ s/\W.*//; # избавиться от всех символов, следующих после первого слова
$name =~ tr/A-Z/a-z/; # перевести все в нижний регистр if ($name eq "randal") ( * теперь можно так сравнить
print "Hello, Randal! How good of you to be here!\n"; else (
print "Hello, $original_name! \n"; ^обычное приветствие $secretword = $words($namel; # получить секретное слово if ($secretword eq "") ( #
не найдено
$secretword == "groucho"; 4 конечно, можно использовать }
print "What is the secret word? "; $guess = <STDIN>; chomp ($guess); while ($guess ne $secretword) (
* Символы с диакритическими знаками такому переводу не поддаются. Подробности см. на man-странице рег11оса1е(\)
в версии языка Pel-15.004.
** Специалисты заметят ,что мы также могли написать нечто вродез/(\3*) .*/\L$1/, чтобы сделать все это за один присест, но специалисты, вероятно, не будут читать данный раздел.
print "Wrong, try again. What is the secret word? "; $guess = <STDIN>; chomp ($guess);
Обратите внимание на то, что сопоставление с регулярным выражением для слова Randai вновь выполняется с помощью обычной операции сравнения. Дело втом, что и RandaL L. Schwartz, и Randai после подстановки и перевода превращаются в randai. Для всех остальных пользователей справедливо то же самое, потому что Fred и Fred Flinstone превращаются во fred; Barney Rubbie И Barney, the little guy — В barney И Т.Д.
Итак, благодаря всего нескольким операторам наша программа стала гораздо более дружелюбной. Вы увидите, что проведение сложных манипуляций со строками посредством всего лишь нескольких нажатий клавиш — одна из многих сильных сторон языка Perl.
Отметим, однако, что в процессе обработки имени (т.е. при его модификации, необходимой для проведения операции сравнения и поиска соответствия в таблице) первоначально введенное имя уничтожается. Поэтому перед обработкой имени программа сохраняет его в переменной $original name. (Как и имена в С, имена переменных в Perl состоят из букв, цифр и знаков подчеркивания, причем длина их практически не ограничена.) Благодаря этому мы впоследствии сможет ссылаться на $ original name.
В Perl имеется много способов, позволяющих проводить анализ и изменение символов в строках. С большинством из них вы познакомитесь в главах 7 и 15.
Повышение степени модульности
Теперь, когда мы добавили так много строк к нашему первоначальному коду, нам при его просмотре будет непросто уловить общую логику построения программы. Поэтому было бы неплохо отделить высокоуровневую логику (запрос имени, циклы, используемые для обработки введенных секретных слов) от низкоуровневой (сравнение введенного секретного слова с заданным). Это необходимо сделать, например, для облегчения понимания программы другими пользователями, или по той причине, что один человек пишет высокоуровневую часть, а другой — низкоуровневые фрагменты.
В Perl существует понятие подпрограммы,
имеющей параметры и возвращаемые значения. Подпрограмма определяется в программе один раз, но использоваться может многократно путем вызова ее из любого места программы.
Давайте создадим для нашей маленькой, но быстро растущей программы подпрограмму good_word, которая будет принимать имя и вариант слова и возвращать значение "истина", если это слово введено правильно, и "ложь", если слово набрано неправильно. Определение такой подпрограммы выглядит следующим образом:
sub good_word (
my($somename,$someguess) = @_; # назвать параметры $somename =~ s/\W.*//; # избавиться от всех символов, стоящих после
# первого слова
$somename =~ tr/A-2/a-z/; t перевести все символы в нижний регистр if ($somename eq "randal") ( # не нужно угадывать
return 1; # возвращаемое значение — true I elsif (($words($somename} 11 "groucho") eq $someguess) (
return 1; # возвращаемое значение — true ) else { return 0; * возвращаемое значение —
false
Во-первых, определение подпрограммы состоит из зарезервированного для этих целей слова sub, за которым идет имя подпрограммы и блок ее кода (выделенный фигурными скобками). Это определение может стоять в тексте программы где угодно, но большинство программистов помещают его в конец.
Первая строка в данном конкретном определении — это операция присваивания, с помощью которой значения двух параметров подпрограммы копируются в две локальные переменные с именами $somename и $someguess (директива ту ( ) определяет эти переменные как локальные для блока, в который они входят (в данном случае для всей подпрограммы), а параметры первоначально находятся в специальном локальном массиве с именем @ .)
Следующие две строки удаляют символы, стоящие после имени (точно так же, как в предыдущей версии программы).
Оператор if-elsif-else позволяет определить, является ли введенный пользователем вариант слова ($someguess) верным для имени ($somename). Имя Randal не должно попасть в эту подпрограмму, но даже если и попадет, то любой вариант его ввода будет принят как правильный.
Для того чтобы подпрограмма немедленно возвращала в вызвавшую ее программу указанное в подпрограмме значение, можно воспользоваться оператором возврата. В отсутствие явного оператора возвращаемым значением является последнее выражение, вычисленное в подпрограмме. Мы посмотрим, как используется возвращаемое значение, после того как дадим определение подпрограммы.
Проверка в части eisif выглядит довольно сложной; давайте разобьем ее на фрагменты:
($words($somename) И "groucho") eq $someguess
Первый элемент в круглых скобках обеспечивает проведение уже знакомого нам хеш-поиска, в результате которого отыскивается некоторое значение в массиве %words на основании ключа, полученного из массива $somename. Знак 1 1, стоящий между этим значением и строкой groucho, обозначает операцию ИЛИ, аналогичную той, что используется в языке С, awk и в различных shell. Если поиск в хеше даст некоторое значение (это
значит, что ключ $ some name находился в хеше), то оно и будет являться значением данного выражения. Если ключ найден не был, то используется строка groucho. Это весьма характерно для Perl: приводится некоторое выражение, а затем с помощью операции 1 1 для него указывается значение по умолчанию на тот случай, если результатом поиска является значение "ложь".
В любом случае, будь то значение из хеша или принимаемое по умолчанию значение groucho, мы сравниваем его с вариантом, вводимым пользователем. Если результат сравнения положителен, возвращается единица, в противном случае возвращается нуль.
Выразим все это в виде правила: если имя — randal или если вводимый пользователем вариант соответствует одному из имен, находящемуся в массиве %words (со значением по умолчанию groucho, если имя в массиве не найдено), то подпрограмма возвращает 1; иначе подпрограмма возвращает 0. Теперь давайте свяжем все новые строки с остальной частью программы:
#! /usr/bin/perl Swords = qw( fred camel barney llama betty alpaca wilma alpaca ) ;
print "What is your name? "; $name = <STDIN>; chomp ($name) ; if ($name "~ /^randal\b/i) ( t обратно на другой путь :-)
print "Hello, Randal! How good of you to be here!\n"; I elscj/ {
print "Hello, $name! \n"; t обычное приветствие print "What is the secret word? "; $guess = <STDIN>; chomp ($guess);
while (! good word( $name, $guess)) ( print "Wrong, try again. What is the secret word? "; ?guess “ <STDIN>; chomp ($guess) ;
t... здесь вставляется определение подпрограммы good_word() .,,]
Обратите внимание: мы вновь вернулись к использованию регулярного выражения для проверки наличия имени Randal в массиве, потому что теперь уже в основной программе не требуется выделять первое имя и заменять все его символы символами нижнего регистра.
Наибольшее отличие этой программы от предыдущей состоит в том, что здесь используется цикл while, содержащий подпрограмму &good_word. При вызове этой подпрограммы ей передаются два параметра, $name и $auess. Значение $somename устанавливается равным первому параметру,
в данном случае $name. Аналогичным образом $someguess передается во втором параметре, $guess.
Значение, возвращаемое этой подпрограммой (1 или 0, если вы помните приведенное выше правило), логически инвертируется префиксной операцией ! (логическое НЕ). Эта операция возвращает значение "истина", если следующее за ней выражение ложно, или "ложь", если оно истинно. Результат этой операции управляет циклом while. Можете читать такую запись как "до тех пор, пока слово угадано неправильно...". Многие хорошо написанные Perl-программы очень похожи на обычный английский язык — если, конечно, не позволять себе много вольностей ни с языком Perl, ни с английским. (Но Пулитцеровскую премию даже за очень хорошую программу вам не дадут.)
Обратите внимание: при разработке этой подпрограммы предполагалось, "то значение хеша %words задается в основной программе.
Столь деликатный подход к использованию глобальных переменных вызван тем, что обращаться с ними нужно очень аккуратно. Говоря в общем, переменные, не созданные с помощью ту, глобальны для всей программы, тогда как переменные ту действуют только до завершения выполнения блока, в которым они были объявлены. Но не беспокойтесь: в языке Perl имеется множество других разновидностей переменных, включая переменные, локальные для файла (или пакета), и переменные, локальные для функции, которые сохраняют свои значения от вызова к вызову — а это как раз то, что мы могли бы здесь использовать. Впрочем, на данном этапе вашего знакомства с Perl изучение этих переменных только осложнило бы вам жизнь. Когда вы будете достаточно готовы к этому, посмотрите, что говорится о контекстах, подпрограммах, модулях и объектах в книге Programming Perl, или обратитесь к диалоговой документации, имеющейся на man-страницах perlsub(\), perlmod(l), perlobJ(l) и perltoot(l).
Перенос списка секретных слов в отдельный файл
Допустим, вы хотели бы использовать список секретных слов в трех программах. Если вы сохраните этот список так, как мы уже это делали, нам придется корректировать все три программы (если, например, Бетти решит, что ее секретным словом должно быть не alpaca, a swine). Это может стать настоящим кошмаром, особенно если Бетти отличается непостоянством.
Поэтому давайте поместим список слов в файл, а затем, чтобы ввести список в программу, просто прочитаем файл. Для этого нужно создать канал ввода-вывода, который называется дескриптором файла. Ваша Perl-программа автоматически получает три дескриптора файлов, stdin, stdout и stderr, которые соответствуют трем стандартным каналам ввода-вывода в большинстве сред программирования. Мы уже используем дескриптор stdin для чтения данных, поступающих от пользователя, запускающего нашу программу. Теперь нужно просто создать для выбранного нами файла другой дескриптор.
Это делается с помощью следующего кода:
sub init words (
open ('..'ORDSLIST, "wordslist"); while ($name = <WORDSLIST” (
chomp ($name); $word = <WORDSLIST>; chomp ($word); $words ($name} = Sword;
close (WORDSLIST) ;
Мы помещаем его в подпрограмму, чтобы не загромождать основную программу. Это означает также, что позже мы сможем изменить место хранения списка слов и даже его формат.
Произвольно выбранный формат списка слов — один элемент в строке с чередованием имен и секретных слов. Для нашей базы данных мы имели бы такой список:
fred camel barney llama betty alpaca wilma alpaca
Функция open инициализирует дескриптор файла wordslist, связывая его с файлом wordslist, находящимся в текущем каталоге. Отметим, что перед этим дескриптором не ставится никакого забавного символа, вроде тех трех, что предваряют наши переменные. Кроме того, дескрипторы файлов обычно записываются прописными буквами (хотя это и не обязательно); причины этого мы рассмотрим позднее.
При выполнении цикла while читаются строки из файла wordslist (через дескриптор файла wordslist) по одной при каждом проходе цикла. Каждая строка заносится в переменную $name. По достижении конца файла операция <wordslist> возвращает пустую строку*, которая для цикла while означает "ложь", и завершает цикл.
Если бы вы выполняли программу с ключом -w, вам пришлось бы проверять, определено ли полученное возвращаемое значение. Пустая строка, которую возвращает операция <wordslist>, не совсем пуста: это опять значение undef. В тех случаях, когда это важно, проверка выражения на значение undef производится функцией defined. При чтении строк из файла эта проверка выполнялась бы следующим образом:
while ( defined ($name = <WORDSLIST) ) (
* На самом деле это опять undef, но для понимания данного материала сказанного достаточно.
Но если бы вы были еще более осторожны, вы, вероятно, проверили бы также и то, возвращает ли функция open значение "истина". Это, кстати, в любом случае неплохая идея. Для выхода из программы с сообщением об ошибке в случае, если что-то работает не так, часто используется встроенная функция die. Мы рассмотрим пример этой функции в следующей версии нашей программы.
С другой стороны, при нормальном развитии событий мы считываем строку (включая символ новой строки) в переменную $ name. Сначала с помощью функции chomp убирается символ новой строки, затем нужно прочитать следующую строку, чтобы получить секретное слово и сохранить sro в переменной $word. Символ новой строки при этом тоже убирается.
Последняя строка цикла while помещает $word в %words с ключом $name, чтобы переменную $word могла использовать остальная часть программы.
По завершении чтения файла его дескриптор можно использовать повторно, предварительно закрыв файл с помощью функции close. (Дескрипторы файлов автоматически закрываются в любом случае при выходе из программы, но мы стараемся быть аккуратными. Однако если бы мы были по-настоящему аккуратными, мы бы даже проверили бы, возвращает ли ^lose значение "истина" в случае, если раздел диска, в котором был файл, решил "отдохнуть", если сетевая файловая система стала недосягаемой или произошла еще какая-нибудь катастрофа. Такое ведь иногда случается. Законы Мерфи никто не отменял.)
Сделанное выше определение подпрограммы может идти после другого аналогичного определения или перед ним. Вместо того чтобы помещать определение %words в начало программы, мы можем просто вызывать эту подпрограмму в начале выполнения основной программы. Один из вариантов компоновки общей программы может выглядеть так:
fr! /usr/bin/perl init words () ;
print "What is your name? "; $name = <STDIN>; ;homp $name; if ($name =~ /^randal\b/i) ( * обратно на другой путь :-)
print "Hello, Randal! How good of you to be here!\n"; I else (
print "Hello, $name! \n"; # обычное приветствие •print "What is the secret word? "; $guess = <STDIN>; ^homp ($guess) ;
^hile (! good word($name, $guess)) I print "Wrong, try again. What is the secret word? "; $guess = <STDIN>; :homp ($guess);
} ## далее — подпрограммы
sub init_words ( open (WORDSLIST, "wordslist") 11
die "can' tl open woraJ.isT:: ^' .ihile ( defined ($name = <WORDSLIST”) ( chomp ($name) ; $word = <WORDSLIST>; chomp $word; $words( $name) = $word; I close (WORDSLIST) II die "couldn't close wordlist: $"'
iub good word (
my($somename,$someguess) = @_; * перечислить параметры $somename =~ s/\W.*//; # удалить все символы, стоящие после первого слова # первое слово
'somename =~ tr/A-Z/a-z/; # перевести все символы в нижний регистр :.f ($somename eq "randal") ) <t не нужно угадывать
return 1; * возвращаемое значение - true elsif (($words($somename) II "groucho") eq $someguess) (
return 1; * возвращаемое значение - true else ( •eturn 0; i возвращаемое значение — false
Теперь написанный нами код начинает выглядеть как настоящая "взрослая" программа. Обратите внимание: первая выполняемая строка — вызов подпрограммы init_word() . Возвращаемое значение в последующих вычислениях не используется, и это хорошо, потому что мы не возвратили ничего заслуживающего внимания. В данном случае это гарантированно значение "истина" (в частности, значение 1), потому что если бы close не выполнилась, то die вывела бы сообщение в stderr и вышла из программы. Функция die подробно описывается в главе 10, но поскольку очень важно проверять возвращаемые значения всего, что может завершиться неудачно, мы возьмем за правило использовать эту функцию с самого начала. Переменная $! (тоже рассматривается в главе 10) содержит системное сообщение об ошибке, поясняющее, почему данный системный вызов завершился неудачно.
Функция open используется также для открытия файлов при выводе в них информации и открытия программ как файлов (здесь она лишь упомянута). Полное описание этой функции будет дано гораздо позже, в главе 10.
Как обеспечить скромный уровень безопасности
'Этот список секретных слов должен меняться минимум раз в неделю!", — требует Главный Директор Списков Секретных Слов. Мы не можем, конечно, заставить пользователей еженедельно менять пароли, но должны хотя бы предупреждать их о том, что список секретных слов не изменялся в течение семи дней и более.
лучше всего делать это в подпрограмме init_words ( ) ; мы уже раоотаем в ней с файлом wordlist. Perl-операция -м возвращает значение, равное количеству дней, прошедшему с момента изменения файла или дескриптора файла, поэтому нам нужно просто посмотреть, превышает ли это значение число семь для дескриптора файла wordslist:
sub init words (
open (HORDSLIST, "wordslist") 11 die "can't open wordlist: $!";
if (-М WORDSLIST >= 7.0) ( f в соответствии с бюрократическими правилами
die "Sorry, the wordslist is older than seven days. "; I while ($name = <WORDSLIST” (
chomp ($name) ; $word = <WORDSLIST> ; chomp ($word) ; $words($name} = $word;
close (WORDSLIST) 11 die "couldn't close wordlist: $!";
Значение -м wordslist сравнивается со значением 7. Если оно больше, то мы, выходит, нарушили правила. Здесь мы видим новую операцию, операцию die, которая одним махом выводит сообщение на экран* и прерывает программу.
Остальная часть программы изменений не претерпевает, поэтому в целях экономии бумаги мы ее повторять не будем.
Помимо определения "возраста" файла мы можем узнать имя его владельца, размер, время последнего доступа к нему и все остальные сведения, хранимые системой о каждом файле. Более подробно об этом написано в главе 10.
Как предупредить пользователя, если он сбился с пути
Давайте посмотрим, как можно заставить систему посылать сообщение электронной почты всякий раз, когда пользователь указывает свое секретное слово неверно. Нам нужно модифицировать только подпрограмму good word ( ) (сказывается преимущество модульности языка Perl), потому что вся необходимая информация находится у нас там.
Почтовое сообщение будет послано вам в том случае, если вы вставите свой адрес электронной почты там, где в программе записано YOUR_AD-DRESS_HERE. Все, что нам нужно для этого сделать, это непосредственно перед тем, как возвращать из подпрограммы 0, создать дескриптор файла, который фактически будет являться процессом (mail):
ячЬ good word (
my($sornename,$someguess) = @ ; # перечислить параметры •^somename =~ s/\W.*//; t удалить все символы, стоящие после
И
первого слова
* Если точнее, то в дескриптор файла stderr, но обычно это и означает терминал.
$somename =~ tr/A-Z/a-z/; # перевести все символы в нижний регистр if ($somename eq "randal") ( # не нужно угадывать return 1; #
возвращаемое значение — true
elsif (($words($somename}ll"groucho") eq $someguess) { return 1; ff возвращаемое значение — true
else (
open MAIL, "Imail YOUR_ADDRESS_HERE"; print MAIL "bad news: $somename guessed $someguess\n"; close MAIL; return 0; 4f
возвоашаемое значение — false
первый новый оператор здесь — open, в начале второго аргумента которого стоит оператор канала (1). Он указывает, что мы открываем процесс, а не файл. Поскольку оператор канала находится перед именем команды, мы открываем процесс так, чтобы можно было осуществить в него запись. (Если поставить оператор канала в конец, а не в начало, то можно будет читать выходную информацию команды.)
Следующий оператор, print, выбирает для вывода не stdout*, а дескриптор файла, стоящий между ключевым словом print и подлежащими выводу на экран значениями. Это значит, что сообщение в конечном итоге станет входной информацией для команды mail.
Наконец, мы закрываем дескриптор файла, в результате чего запускается программа mail и передает свои данные.
Чтобы соблюсти все формальности, мы могли бы посылать не только ошибочный, но и правильный ответ, но тогда тот, кто заглядывает нам через плечо (или прячется в системе электронной почты), когда мы читаем сообщения, получил бы слишком много полезной информации.
Perl может также открывать дескрипторы файлов, вызывать команды с необходимыми аргументами, даже порождать копию текущей программы и выполнять две (или более) копии программы одновременно. Обратные кавычки (как в shell) дают возможность получать результаты работы команды как данные. Все это описывается в главе 14, так что читайте дальше.
Несколько файлов секретных слов в текущем каталоге
Давайте слегка изменим способ определения имени файла секретных слов. Вместо файла с именем wordslist будем искать в текущем каталоге нечто, заканчивающееся на .secret. Попросим shell выдать краткий перечень таких имен.
echo *. secret
" Говоря техническим языком — выбранный в текущий момент дескриптор файла. ud этом, однако, мы поговорим позже.
Как вы скоро увидите, Perl применяет похожий синтаксис имен с использованием метасимволов. Еще раз вернемся к определению подпрограммы init_words () :
;ub init words (
while ( defined($filename = glob("*.secret")) ) ( open (WORDSLIST, $filename) 11
die "can't open wordlist: $!"; f (-M WORDSLIST >= 7.0) ( while ($name = <WORDSLIST” (
chomp $name; Sword = <WORDSLIST>; chomp $word; Swords ($name ) = $word; ) ) close (WORDSLIST) II die "couldn't close wordlist: $!";
Сначала мы поместили в новый цикл while основную часть подпрограммы из предыдущей версии. Новый элемент здесь — функция glob. По историческим причинам она называется filename glob. Эта функция работает почти так же, как <stdin>: при каждом обращении к ней она возвращает очередное значение из списка имен файлов, которые соответствуют образцу shell, в данном случае * . secret. Если таких имен файлов нет, возвращается пустая строка*.
Таким образом, если текущий каталог содержит файлы fred. secret и sarney.secret, то при первом выполнении цикла while значением переменной $fiiename будетЬагпеу.secret (именадаютсяпоалфавиту).При втором выполнении цикла значением $filename будет fred .secret. Поскольку при третьем вызове функции glob она возвращает пустую строку, го третий проход не делается, так как цикл while интерпретирует это значение как "ложь", что приводит к выходу из подпрограммы.
В ходе выполнения цикла while мы открываем файл и проверяем, достаточно ли давно он обновлялся (с момента последнего изменения должно пройти не более семи дней). С этими достаточно новыми файлами мы работаем так же, как и раньше.
Отметим, что в отсутствие файлов, имена которых совпадали бы с шаблоном *. secret и "возраст" которых не превышал бы семи дней, подпрограмма завершится, не поместив ни одного секретного слова в массив %words. Это значит, что всем придется пользоваться словом groucho. Прекрасно. (В реальном
коде перед выходом из подпрограммы следовало бы ввести операцию проверки количества элементов в массиве %words — и при неудовлетворительном результате выполнить функцию die. Обратите внимание на функцию keys, когда мы дойдем до определения хешей в главе 5.)
* Да-да, опять undef. /.
Как получить список секретных слов
Итак, Главный Директор Списков Секретных Слов желает получить отчет обо всех секретных словах, используемых в текущий момент, с указанием их "возраста". Если мы на минутку расстанемся с программой проверки секретного слова, у нас будет время написать для Директора программу формирования необходимого ему отчета.
Сперва давайте получим все наши секретные слова, воспользовавшись для этого частью кода из подпрограммы init_words ( ) :
while ( defined($filename = glob("*.secret")) ) ( open (WORDSLIST, $filename) II die "can't open wordlist: $!"; if (-M WORDSLIST >= 7.0) < while ($name - <WORDSLIST” (
chomp ( $name ) ; $word = <WORDSLIST> ; chomp (Sword) ;
*** отсюда начинается новый код }
) I close (WORDSLIST) 11 die "couldn't close wordlist: $!"
К моменту достижения того места программы, где дан комментарий "отсюда начнется новый код", мы знаем три вещи: имя файла (содержится в переменной $filename), чье-то имя (в переменной $name) и секретное слово этого человека (содержится в $word). Здесь и нужно использовать имеющиеся в Perl инструменты формирования отчетов. Для этого где-то в программе мы должны определить используемый формат (обычно это делается в конце, как и для подпрограмм):
format STDOUT =
@“““““<““ @<““““ @“““““<
$filename, $name, $word
Определение формата начинается строкой format stdout=, а завершается точкой. Две строки между первой строкой и точкой — это сам формат. Первая строка формата — это строка определения полей, в которой задается число, длина и тип полей. В этом формате у нас три поля. Строка, следующая за строкой определения полей — это всегда строка значений полей. Строка значений содержит список выражений, которые будут вычисляться при использовании формата; результаты вычисления этих выражений вставляются в поля, определенные в предыдущей строке.
Вызывается определенный таким образом формат функцией write, например:
*! /usr/bin/perl
while ( defined($filename = glob("*.secret")) ) ( open (WORDSLIST, $filename) 11 die "can't open wordlist: $"';
ir (-M WORDSLIST >= i.u) { while ($name = <WORDSLIST>) { chomp ($name); $word = <WORDSLIST> ; chomp ($word) ; write; # вызвать format STDOUT в STDOUT
close (WORDSLIST) II die "couldn't close wordlist: $!"; }
format STDOUT ”
@<<<<<<<<<<<<<<< @<<<<<<<<<< @<<<<<<<<<<<< $filename, $name, $word
Когда вызывается формат. Perl вычисляет выражения, имеющиеся в строке значений, и генерирует строку, которую передает в дескриптор файла stdout. Поскольку write вызывается один раз при каждом проходе цикла, мы получим ряд строк (под одной строке для каждого секретного слова); итоговый текст будет разбит на столбцы.
Гм-м. Мы забыли дать столбцам названия. Впрочем, это достаточно легко сделать. Нужно просто ввести формат начала страницы:
format STDOUT_TOP = Page @“ $%
Filename Name Word
Этот формат называется stdout_top; он будет использоваться при первом вызове формата stdout, а затем через каждые 60 строк, выведенных в stdout. заголовки столбцов позиционируются точно по столбцам формата ^tdout, поэтому все выглядит аккуратно.
В первой строке заголовка стоит неизменяемый текст (Page) и трехзначный определитель поля. Следующая строка — строка значений полей, в данном случае она содержит выражение. Это выражение является переменной $%*, в которой содержится число выведенных страниц.
Третья строка формата пуста. А поскольку она не содержит никаких полей, то следующая за ней строка тоже пустая; она копируется прямо на вывод, вследствие чего между идущими ниже номером страницы и заголовками столбцов появляется пустая строка.
Последние две строки формата также не содержат никаких полей, поэтому они копируются на вывод в том виде, в каком записаны. Таким образом, этот формат обеспечивает создание четырех строк, одна из которых меняется от страницы к странице.
*Благодаря модулю English можно использовать для этих предопределенных скалярных переменных более мнемонические псевдонимы легко запоминающиеся названия.
Чтобы это опредиление заработало, попробуйте присоеденить его к преды-дущей программе. Perl отыщет формат начала страницы автоматически.
В Perl имеются также поля, которые центрируються и выравниваются по правому краю области вывода. Этот язык кроме тогоподдерживает одновременное выравнивание и по правому и по левому краям. Подпобно об этом мы поговорим,
когда дойдем до форматов , в главе 11.
Как сделать старые списки слов более заметными
Просматривая файлы *.secret в текущем каталоге, мы, возможно, обнаружим слишком старые файлы. До сих пор мы просто пропускали их. Давайте сделаем очередной шаг и переименуем эти файлы в *.secret.old, чтобы в перечне содержимого сразу было видно - по имени - какие файлы необходимо обновить.
Вот как выглядит подпрограмма init_words () , модифицированная для выполнения такой операции:
sub init_words {
while ($filename = <*.secret>) {
open (WORDSLIST, $filename) ||
die "can't open $filename: $!";
if (-M WORDSLIST < 7) {
while ($name = <WORDSLIST>) { chomp ($name) ;
$word = <WORDSLIST> ;
chomp ($word);
$words {$name} = $word ;
}
} else { # rename the file so it gets noticed rename ($filename, "$filename.old") ||
die "can't rename $filename.old: $!";
}
}
Обратите внимание на новую часть оператора else в блоке проверки "возраста" файлов. Если файл не обновлять семь дней и более, функция rename переименовывает его. Эта функция принимает два параметра и переименовывает файл заданный первым параметром, присваивая ему имя, указанное во втором параметре.
В Perl имеет полный набор операций, необходимый для манипулирования файлами; все, что можно сделать с файлом в С-программе, можно сделать с ним в Perl.
52
Изучаем Perl
Ведение базы данных о времени правильного ввода секретных слов
Давайте проследим за тем, когда каждый пользователь последний раз правильно вводил свой вариант секретного слова. Очевидно, единственная структура данных, которая годится для этого — хеш. Например, оператор
$last_good{$name} == time;
присваивает значение текущего времени, выраженное во внутреннем формате (некоторое большое целое, свыше 800 миллионов, число, которое увеличивается на единицу каждую секунду) элементу хеша %last_good, имеющему указанное имя в качестве ключа. В последующем это даст базу данных о времени последнего правильного ввода секретного слова каждым из пользователей, который вызывал эту программу.
При этом, однако, в промежутках между вызовами программы хеш не существует. Каждый раз, когда программа вызывается, формируется новый хеш, т.е. по большому счету мы всякий раз создаем одноэлементный хеш, а по завершении выполнения программы немедленно про него забываем.
Функция dbmopen* отображает хеш в файл (фактически в пару файлов), известный как DBM-файл. Она используется следующим образом:
dbmopen (%last_good,"lastdb",0666) ||
die "can't dbmopen lastdb: $!", $last_good{$name} = time;
dbmclose (%last_good) 11 die "can't dbrnclose lastdb: $!";
Первый оператор выполняет отображение, используя имена файлов lastdb. dir и lastdb.pag (это общепринятые имена для файлов lastdb, образующих DBM-файл). Если эти файлы необходимо создать (а это бывает при первой попытке их использования), то для них устанавливаются права доступа 0666**. Такой режим доступа означает, что все пользователи могут читать и осуществлять запись в эти файлы. Если вы работаете в UNIX-системе, то описание битов прав доступа к файлу вы найдете на man-странице chmoc/(2). В других системах chmod()
может работать так же, а может и не работать. Например, в MS- DOS для файлов не устанавливаются права доступа, тогда как в Windows NT — устанавливаются. Если уверенности нет, прочтите описание версии вашей системы.
Второй оператор показывает, что мы используем этот преобразованный хеш как первоначальный. При этом, однако, при создании или корректировке какого-либо элемента хеша автоматически обновляются файлы, образующие DBM-файл. При последующем обращении к хешу значения его элементов поступают непосредственно из его отображения на диске. Благодаря этому
* Можно также использовать низкоуровневую функцию tie с конкретной базой данных;
этот вариант подробно описан в главах 5 и 7 книги Programming Perl и на man-страницах perHfc(\) и AnyDMB_File (3).
** Фактически права доступа к этим файлам определяются в результате выполнения логической операции И над числом 0666 и текущим значением переменной umask вашего процесса.
7. Введение 53
механизму хеш физически существует и в периоды между вызовами данной программы.
Третий оператор отсоединяет хеш от DBM-файла, делая это практически так же, как операция close закрывает файл.
Хотя все вновь введенные нами операторы позволяют вести базу данных (и даже обеспечивают ее создание), у нас пока нет никакого способа получить имеющуюся в этой базе данных информацию. Для этого придется создать отдельную программку, которая выглядит примерно так:
#!/usr/bin/peri dbmopen (%last_good, "lastdb", 0666) I I
die "can't dbmopen lastdb: $!";
foreach $name (sort keys (%last_good) ) {
$when = $last_good($name);
$hours = (timed - $when) / 3600; # вычислить истекшее время в часах
write;
}
format STDOUT =
User @<<<<<<: last correct guess was
@“< hours ago.
$name, $hours
Здесь мы осуществляем несколько новых операций: выполняем цикл foreach, сортируем список и получаем значения ключей массива.
Сначала функция keys принимает имя хеша в качестве аргумента и возвращает список значений всех ключей этого хеша в произвольном порядке. Для хеша %words, описанного ранее, результат будет примерно таким:
fred, barney, betty, wiima, причем имена могут быть перечислены в произвольном порядке. В случае хеша %last_good результатом будет список всех пользователей, которые правильно ввели свои секретные слова.
Функция sort сортирует этот список в алфавитном порядке (как если бы вы пропустили текстовый файл через фильтр sort). Это гарантирует, что список, обрабатываемый следующим далее оператором foreach, всегда будет отсортирован по алфавиту.
Уже упомянутый Perl-оператор foreach очень похож на оператор foreach shell С. Он получает список значений и присваивает каждое из них по очереди какой-либо скалярной переменной (в нашем случае — переменной $name), выполняя для каждого значения по одному разу проход цикла (блок). Так, для пяти имен в списке %last_good мы делаем пять проходов, при этом каждый раз переменная $name имеет другое значение.
При выполнении цикла foreach происходит загрузка пары переменных, используемых затем в формате stdout, и вызывается этот формат. Обратите внимание: мы вычисляем "возраст" файла, вычитая записанное (в массиве) системное время из текущего времени (которое возвращает функция time), а затем деля результат на 3600 (чтобы преобразовать секунды в часы).
54
Изучаем Perl
В Perl используются также удобные способы создания и ведения текстовых баз данных (например, файла паролей) и баз данных с фиксированной длиной записей (таких, как база данных "последней регистрации", которую ведет программа login). Эти способы описаны в главе 17.
Окончательные варианты программ
Здесь вашему вниманию предлагаются программы, которые мы писалрт в этой главе, в окончательном виде. Сначала — программа-приветствие:
^!/usr/bin/peri &init_words ( ) ;
print "what is your name? " ;
$name = <STDIN>;
chomp ($name) ;
if
($name =~ /^randalVb/i) { # обратно на другой путь :-) print "Hello, Randal! How good of you to be here!\n";
} else {
print "Hello, $name! \n"; # обычное приветствие print "What is the secret word? ";
$guess = <STDIN> ;
chomp $ guess ;
while (! good_word( $name, $guess)) {
print "Wrong, try again. What is the secret word? ";
$guess = <STDIN> ;
chomp $guess;
} } dbmopen
(%last_good, "lastdb", 0666);
$last_good{$name} = time;
dbmclose (%last_good);
sub
init_words {
while ($filename = <*.secret>) { open (WORDSLIST, $filename) ||
die "can't open $filename: $!";
if (-M WORDSLIST < 7) {
while ($name = <WORDSLIST>) { chomp ($name) ;
$word = <WORDSLIST> ;
chomp ($word);
$words {$name} = $word ;
}
} else { # rename the file so it gets noticed
rename ($filename, "$filename.old") ||
die "can't rename $filename.old: $!";
}
close WORDSLIST;
}
}
sub good_word {
my($somename,$someguess) = @_; # перечислить параметры
$somename =~ s/\W.*//; # удалить все символы, стоящие после первого слова
$somename =~ tr/A-Z/a-z/; # перевести все символы в нижний регистр
if ($somename eq "randal") { # не нужно угадывать
1. Введение
55
return 1; # возвращаемое значение — true ) elsif (($wordsf$somename( II "groucho") eq $someguess) (
return 1; # возвращаемое значение — true } else {
open MAIL, "| mail YOUR_ADDRESS_HERE";
print MAIL "bad news: $somename guessed $someguess\n";
close MAIL;
return 0; # возвращаемое значение — false ”
Теперь — листер секретных слов:
#!/usr/bin/peri
while ($filename = <*.secret>) (
open (WORDSLIST, $filename) I I
die "can't open $filename: $!";
if (-M WORDSLIST < 7) {
while ($name = <WORDSLIST>) ( chomp ($name) ;
$word = <WORDSLIST> ;
chomp ($word) ;
write; # вызвать format STDOUT в STDOUT } } close (WORDSLIST) ;
format STDOUT = @<“““““““ @““<““ @““““<“
$ filename, $name, $word
format STDOUT_TOP =
Page @“
$%
Filename Name Word
И, наконец, программа выдачи времени последнего правильного ввода пароля:
#!/usr/bin/peri
dbmopen (%last good, "lastdb", 0666);
foreach $name (sort keys %last_good) (
$when = $last_good ($name);
$hours = (time - $when) / 3600; # вычислить истекшее время в часах
write;
}
format STDOUT =
User @<“““““: last correct guess was @“< hours ago.
$name, $hours
.
56 Изучаем Perl
Добавьте к этим программам списки секретных слов (файлы с именами что-то.secret,
находящиеся в текущем каталоге) и базу данных lastdb.dir и lastdb .рад, и у вас будет все, что нужно.
Доступность
Если при попытке вызвать Perl из sneil вы получите сообщение perl: not found
это значит, что вашего системного администратора еще не охватила Perl-лихорадка. Если язык Perl не инсталлирован в вашей системе, его можно получить бесплатно (или почти бесплатно).
Perl распространяется по открытой лицензии GNU (GNU Public License)*, согласно которой "вы можете распространять двоичные файлы языка Perl только в том случае, если предоставляете исходный код бесплатно, а если вы модифицируете их, вы должны распространять и код этих изменений". По сути дела это означает, что Perl распространяется бесплатно. Его исходный текст можно получить по цене пустой ленты или оплатив стоимость передачи нескольких мегабайтов информации по телефонной линии. При этом никто не может "придержать" сам Perl и послать вам просто двоичные файлы, соответствующие чьему-либо конкретному представлению о "поддерживаемых аппаратных платформах".
По сути дела, Perl не только бесплатен, но и работает достаточно хорошо почти на всем, что называет себя UNIX или UNIX-подобной системой и включает С-компилятор. Это обусловлено тем, что пакет распространяется с адаптивной программой конфигурации, называемой Configure,
которая рыщет и шарит по системным каталогам в поисках нужных ей вещей, соответствующим образом корректирует используемые файлы и определенные символы, обращаясь к вам за подтверждением результатов своих изысканий.
Программисты настолько увлеклись языком Perl, что, помимо UNIX- и UNIX-подобных систем, начали использовать его и в системах Amiga, Atari ST, системах семейства Macintosh, VMS, OS/2, даже MS-DOS и, наконец, в Windows NT и Windows 95. К тому моменту, когда вы будете читать эти строки, Perl, вероятно, перенесут и на многие другие системы. Исходные тексты языка Perl (и многие предкомпилированные двоичные файлы для He-UNIX-архитектур) можно получить на одном из серверов сети CPAN (Comprehensive Perl Archive Network). Если вы имеете доступ к World Wide Web, посетите сервер hftp://www.perl.com/CPAN,
являющийся одним из множества "зеркальных" (дублирующих) серверов. Если вы абсолютный новичок, отправьте по адресу bookquestions@ora.com
послание с вопросом "Где можно получить Perl?!?!" ^Where 1 сап set Perl?!?!").
Или по несколько более либеральной "художественной лицензии" (Artistic License), согласно которой Perl распространяется в виде дистрибутива.
Другие книги
Programming Perl — полный справочник по Perl, тогда как нашу книгу скорее можно назвать пособием. Если вы хотите больше узнать о регулярных выражениях, используемых в Perl, предлагаем вам книгу Mastering Regular Expressions by Jeffrey E.F. Friedl (O'Reilly & Associates).
Посмотрите также вышедшие в издательстве O'Reilly & Associates книги CGf Programming on the World Wide Web by Shishir Gundavaram; Web Client Programming with Perl by Clinton Wong; HTML: The Definitive Guide by Chuck Musciano and Bill Kennedy.
Книги The AWK Programming Language by Aho, Kernighan, and Weinberger (Addison-Wesley) и sed &. awk by Dale Dougherty (O'Reilly & Associates) содержат обширный базовый материал по таким вопросам, как ассоциатив-ные массивы, регулярные выражения и общее мировоззрение, благодаря которому появился Perl. В них приведено множество примеров, которые можно перевести на Perl с помощью конвертора awk-to-perl (а2р) или конвертора sed-to-perl (s2p). Конечно, эти конверторы не выдают совершен-ный Perl-код, но если вы не сможете реализовать один из этих примеров в Perl, выходная информация конвертора даст вам хорошую подсказку.
Для Web-мастеров мы рекомендуем второе издание книги How to Setup and Maintain a Web Site by Lincoln Stein (Addison Wesley). Д-р Штейн, известный как автор Perl-модуля CGI.pm (см. главу 19), профессионально и всесторонне рассматривает все вопросы, связанные с администрированием Web-узла на платформах UNIX, Mac и Windows.
Мы также рекомендуем удобный и тщательно проработанный краткий справочник Perl 5 Desktop Reference by Johan Vromans (O'Reilly & Associates).
Другие компоненты формы
Теперь, когда вы знаете, как создавать в форме простые текстовые поля и заполнять их, вам, наверное, интересно будет узнать, как создавать компоненты формы других типов — кнопки, отмечаемые блоки и меню.
Сейчас мы рассмотрим более развитую версию нашей программы. В частности, мы включили в нее новые компоненты формы: всплывающие меню, кнопку передачи (которая называется order) и кнопку очистки полей формы, позволяющую стереть все данные, введенные пользователем. Всплывающие меню делают именно то, о чем говорят их имена, но аргументы, указанные в popup_menu, могут озадачить вас — пока вы не прочитаете следующий раздел, "Ссылки". Функция textfieldO создает поле для ввода текста с указанным именем. Подробнее об этой функции мы расскажем ниже, когда будем описывать программу гостевой книги.
#!/usr/local/bin/perl5 -w
# программа ответа на форму заказа мороженого и генерирования этой формы (версия 4) use strict;
# ввести объявления переменных и выполнить заключение в кавычки use CGI qw(:standard);
print header;
print start html("Ice Cream Stand"), hi ("Ice Cream Stand");
if (paramO) ( # форма уже заполнена
my $who = param("name");
my $flavor = param("flavor");
my $scoops = param("scoops");
my $taxrate = 1.0743;
my $cost = sprintf("%.2f", $taxrate * (1.00 + $scoops * 0.25));
print p("0k, $who, have $scoops scoops of $flavor for \$$cost.");
}
else ( # первый проход, представить незаполненную форму
print hr() ;
print start_form();
print p("What's your name? ",textfield("name"));
print p("What flavor: ", popup_menu("flavor",
['mint','cherry','mocha']));
print p("How many scoops? ", popup_menu("scoops", [1..3]));
print p(submit("order"), reset("clear"));
print end_form(), hr();
} print end_html;
На рис. 19.4 представлено изображение начальной формы, которую создает рассматриваемая программа.
Ice Cream Stand
What's your name? |
What flavor: |t"ii4 How many scoops? 11
Рис. 19.4. Более сложная форма
Как вы помните, функция param() при вызове ее без аргументов возвращает имена всех полей формы, которые были заполнены. Таким образом вы можете узнать, была ли заполнена форма перед вызовом программы. Если у вас есть параметры, это значит, что пользователь заполнил некоторые поля существующей формы, поэтому на них нужно ответить. В противном случае следует генерировать новую форму с расчетом на вторичный вызов той же самой программы.
Ссылки
Вы, возможно, заметили, что обе функции popup_menu () в предыдущем примере имеют весьма странные аргументы. Что означают [ 'mint', 'cherry' , 'mocha' ] и [ 1. . 3 ] ? Квадратные скобки создают нечто такое, с чем вы раньше не встречались: ссылку на анонимный массив. Это обусловлено тем, что функция popup_menu () в качестве аргумента рассчитывает получить именно ссылку на массив. Другой способ создания ссылки на массив — использовать перед именованным массивом обратную косую черту, например \@choices. Так, следующий фрагмент кода:
@choises = ('mint',"cherry','mocha');
print pC'What flavor: ", popup_menu ("flavor", \@choises));
работает так же хорошо, как этот:
print pC'What flavor: ", popup_menu ("flavor", ['mint','cherry','mocha']));
Ссылки функционируют примерно так, как указатели в других языках, но с меньшей вероятностью появления ошибок. Они представляют собой значения, которые указывают на другие значения (или переменные). Ссылки Perl строго делятся на типы (без возможности приведения типов) и никогда не вызывают вывода дампов ядра операционной системы. Более того, если область памяти, на которую указывают ссылки, больше не используется, она автоматически возвращается в использование. Ссылки'играют центральную роль в объектно-ориентированном программировании. Они применяются и в традиционном программировании, являясь основой для создания структур данных, более сложных, нежели простые одномерные массивы и хеши. Язык Perl поддерживает ссылки как на именованные, так и на анонимные скаляры, массивы, хеши и функции.
Также, как методом \@ массив можно создавать ссылки на именованные массивы и посредством указания [ список ] — на анонимные хеши, можно методом \%хеш создавать ссылки на именованные хеши, а методом
( ключ1, значение!, ключ2, значение2, ... }
— на анонимные*.
Да, фигурные скобки теперь используются в Perl с различными целями. Их функцию определяет контекст, в котором используются фигурные скобки.
Подробнее о ссылках вы прочитаете в главе 4 книги Programming Perl и на man-странице perlref(l).
Более сложные вызывающие последовательности
Мы закончим наш рассказ о компонентах форм созданием одного очень полезного компонента, который позволяет пользователю выбирать любое число элементов этого компонента. Функция scrolling_list () модуля CGI.pm может принимать произвольное число пар аргументов, каждая из которых состоит из именованного параметра (начинающегося со знака -) и значения этого параметра.
Чтобы ввести в форму прокручиваемый список, нужно сделать следующее:
print scrolling_list(
-NAME => "flavors",
-VALUES => [ qw(mint chocolate cherry vanilla peach) ],
-LABELS => {
mint => "Mighty Mint",
chocolate => "Cherished Chocolate",
cherry => "Cherry Cherry",
vanilla => "Very Vanilla",
peach => "Perfectly Peachy", },
-SIZE =>3,
-MULTIPLE => 1, tl for true , 0 for false
Значения параметров имеют следующий смысл:
-NAME
Имя компонента формы. Значение этого параметра можно использовать позже для выборки пользовательских данных из формы с помощью функции param().
-LABELS
Ссылка на анонимный хеш. Значения хеша — это метки (элементы списка), которые видит пользователь формы. Когда пользователь выбирает ту или иную метку, в CGI-программу возвращается соответствующий ключ хеша. Например, если пользователь выбирает элемент, заданный как Perfectly Peachy, CGI-программа получает аргумент peach.
-VALUES
Ссылка на анонимный массив. Этот массив состоит из ключей хеша, на которые ссылается -labels.
-SIZE
Число, определяющее, сколько элементов списка пользователь будет видеть одновременно.
-MULTIPLE
Истинное или ложное значение (в том смысле, который принят для этих понятий в Perl), показывающее, можно ли будет пользователю формы выбирать более одного элемента списка.
Если -multiple установлена в значение "истина", вы можете присвоить список, возвращаемый функцией param(), массиву:
@choices = param("flavors");
Вот другой способ создания этого прокручиваемого списка — с передачей ссылки на существующий хеш вместо создания такого хеша "на ходу":
%flavors = (
"mint", "Mighty Mint",
"chocolate", "Cherished Chocolate",
"cherry", "Cherry Cherry",
"vanilla", "Very Vanilla",
"peach", "Perfectly Peachy",
);
print scrolling list(
-NAME => "flavors",
-LABELS => \%flavors,
-VALUES => [ keys %flavors ],
-SIZE => 3,
-MULTIPLE => 1, #1 for true , 0 for false ) ;
На этот раз мы передаем в функцию значения, вычисленные по ключам хеша %flavors, ссылка на который выполняется с помощью операции \, Обратите внимание: параметр -values здесь тоже взят в квадратные скобки. Простая передача результата операции keys в виде списка не сработает, потому что в соответствии с правилом вызова функции scrolling_list() должна быть сделана ссылка на массив, которую как раз и создают квадратные скобки. Считайте квадратные скобки удобным способом представления нескольких значений как одного.
Еще о поледержателяж
Из примеров вы уже поняли, что поледержатель @““ обозначает выровненное по левому краю поле, которое содержит пять символов, а @“““““ — выровненное по левому краю поле, содержащее одиннадцать символов. Как мы и обещали, опишем поледержатели более подробно.
Текстовые поля
Большинство поледержателей начинается со знака @. Символы, стоящие после него, обозначают тип поля, а число этих символов (включая @) соответствует ширине поля.
Если после знака @ стоят левые угловые скобки (““), поле выровнено по левому краю, т.е. в случае, если значение окажется короче, чем отведенное для него поле, оно будет дополняться справа символами пробела. (Если значение слишком длинное, оно автоматически усекается; структура формата всегда сохраняется.)
Если после знака @ стоят правые угловые скобки (””), поле выровнено по правому краю, т.е. в случае, если значение окажется короче, чем отведенное для него поле, оно будет заполняться пробелами слева.
Наконец, если символы, стоящие после знака @, — повторяющаяся несколько раз вертикальная черта (||||), то поле центрировано. Это означает, что если значение слишком короткое, производится дополнение пробелами с обеих сторон, в результате значение будет расположено по центру поля.
Числовые поля
Следующий тип поледержателя — числовое поле с фиксированной десятичной запятой, полезное для больших финансовых отчетов. Это поле также начинается со знака @; за ним следует один или более знаков # с необязательной точкой (она обозначает десятичную запятую). Опять-таки, знак @ считается одним из символов поля. Например:
format MONEY =
Assets: @#W#.*” Liabilities: @#*”##.*# Net: @t”##*.*#
$assets, $liabilities, $assets-$liabilities
Эти три числовых поля предусматривают шесть знаков слева от десятичной запятой и два справа (в расчете на суммы в долларах и центах). Обратите внимание на то, что в формате используется выражение — это абсолютно допустимо и встречается очень часто.
Ничего оригинального в Perl больше нет: вам не удастся установить формат с плавающей запятой для вывода значений денежных сумм, нельзя также заключить отрицательные значения или что-нибудь подобное в квадратные скобки. Для этого придется писать отдельную программу, например:
format MONEY =
Assets: @“““<“ Liabilities: @““““ Net: @““<““
&cool($assets,10), scool($liab,9), Scool($assets-$liab,10)
sub pretty (
my($n,$width) = @_;
$width -— 2; # учтем отрицательные числа
$n ° sprintf("%.2f",$n); # sprintf описывается в одной из следующих глав if ($n < 0) (
return sprintf ("[t$width.2f]", -$n); # отрицательные числа
# заключаются в квадратные скобки ) else {
return sprintf (" %$width.2f ", $n); # положительные числа выделяются
^
пробелами } }
## body of program:
$assets = 32125.12;
$liab = 45212.15;
write (MONEY) ;
Многостроковые поля
Как уже упоминалось выше, при подстановке значения, содержащегося в строке значений. Perl обычно заканчивает обработку строки, встретив в этом значении_символ новой строки. Многостроковые поледержатели позволяют использовать значения, которые представляют собой несколько строк информации. Эти поледержатели обозначаются комбинацией @*, которая ставится в отдельной строке. Как всегда, следующая строка определяет значение, подставляемое в это поле. В данном случае это может быть выражение, которое дает в результате значение, содержащее несколько строк. Подставленное значение будет выглядеть так же, как исходный текст:
четыре строки значения становятся четырьмя строками выходной информации. Например, приведенный ниже фрагмент программы
format STDOUT = Text Before. @*
$long_string Text After.
$long_string = "Fred\nBarney\nBetty\nWilma\n";
write;
позволяет получить такие выходные данные:
Text Before.
Fred
Barney
Betty
Wilma
Text After.
Заполненные поля
Следующий вид поледержателя — заполненное поле. Этот поледержатель позволяет создавать целый текстовый абзац, разбиение на строки которого выполнено по границам слов. Для этого используются несколько элементов формата, которые работают в совокупности. Но давайте рассмотрим их по отдельности.
Во-первых, заполненное поле обозначается путем замены маркера @ в текстовом поледержателе на символ А
(например, /'<“).
Соответствующее значение для заполненного поля (в следующей строке формата) должно быть скалярной переменной*, содержащей текст, а не выражением, которое возвращает скалярное значение. Это объясняется тем, что при заполнении этого поледержателя Perl изменяет значение переменной, а значение выражения изменить очень трудно.
Когда Perl заполняет данный поледержатель, он берет значение этой переменной и "захватывает" из него столько слов (подразумевая разумное определение термина "слово")**, сколько поместится в этом поле. Эти слова фактически "выдираются" из переменной; после заполнения поля значение переменной представляет собой то, что осталось после удаления слов. Почему делается именно так, вы скоро увидите.
* А также отдельным скалярным элементом массива или хеша, например, $а[3] или $h{"fred").
** Разделители слов задаются переменной $:.
Пока что особой разницы между заполненным полем и обычным текстовым полем не видно; мы выводим в поле ровно столько слов, сколько в нем умещается (за исключением того, что мы соблюдаем границу последнего слова, а не просто обрезаем текст по ширине поля). Удобство использования заполненного поля проявляется, когда в одном формате делается несколько ссылок на одну переменную. Взгляните на этот пример:
format PEOPLE =
Name: @<““““““ Comment: "““““““““““^“““^ $name, $comment
$comment $comment $comment
Обратите внимание: переменная $ comment появляется четыре раза. Первая строка (строка с полем имени) выводит имя и первые несколько слов значения этой переменной. В процессе вычисления этой строки $ comment изменяется так, что эти слова исчезают. Вторая строка опять ссылается на ту же переменную ($comment) и поэтому получает из нее следующие несколько слов. Это справедливо и для третьей, и для четвертой строк. По сути дела, мы создали прямоугольник, который будет максимально заполнен словами из переменной $ comment, расположенным в четырех строках.
Что будет, если полный текст занимает меньше четырех строк? Получатся одна-две пустые строки. Это, наверно, нормально, если вы печатаете этикетки и вам нужно, чтобы в каждом элементе было одинаковое количество строк. Если же вы печатаете отчет, наличие множества пустых строк приведет к неоправданному расходу бумаги.
Чтобы решить эту проблему, нужно использовать признак подавления. Строка, содержащая тильду (~), подавляется (не выводится), если при печати она окажется пустой (т.е. будет содержать только пробельные символы). Сама тильда всегда печатается как пробел и может ставиться в любом месте строки, в котором можно было бы поставить пробел. Перепишем последний пример так:
format PEOPLE = Name: @““““““< Comment: ^“““““““““““““x
$name, $comment
$comment $comment $comment
Теперь, если комментарий занимает всего две строки, третья и четвертая строки будут автоматически подавлены.
Что будет, если комментарий занимает больше четырех строк? Мы могли бы сделать около двадцати копий последних двух строк этого формата, надеясь, что двадцати строк хватит. Это, однако, противоречит идее о том, что Perl помогает лентяям, поэтому специально для них предусмотрено следующее: любая строка, которая содержит две рядом стоящие тильды, автоматически повторяется до тех пор, пока результат не будет полностью пустой строкой. (Эта пустая строка подавляется.) В итоге наше определение формата приобретает такой вид:
format PEOPLE = Name: @<““““““ Comment: ^“““““““““““““^
$name, $comment
$comment
Таким образом, все будет нормально, сколько бы строк ни занимал комментарий — одну, две или двадцать.
Отметим, что данный критерий прекращения повторения строки требует, чтобы в некоторый момент данная строка стала пустой. Это значит, что в этой строке не нужно размещать постоянный текст (кроме символов пробела и тильд), иначе она никогда не станет пустой.
Еще об операции сопоставления
Мы уже рассматривали простейшие варианты использования операции сопоставления (регулярного выражения, заключенного между косыми). Теперь давайте изучим способы, которыми можно заставить эту операцию делать нечто иное.
Выбор другого объекта для сопоставления (операция :='•)
Обычно строка, которую нужно сопоставить с образцом, не находится в переменной $_, и помещать ее туда довольно утомительно. (Может быть, в переменной $__ уже хранится значение, которое вам не хочется терять.) Ничего страшного — здесь нам поможет операция =~. С ее помощью вы можете назначить для проведения операции сопоставления строку, хранящуюся в переменной, отличной от $_.
Эта переменная указывается справа от знака операции. Выглядит это так:
$а = "hello world";
$а =~ /^he/; # истина
$а =~ /(.)\1/; # тоже истина (соответствует двум 1)
if ($а =~ /(.)\1/) ( t истина, поэтому проводятся дальнейшие операции
1
Справа от знака операции =~ может стоять любое выражение, которое дает в результате некоторое скалярное строковое значение. Например, <stdin> при использовании в скалярном контексте дает скалярное строковое значение, поэтому, объединив эту операцию с операцией =~ и операцией сопоставления с регулярным выражением, мы получим компактную программу проверки входных данных:
print "any last request? ";
if (<STDIN> ==~ /л[y1}/) { # начинаются ли входные данные с буквы у? print "And just what might that request be? ";
# и чтобы это мог быть за запрос? <STDIN>; # получить строку со стандартного ввода print "Sorry, I'm unable to do that.\n";
# прошу прощения, но я не могу этого сделать )
В данном случае при помощи <stdin> берется очередная строка со стандартного ввода, которая затем сразу же используется как строка, сопоставляемая с образцом л [ yY ]. Отметим, что мы не сохраняли входные данные в переменной, поэтому если мы захотим сопоставить эти данные с другим образцом или же вывести их в сообщении об ошибке, то у нас ничего не выйдет. Тем не менее эта форма часто оказывается удобной.
Игнорирование регистра
В предыдущем примере мы указывали образец [yY] для обозначения строчной и прописной буквы у. Если речь идет об очень коротких строках, например, у или fred, то данный способ обозначения достаточно удобен, скажем, [fF] [rR] [eE] [dD]. А что делать, если сопоставляемая строка — это слово procedure в нижнем или верхнем регистре?
В некоторых версиях grep
флаг -i означает "игнорировать регистр". В Perl тоже есть такая опция. Чтобы ею воспользоваться, нужно добавить строчную i к закрывающей косой черте, т.е. написать / образец/i. Такая запись говорит о том, что буквы образца будут соответствовать буквам строки в любом регистре. Например, чтобы найти слово procedure в любом регистре, стоящее в начале строки, запишите /^procedure/i.
Теперь наш предыдущий пример будет выглядеть так:
print "any last request? ";
if (<STDIN> =~ /"y/i) { # начинаются ли входные данные с буквы у? # да! выполнить какие-то операции
…
}
Использование другого разделителя
Чтобы найти строку, которая содержит несколько косых (/), в соответствующем регулярном выражении нужно перед каждой из них поставить обратную косую черту (\). Например, чтобы найти строку, которая начинается с названия директории /usr/etc, нужно записать:
$path = <STDIN>; # прочитать путевое имя (вероятно, из find?) if ($path =~ /"VusrVetc/) {
# начинается с /usr/etc... }
Как видите, комбинация "обратная косая — косая" создает между элементами текста своеобразные "проходы". Если косых очень много, это занятие может стать весьма утомительным, поэтому в Perl предусмотрена возможность использования другого разделителя (delimiter). Поставьте перед любым специальным символом* (выбранным вами в качестве разделителя) букву т, укажите свой образец и дайте еще один такой же разделитель:
/''•VusrVetc/ # использование стандартного разделителя — косой черты m@^/usr/etc@ # использование в качестве разделителя символа @ m#^/usr/etc# # использование в качестве разделителя символа # # (это мой любимый символ)
Если хотите, можете опять использовать косые, например, m/fred/. Таким образом, m — общепринятое обозначение операции сопоставления с регулярным выражением, но если в качестве разделителя выбрана косая черта, то m не обязательна.
Использование интерполяции переменных
Перед тем как регулярное выражение рассматривается на предмет наличия специальных символов, в нем производится интерполяция переменных. Следовательно, регулярное выражение можно строить не только из литералов, но и из вычисляемых строк. Например:
$what = "bird";
$sentence = "Every good bird does fly.";
if ($sentence =~ /\b$what\b/) {
print "The sentence contains the word $what!\n";
>
Здесь мы использовали ссылку на переменную для построения операции сопоставления с регулярным выражением \bbird\b/.
* Если этот разделитель — левый элемент пары (круглая, фигурная, угловая или квадратная скобка), то закрывающим разделителем будет соответствующий правый элемент пары. В остальных случаях первый и второй разделители будут совпадать.
Вот несколько более сложный пример:
$sentence = "Every good bird does fly.";
print "What should I look for? ";
$what = <STDIN>;
chomp($what) ;
if ($sentence =~ /$what/) ( # нашли! print "I saw $what in $sentence.\n";
} else (
print "nope... didn't find it.\n";
)
Если вы введете слово bird, оно будет найдено, а если слово scream — не будет. Если ввести [bw] ird, результаты поиска тоже будут успешными. Это говорит о том, что квадратные скобки в данном случае воспринимаются как символы сопоставления с образцом.
Чтобы избежать этого, следует поставить перед этими символами обратную косую, которая превратит их в символы буквального сопоставления. Это кажется сложным, если в вашем распоряжении нет закавычивающей управляющей последовательности \Q:
$what = "[box]";
foreach (qw(in([box] out [box] white [sox] ) ) { if (/\Q$what\E/) {
print "$_ matched!\n";
1 }
Здесь конструкция \Q$what\E превращается в \[box\], в результате чего операция сопоставления ищет пару квадратных скобок, а не рассматривает всю конструкцию как класс символов.
Специальные переменные, защищенные от записи
После успешного сопоставления с образцом переменным $1, $2, $3 и т.д. присваиваются те же значения, что и \1, \2,\3 и т.д. Это можно использовать для поиска соответствия в последующем коде. Например:
$_ = "this is a test";
/(\w+)\W+(\w+)/; # сопоставление первых двух слов
# $1 теперь содержит this, а $2 — is
Доступ к тем же значениям ($1, $2, $3 и т.д.) можно также получить, использовав операцию сопоставления для соответствующих списков. Если результаты сопоставления окажутся положительными, будет получен список значений от $1 до $п (где n — количество занесенных в память элементов). В противном случае значения не определены. Запишем последний пример по-другому:
$_ = "this is a test";
($first, $second) = /(\w+)\W+(\w+)/; # сопоставление первых двух слов # $first теперь содержит this, a $second - is
К другим предопределенным защищенным от записи переменным относятся: $& (часть строки, совпавшая с регулярным выражением); $' (часть строки, стоящая перед совпавшей частью); $ ' (часть строки, стоящая после совпавшей части). Например:
$_ = "this is a sample string";
/sa.*le/; # соответствует слову sample внутри строки
# $' теперь содержит "this is a "
# $& теперь содержит "sample"
# $' теперь содержит "string"
Поскольку значения этим переменным присваиваются при каждом успешном сопоставлении, их нужно где-нибудь сохранить, если они вам впоследствии понадобятся*.
Формат начала страницы
Многие отчеты в конечном итоге выводятся на печатающее устройство, например на принтер. Принтерная бумага обычно обрезается по размеру страницы, потому что в большинстве своем мы уже давно не пользуемся рулонами. Поэтому в тексте, поступающем на печать, должны, как правило, отмечаться границы страниц, для чего нужно вставлять пустые строки или символы перехода на новую страницу. В принципе, можно было бы взять результат работы Perl-программы и обработать его какой-либо утилитой (может быть, даже написанной на Perl), которая выполняет такую разбивку на страницы. Существует, однако, более легкий способ.
Perl позволяет определять специальный формат начала страницы, с помощью которого запускается режим постраничной обработки. Perl подсчитывает все выходные строки, генерируемые при вызове формата для конкретного дескриптора файла. Если следующая строка не умещается на оставшейся части текущей страницы, Perl выдает символ перехода на новую страницу, за которым автоматически следует вызов формата начала страницы и текст, который выводится с использованием формата для конкретного дескриптора файла. Благодаря этому текст, полученный в результате одного вызова функции write, никогда не разбивается на несколько страниц (если только он не настолько велик, что не помещается на одной странице).
Формат начала страницы определяется так же, как и всякий другой формат. Имя формата начала страницы для конкретного дескриптора файла по умолчанию состоит из имени этого дескриптора и символов _тор (обязательно прописных).
Переменная $% в Perl определяется как количество вызовов формата начала страницы для конкретного дескриптора файла, что позволяет использовать эту переменную в составе формата начала страницы для нумерации страниц. Например, добавление следующего определения формата в предыдущий фрагмент программы предотвращает разрыв адресной этикетки на границах страниц и обеспечивает указание текущего номера страницы:
format ADDRESSLABEL_TOP = My Addresses -- Page @< $%
Длина страницы по умолчанию — 60 строк. Этот параметр можно изменить, присвоив значение специальной переменной, о которой вы вскоре узнаете.
Perl не замечает, если вы выполняете для этого же дескриптора файла функцию print из другого места в программе, вследствие чего число строк, которые можно разместить на текущей странице, уменьшается. Вам следует либо переписать свой код, чтобы с помощью одних и тех же форматов выводить на печать всю информацию, либо после выполнения print изменить переменную "число строк на текущей странице". Через минуту мы увидим, как можно изменить это значение.
Форматирование данных с помощью функции sprintf()
Функция printf оказывается удобной, когда нужно взять список значений и создать выходную строку, в которой зти значення отображались бы в заданном виде. Функция sprint f использует такие же аргументы, как и функция printf, но возвращает то, что выдала бы printf, в виде одной строки. (Можете считать ее "строковой функцией printf".) Например, чтобы создать строку, состоящую из буквы х и значення переменной $у, дополненного нулями до пяти разрядов, нужно записать:
$result = sprintf("X%05d",$y);
Описание аргументов функции sprintf вы найдете в разделе sprintf главы 3 книги Programming Perl и на man-странице printf(3)
(если она у вас єсть).
FTP
Чтобы использовать FTP, вам понадобится компьютер, имеющий непо-средственный выход в Internet. Ниже приведен пример сеанса связи.
% ftp ftp.ora .corn Connected to ftp.uu.net
220 ftp.ora.corn FTP server (Version 6.34 Thu Oct 22 14:32:01 EDT 1992)ready. Name (ftp. ora. com:username) : anonymous 331 Guest login ok, send e-mail address as password. Password: username@hostname
(здесь используйте свое пользовательское имя и хост-имя)
230 Guest login ok, access restrictions apply. ftp> cd /published/oreilly/nu-tshell/learn3.ng_Jperl2 250 CWD command successful. ftp> get README
200 PORT command successful.
150 Opening ASCII mode data connection for README (xxxx bytes). 226 Transfer complete. local: README remote: README
xxxx bytes received in xxx seconds (xxx Kbytes/s) ftp> binary
200 Type set to 1. ftp> get examples, tar.gz 200 PORT command successful.
150 Opening BINARY mode data connection for examples.tar.gz (xxxx DycesJ. 226 Transfer complete, local; exercises remote: exercises xxxx bytes received in xxx seconds (xxx Kbytes/s) ftp> quit 221 Goodbye.
FTPMAIL
FTPMAIL — это почтовый сервер, доступный каждому, кто имеет воз-можность посылать электронную почту на узлы Internet и получать ее оттуда. Доступ к FTPMAIL обеспечивают все провайдеры Internet, предоставляющие услуги электронной почты. Вот как это можно сделать.
Вы посылаете почту HQ.y3enftpmail@online.ora.com. В теле сообщения дайте FTP-команды, которые хотите выполнить. Сервер запустит для вас FTP-сеанс от имени анонимного пользователя и пошлет вам необходимые файлы. Чтобы получить полный справочный файл, пошлите сообщение без указания темы и с телом, состоящим из одного слова — help. Ниже приведен пример сеанса работы с электронной почтой в UNIX, в ходе которого пользователь получает программы-примеры. Эта команда посылает вам перечень файлов, имеющихся в текущем каталоге, и затребованные файлы примеров. Данный перечень полезен, если существуют более поздние версии примеров, которые вас интересуют.
% mail ftpmail@online. ora. corn Subject: reply-to usernaitie@hostname
(на какой компьютер вы хотите принять файлы)
open cd /published/oreilly/nutahell/learning_perl2 dir get README mode binary uuencode get examples, tar.gz quit
Подпись в конце сообщения приемлема, если она стоит после команды quit.
Функции
Мы уже знакомы со встроенными пользовательскими функциями, например chomp, print и другими, и пользовались ими. Теперь давайте рассмотрим функции, которые вы можете определить сами.
Функции split и join
Регулярные выражения можно использовать для разбивки строки на поля. Это делает функция split. Функция join выполняет противоположное действие — вновь "склеивает" эти кусочки.
Функция split
Функция split получает регулярное выражение и строку и ищет в этой строке все экземпляры указанного регулярного выражения. Те части строки, которые не совпадают с регулярным выражением, возвращаются по порядку как список значений. Вот, например, код синтаксического анализа разделенных двоеточиями полей, аналогичных тем, которые используются в UNIX-файлах /etc/passwd:
$line =
"merlyn::118:10:Randal:/home/merlyn:/usr/bin/peri";
@fields = split (/:/,$line); # разбить $line, используя в качестве t разделителя двоеточие
# теперь @fields содержит ("merlyn","","118","10",
# "Randal","/home/merlyn","/usr/bin/peri")
Обратите внимание на то, что второе пустое поле стало пустой строкой. Если вы этого не хотите, задайте сопоставление следующим образом:
Sfields = split(/:+/, $line);
Здесь при сопоставлении принимаются во внимание одно и более расположенных рядом двоеточий, поэтому пустое поле не образуется.
Очень часто приходится разбивать на поля значение переменной $_, поэтому этот случай предлагается по умолчанию:
$ = "some string";
Swords = split (/ /); # то же самое, что и Swords = split(/ /, $_); *
# Или две пары, если используется символ из пары "левая-правая".
При такой разбивке соседние пробелы в разбиваемой строке вызовут появление пустых полей (пустых строк). Лучше использовать образец / +/, а лучше /\s+/, который соответствует одному и более пробельным символам. Этот образец, по сути дела, используется по умолчанию*, поэтому, если вы разбиваете переменную $_ по пробельным символам, вы можете использовать все стандартные значения и просто написать :
Swords = split; # то же самое, что и (Swords = split(/\s+/, $_) ;
Завершающие строки пустые поля в список, как правило, не включаются. Особой роли это обычно не играет. Решение вроде
$line = "merlyn::118:10:Randal:/home/merlyn:";
($name,$password,$uid,$gid,$gcos,$home,$shell) = split(/:/,$line);
# разбить $line, используя в качестве разделителя двоеточие
просто присваивает переменной $shell нулевое значение (undef), если эта строка недостаточно длинна или содержит в последнем поле пустые значения. (Разбиение выполняется так, что лишние поля просто игнорируются.)
Функция join
Функция join берет список значений и "склеивает" их, ставя между элементами списка строку-связку. Выглядит это так:
$bigstring = join($glue,@list);
Например, чтобы восстановить строку пароля, попробуйте использовать следующее:
$outline = join(":", @fields) ;
Отметим, что строка-связка — это не регулярное выражение, а обычная строка, состоящая из символов общим числом нуль или более.
Если нужно поставить связку не между элементами, а перед каждым элементом, то достаточно такого трюка:
$result = (join "+", "", @fields);
Здесь пустая строка "" рассматривается как пустой элемент, который должен быть связан с первым элементом данных массива @fields.B результате связка помещается перед каждым элементом. Аналогичным образом можно поставить пустой элемент-связку в конец списка:
$output = join ("\n", @data, "");
* На самом деле образец по умолчанию — строка "", поэтому начальный пробельный разделитель игнорируется, но для нас вышесказанного пока достаточно.
Функции stat и Istat
Вышеупомянутые операции весьма эффективны при проверке различных атрибутов конкретного файла или дескриптора файла, но полную информацию с их помощью получить нельзя. Например, не предусмотрена операция проверки, которая возвращала бы число ссылок на файл. Чтобы добраться до остальных сведений о файле, вызовите функцию stat, которая возвращает практически все, что возвращает системный вызов stat в POSIX (надеемся, мы сказали больше, чем вы хотите знать).
Операнд функции stat — дескриптор файла или выражение, посредством которого определяется имя файла. Возвращаемое значение — либо undef, если вызов неудачен, либо 13-элементный список**, который легче всего описать с помощью такого списка скалярных переменных:
($dev,$ino,$mode,$nlink,$uid,$gid,$rdev, $size, $atinie, $mtime, $ctime, $blksize,$blocks) = stat (. . .)
* Или операцию int.
** Если вам тяжело запомнить порядок значений, возвращаемых функцией stat, можете обратиться к модулю File: :stat, впервые введенному в выпуске 5.004. Он обеспечивает доступ к этим значениям следующим образом:
$file_owner = stat($filename)->uid
Имена здесь соответствуют частям структуры stat, подробно описанной на man-странице stat(T). Рекомендуем изучить приведенные там подробные пояснения.
Например, чтобы получить идентификаторы пользователя и группы из файла паролей, нужно записать:
($uid,,$gid) = (stat("/etc/passwd")) [4,5];
и этого окажется достаточно.
Вызов функции stat с именем символической ссылки возвращает информацию о том, на что указывает эта ссылка, а не сведения о самой ссылке (если только она не указывает на что-то в текущий момент недоступное). Если вам нужна информация о самой символической ссылке (большей частью бесполезная), используйте вместо stat функцию istat (которая возвращает те же данные в том же порядке). С элементами, которые не являются символическими ссылками, функция istat работает аналогично stat.
Как и в операциях проверки файлов, операнд функций stat и Istat по умолчанию — $_. Это значит, что операция stat будет выполняться над файлом, заданным скалярной переменной $_.
Где найти упражнения
Упражнения, приведенные в этой книге, можно получить в электронном варианте разными способами: по FTP, FTPMAIL, BITFTP и UUCP. Самые дешевые, самые быстрые и самый легкие способы указаны первыми. Если читать сверху вниз, то самый лучший метод — это, вероятно, первый из тех, которые работают. При непосредственной работе в Internet используйте FTP. Если у вас нет прямого соединения с Internet, но вы можете посылать электронную почту на узлы Internet и получать ее с этих узлов, используйте FTPMAIL. Если вы посылаете электронную почту по BITNET, используйте BITFTP. Если ни один из этих способов не действует, используйте UUCP.
Примечание: упражнения разрабатывались с помощью UNIX-системы. Если вы работаете в среде UNIX, можете использовать их, не корректируя. При работе на другой платформе эти упражнения, возможно, придется слегка модифицировать. Например, при работе с UNIX каждая строка завершается символом новой строки (возврат каретки подразумевается), тогда как в DOS каждая строка должна завершаться явно указанными символами и новой строки, и возврата каретки. В зависимости от конфигурации вашей системы и используемого метода пересылки упражнений по сети вам, возможно, придется добавить символы возврата каретки. Дополнительную информацию по этому вопросу можно найти в файле README, который прилагается к упражнениям.
Генерирование формы
Если вам надоело вводить параметры своей программы в броузер — создайте заполняемую форму. К таким формам привыкли большинство пользователей. Компоненты формы, которые принимают вводимые пользователем данные, иногда называются vidgets; считается, что этот термин гораздо удобнее, чем "устройства графического ввода". Такие компоненты форм включают одно- и многостроковые текстовые поля, всплывающие меню, прокручиваемые списки, различные виды кнопок и отмечаемых блоков.
Создайте следующую HTML-страницу, которая включает форму с одним компонентом "текстовое поле" и кнопкой передачи. Когда пользователь щелкает на кнопке передачи*, вызывается сценарий ice_cream, заданный атрибутом ACTION.
<!-- ice_cream.html —> <HTML>
<HEAD>
<TITLE>HeUo Ice Cream</TITLE>
</HEAD>
<BODY>
<Hl>Hello Ice Cream!</Hl>
<FORM ACTION-"http://www.SOMEWHERE.org/cgi-bin/ice_cream">
What's your flavor? <INPUT NAME="favorite" VALUE="mint">
<P>
<INPUT TYPE="submit">
</FORM>
</BODY> </HTML>
Помните, что CGI-программа может выдавать ту выходную HTML-информацию, которую вы ей укажете. Эта информация будет затем передаваться в тот броузер, который обратится к URL данной программы. CGI-программа может, таким образом, не только реагировать на данные, введенные пользователем в форму, но и генерировать HTML-страницу с формой. Более того, одна программа может выполнять одну за другой обе эти задачи. Все, что вам нужно сделать,— это разделить программу на две части, которые делают разные вещи в зависимости от того, была ли программа вызвана с аргументами или нет. Если аргументов не было, программа посылает в броузер пустую форму; в противном случае аргументы содержат данные, введенные пользователем в ранее переданную форму, и программа возвращает в броузер ответ на основании этих данных.
* Некоторые броузеры позволяют обходиться без кнопки передачи, если форма содержит только одно поле для ввода текста. Если курсор находится в этом поле и пользователь нажимает клавишу [Enter], это считается запросом на передачу. Однако лучше здесь использовать традиционный способ.
При размещении всех компонентов программы в одном CGI-файле упрощается ее сопровождение. Цена — незначительное увеличение времени обработки при загрузке исходной страницы. Вот как все это выглядит:
#!/usr/local/bin/perlS -w
# программа ответа на форму о любимом сорте мороженого
# *и генерирования этой формы* (версия 3) use CGI qw(:standard);
my $favorite = param("flavor");
print header;
print start_html("Hello Ice Cream"), hi ("Hello Ice Cream");
if ($favorite) {
print p("Your favorite flavor is $favorite. ");
} else {
print hr, start_form;
print p ("Please select a flavor: ", textfield("flavor","mint"));
print end form, hr;
Если во время работы с броузером вы щелкнете на ссылке, которая указывает на эту программу (и если ссылка в конце URL не содержит ?whatever), то увидите экран, подобный изображенному на рис. 19.2. Текстовое поле изначально содержит значение по умолчанию, но это значение заменяется данными, введенными пользователями (если они есть).
Рис. 19.2. Исходная заполняемая форма
Теперь заполните поле Please select a flavor, нажмите клавишу [Enter], и вы увидите то, что показано на рис. 19.3.
Рис. 19.3. Результат обработки переданного с использованием формы запроса
Изучаем Perl
В этой главе:
История создания языка Perl
Назначение языка Perl
Доступность
Основные понятия
Прогулка по стране Perl
Упражнение
Изучаем Perl
В этой главе:
Что такое скалярные данные
Скалярные операции
Скалярные переменные
Скалярные операции и функции
<STDIN> как скалярное значение
Упражнения
Скалярные данные"
1. Вот один из способов решения этой задачи:
$pi = 3.141592654;
$result = 2 * $pi * 12.5;
print "radius 12,5 is circumference $result\n";
Сначала мы присваиваем константу (число к) скалярной переменной $pi. Затем мы вычисляем длину окружности, используя значение $pi в выражении, и, наконец, выводим результат, применяя строку, содержащую ссылку на него.
2. Вот один из способов решения этой задачи:
print "What is the radius: ";
chomp($radius = <STDIN>) ;
$pi = 3.141592654;
$result = 2 * $pi * $radius;
print "radius $radius is circumference $result\n";
Это похоже на предыдущий пример, но здесь мы попросили пользователя, выполняющего программу (применив для выдачи приглашения оператор print), ввести значение. Считывание строки с терминала осуществляется посредством операции <stdin>.
Если бы мы забыли применить функцию chomp, то получили бы посреди выведенной строки символ новой строки. Важно как можно быстрее выбросить этот символ из строки.
3. Вот один из способов решения этой задачи:
print "First number: "; chomp($a = <STDIN>) ;
print "Second number: "; chomp($b = <STDIN>) ;
$c = $a * $b; print "Answer is $c.\n";
Первая строка делает три вещи: приглашает вас ввести число, считывает строку со стандартного ввода, а затем избавляется от неизбежного символа новой строки. Поскольку мы используем значение $а строго как число, функцию chomp здесь можно опустить, потому что в числовом контексте 45\n — это 45. Однако столь небрежное программирование может впоследствии обернуться неприятностями (например, если нужно будет включить $а в сообщение).
Вторая строка делает то же самое со вторым числом и помещает его в скалярную переменную $Ь.
Третья строка перемножает эти два числа и выводит результат. Отметьте здесь наличие символа новой строки в конце строки (тогда как в первых двух строках он отсутствует). Первые два сообщения — это приглашения, в ответ на которые пользователь должен ввести число в той же строке. Последнее сообщение — это оператор; если бы мы выбросили символ новой строки, то сразу же за сообщением появилось бы приглашение shell. He очень-то хорошо.
4. Вот один из способов решения этой задачи:
print "String: "; $а = <STDIN>;
print "Number of times: "; chomp($b = <STDIN>) ;
$c = $a x $b; print "The result is:\n$c";
Как в предыдущем упражнении, первые две строки запрашивают значения двух переменных и принимают их. Однако здесь мы не выбрасываем символ новой строки, потому что он нам нужен! Третья строка получает введенные значения и выполняет над ними операцию многократного повторения строк, а затем выводит ответ. Обратите внимание на то, что за вычисляемой переменной $с в операторе print нет символа новой строки, поскольку мы считаем, что $с в любом случае заканчивается этим символом.
Изучаем Perl
В этой главе:
Список и массив
Литеральное представление
Переменные
Операции над массивами и функции обработки массивов
Скалярный и списочный контексты
<STDIN> как массив
Интерполяция массивов
Упражнения
Массивы и списочные данные"
1. Вот один из способов решения этой задачи:
print "Enter the list of strings:\n";
@list = <STDIN>;
Sreverselist = reverse @list;
print @reverselist;
Первая строка приглашает ввести строки. Вторая строка считывает эти строки в переменную-массив. Третья строка формирует список с обратным порядком расположения элементов и заносит его в другую переменную. Последняя строка выводит результат.
Последние три строки можно объединить:
print "Enter the list of strings:\n";
print reverse <STDIN>;
Этот код работает аналогично предыдущему, так как операция print ожидает ввода списка, а операция reverse возвращает список. Далее операции reverse нужен список значений для реверсирования, а операция <stdin>, применяемая в списочном контексте, возвращает список строк — и они получают необходимое!
2. Вот один из способов решения этой задачи:
print "Enter the line number: "; chomp($a = <STDIN>) ;
print "Enter the lines, end with "D:\n"; @b = <STDIN>;
print "Answer: $b[$a-l]";
Первая строка приглашает ввести число, считывает его со стандартного ввода и удаляет назойливый символ новой строки. Вторая строка запрашивает список строк, а затем с помощью операции <stdin> в списочном контексте считывает все эти строки (до появления признака конца файла) в переменную-массив. Последний оператор выводит ответ, используя для выбора соответствующей строки ссылку на массив. Обратите внимание:
нам не нужно добавлять символ новой строки в конце, потому что строка, выбранная из массива @ь, уже заканчивается таким символом.
Если вы попробуете запустить эту программу с терминала, конфигурированного самым обычным образом, вам нужно будет нажать клавиши [Ctrl+D], чтобы обозначить конец файла.
3. Вот один из способов решения этой задачи:
srand;
print "List of strings: "; @b = <STDIN>;
print "Answer: $b[rand (@b)]";
Первая строка запускает генератор случайных чисел. Вторая строка считывает группу строк. Третья строка выбирает случайный элемент из этой группы и выводит его на экран.
Изучаем Perl
В этой главе:
Блоки операторов
Оператор if/unless
Оператор while/until
Оператор for
Оператор foreach
Упражнения
Управляющие структуры"
1. Вот один из способов решения этой задачи:
print "What temperature is it? ";
chomp($temperature °° <STDIN>);
if ($temperature > 72) {
print "Too hot!\n";
} else (
print "Too cold!\n";
>
Первая строка приглашает ввести температуру. Вторая строка принимает введенное значение температуры. Оператор if в последних пяти строках выбирает для вывода одно из двух сообщений в зависимости от значения переменной $temperature.
2. Вот один из способов решения этой задачи:
print "What temperature is it? ";
chomp($temperature = <STDIN>) ;
if ($temperature > 75) (
print "Too hot!\n";
} elsif ($temperature < 68) (
print "Too cold!\n";
) else {
print "Just right!\n";
1
Здесь мы модифицировали программу, введя трехвариантный выбор. Сначала температура сравнивается со значением 75, затем со значением 68. Обратите внимание: при каждом запуске программы будет выполняться только один из трех вариантов.
3. Вот один из способов решения этой задачи:
print "Enter a number (999 to quit): ";
chomp($n = <STDIN>) ;
while ($n != 999) f
$sum += $n;
print "Enter another number (999 to quit): ";
chomp($n = <STDIN>);
1 print "the sum is $sum\n";
Первая строка приглашает ввести первое число. Вторая строка считывает это число с терминала. Цикл while продолжает выполняться до тех пор, пока число не станет равным 999.
Операция += накапливает числа в переменной $sum. Обратите внимание:
начальное значение этой переменной — undef, что очень хорошо для сумматора, потому что первое прибавляемое значение будет фактически прибавляться к нулю (помните, что при использовании в качестве числа undef равно нулю).
В этом цикле мы должны запрашивать и принимать еще одно число, чтобы проверка в начале цикла производилась по вновь введенному числу.
После выхода из цикла программа выводит накопленные результаты.
Если сразу же ввести 999, то значение переменной $sum будет равно не нулю, а пустой строке — т.е. значению undef в строковом контексте. Если вы хотите, чтобы программа в этом случае выводила нуль, нужно в начале программы инициализировать значение $s urn операцией $ sum = 0.
4. Вот один из способов решения этой задачи:
print "Enter some strings, end with "D:\n";
@strings = <STDIN>;
while (Ostrings) (
print pop @strings;
}
Сначала программа запрашивает строки. Эти строки сохраняются в переменной-массиве @strings (по одной на элемент).
Управляющее выражение цикла while — Sstrings. Это управляющее выражение ищет только одно значение ("истина" или "ложь"), поэтому вычисляет выражение в скалярном контексте. Имя массива (такое как @ strings) при использовании в скалярном контексте представляет собой количество элементов, находящихся в массиве в текущий момент. Поскольку массив не пуст, это число не равно нулю и, следовательно, имеет значение "истина". Эта идиома очень широко используется в Perl, она соответствует указанию "делать это, пока массив не пуст".
Тело цикла выводит значение, полученное путем "выталкивания" крайнего справа элемента массива. Следовательно, поскольку этот элемент выводится, при каждом выполнении цикла массив становится на один элемент короче.
Возможно, вам пришла в голову мысль использовать для решения данной задачи индексы. Действительно, эту задачу можно решить несколькими способами, однако в программах настоящих Perl-хакеров индексы встречаются редко, ибо почти всегда находится лучший метод.
5. Вот один из способов решения этой задачи без использования списка:
for ($number = 0; $number <= 32; $number++) {
$square = $number * $number;
printf "%5g %8g\n", $number, $square;
}
А вот как можно решить задачу с помощью списка:
foreach $number (0..32) (
$square = $number * $number;
printf "%5g %8g\n", $number, $square;
}
В обоих решениях применяются циклы с использованием операторов for и foreach. Тела этих циклов идентичны, потому что в обоих решениях значение переменной $ number при каждой итерации изменяется от 0 до 32.
В первом решении использован традиционный С-подобный оператор for. Первое выражение устанавливает переменную $number в 0, второе проверяет, меньше ли $number, чем 32, а третье инкрементирует $number при каждой итерации.
Во втором решении использован оператор foreach, подобный аналогичному оператору C-shell. С помощью конструктора списка создается список из 33 элементов (от 0 до 32). Затем переменной $number поочередно присваиваются значения, равные этим элементам.
Изучаем Perl
В этой главе:
Что такое хеш
Хеш-переменные
Литеральное представление хеша
Хеш-функции
Срезы хешей
Упражнения
Хеши"
1. Вот один из способов решения этой задачи:
%map = qwfred apple green leaves blue ocean);
print "A string please: "; chomp($some_string = <STDIN>);
print "The value for $some_string is $map($some_string(\n";
Первая строка создает хеш из требуемых пар ключ-значение. Вторая строка выбирает строку, удаляя символ новой строки. Третья строка выводит на экран введенную строку и соответствующее ей значение.
Этот хеш можно создать и с помощью серии отдельных операций присваивания:
$map('red') = 'apple';
$map('green'( = 'leaves';
$map('blue'} = 'осеап',-
2. Вот один из способов решения этой задачи:
chomp(Swords = <STDIN>); # читать слова минус символы новой строки foreach $word (@words) (
$count{$word} = $count($word} + 1; # или $count{$word}++ t foreach $word (keys %count) {
print "$word was seen $count($word) times\n";
}
Первая строка считывает строки в массив @ words. Вспомните: в результате выполнения этой операции каждая строка становится отдельным элементом массива, причем символ новой строки останется нетронутым.
В следующих четырех строках осуществляется обход массива, при этом $word приравнивается по очереди каждой строке. Функция chomp отсекает символ новой строки, а потом начинается волшебство. Каждое слово используется как ключ хеша. Значение элемента, выбранного по этому ключу (слову), представляет собой значение счетчика повторений данного слова до текущего момента. Сначала в хеше элементов нет, поэтому если слово wild встречается в первой строке, то $count {"wild"} будет содержать undef. Это значение undef плюс единица оказывается равным нулю плюс единица, то есть единице. (Напомним, что при использовании в качестве числа undef означает нуль.) При следующем проходе у нас будет единица плюс единица, или два, и т.д.
Другой распространенный способ задания этой операции инкременти-рования приведен в комментарии. Опытные Perl-программисты обычно отличаются леностью (мы называем это "краткостью") и никогда не пишут одну и ту же ссылку на хеш в обеих частях операции присваивания, если можно обойтись автоинкрементированием.
После подсчета слов в последних нескольких строках программы осуществляется просмотр хеша и поочередное получение всех его ключей. После вычисления строкового значения сам ключ и соответствующее ему значение выводятся на экран.
Есть и другое решение, отличающееся от описанного только тем, что перед словом keys в третьей с конца строке вставлена операция sort. Без проведения операции сортировки выводимый результат кажется случайным и непредсказуемым. После сортировки все упорядочивается и становится предсказуемым. (Лично я редко использую операцию keys без сортировки; при наличии операции sort непосредственно перед keys повторные просмотры одних и тех же или похожих данных дают сопоставимые результаты.)
Изучаем Perl
В этой главе:
Ввод из STDIN
Ввод из операции "ромб"
Вывод в STDOUT
Упражнения
Базовые средства ввода-вывода^
1. Вот один из способов решения этой задачи:
print reverse о;
Вас, может быть, удивит краткость этого ответа, но он, тем не менее, верен. Вот как работает этот механизм:
а) Сначала функция reverse ищет список своих аргументов. Это значит, что операция "ромб" (О) выполняется в списочном контексте. Следовательно, все строки файлов, указанных как аргументы командной строки (или данные, поступающие со стандартного ввода, если аргументов нет), считываются и преобразуются в список, каждый элемент которого состоит из одной строки.
б) Затем функция reverse меняет порядок следования элементов списка на обратный.
в) Наконец, функция print получает список-результат и выводит его. И. Вот один из способов решения этой задачи:
print "List of strings:\n";
chomp(@strings = <STDIN>) ;
foreach (@strings) (
printf "%20s\n", $_;
)
Первая строка приглашает ввести список строк.
Следующая строка считывает все строки в один массив и избавляется от символов новой строки.
В цикле foreach осуществляется проход по этому массиву с присвоением переменной $_ значения каждой строки.
Функция printf получает два аргумента. Первый аргумент определяет формат "%20s\n", который означает 20-символьный столбец с выравниванием справа и символ новой строки.
3. Вот один из способов решения этой задачи:
print "Field width: ";
chomp($width = <STDIN>) ;
print "List of strings:\n";
chomp(@strings = <STDIN>);
foreach (@strings) (
printf "%$(width}s\n", $_;
}
В решение, данное к предыдущей задаче, мы добавили приглашение ввести ширину поля и ответ на него.
Есть еще одно изменение: строка формата printf теперь содержит ссылку на переменную. Значение переменной $width включается в эту строку до того, как printf использует данный формат. Отметим, что мы не можем записать эту строку как
printf "%$widths\n", $_; #WRONG
потому что тогда Perl искал бы переменную с именем $ widths, а не переменную с именем $width, к которой мы прибавляем букву s. По-другому это можно записать так:
printf "%$width"."s\n", $_; * RIGHT
потому что символ конца строки завершает также имя переменной, защищая следующий символ от присоединения к имени.
Изучаем Perl
В этой главе:
Основные понятия
Основные направления использования регулярных выражений
Образцы
Еще об операции сопоставления
Операция замены
Функции split и join
Упражнения
Регулярные выражения"
1. Вот несколько возможных ответов:
а) /а+ь*/
б) /\\*\**/ (Вспомним, что обратная косая черта отменяет значение следующего за ней специального символа.)
в) / ($whatever) {3} / (Не забудьте про круглые скобки, иначе множитель будет действовать только на последний символ $whatever; этот вариант не проходит также в том случае, если $whatever содержит специальные символы.)
г) /[\000-\377] (5}/ или /(. |\п) (5)/ (Использовать точку без дополнительных знаков здесь нельзя, потому что она не соответствует символу новой строки.)
д) / (л l \s) (\s+) (\s+\2)+ (\s | $) / (\s — это не пробельный символ, а \2 — ссылка на все, что есть "слово"; знак " или альтернативный пробельный символ гарантирует, что \s+ начинается на границе пробельного символа.)
2. а) Вот один из способов решения этой задачи:
while (<STDIN>) {
if (/a/i && /e/i &S /i/i &S /o/i && /u/i) ( print;
)
Здесь у нас приведено выражение, состоящее из пяти операций сопоставления. Все эти операции проверяют содержимое переменной $_, куда управляющее выражение цикла while помещает каждую строку. Выражение даст значение "истина" лишь в том случае, если будут найдены все пять гласных.
Обратите внимание: если любая из пяти гласных не обнаруживается, остальная часть выражения пропускается, потому что операция && не вычисляет свой правый аргумент, если значение левого аргумента — "ложь".
б) Еще один способ:
while (<STDIN>) (
if (/a.*e.*i.*o.*u/i) ( print;
} )
Этот ответ, как оказывается, проще. Здесь у нас в операторе if используется более простое регулярное выражение, которое ищет пять гласных в указанной последовательности, разделенных любым количеством символов.
в) Вот один из способов решения этой задачи:
while “STDIN” (
if (/"[eiou]*a[лiou]*e[лaou]*i[^aeu]*o[лaei]*u["aeio]*$/i) ( print;
> )
Выглядит уродливо, но работает. Чтобы написать такое, просто подумайте: "Что может стоять между началом строки и первой буквой а?", а затем "Что может стоять между первой буквой а и первой буквой е?". В конце концов все решится само, от вас потребуется минимум усилий.
3. Вот один из способов решения этой задачи:
while (<STDIN>) {
chomp;
($user, $gcos) = (split /:/)[0,4];
($real) = split (/,/, $gcos) ;
print "$user is $real\n";
}
Во внешнем цикле while производится считывание по одной строке из файла паролей в переменную $_. По достижении последней строки цикл завершается.
Вторая строка тела цикла while означает разбиение строки на отдельные переменные с использованием в качестве разделителя двоеточия. Два из этих семи значений заносятся в отдельные скалярные переменные с имеющими смысл (мы надеемся) именами.
Поле GCOS (пятое поле) затем разбивается на части с использованием в качестве разделителя символа запятой, и список-результат присваивается одной скалярной переменной, заключенной в круглые скобки. Эти скобки играют важную роль — они указывают, что операция присваивания должна быть не скалярной, а для массива. Скалярная переменная $геа1 получает первый элемент списка-результата, а остальные элементы отбрасываются.
Оператор print затем выводит результаты на экран.
4. Вот один из способов решения этой задачи:
while (<STDIM>) (
chomp;
($gcos) = (split /:/)[4];
($real) =split(/,/, $gcos);
($first) ° split(/\s+/, $real);
$seen($first>++;
} foreach (keys %seen) (
if ($seen($_) > 1) {
print "$_ was seen $seen($_) times\n";
) }
Цикл while работает во многом так же, как цикл while из предыдущего упражнения. Помимо разбивки строки на поля и поля GCOS на реальное имя (и другие компоненты), в этом цикле осуществляется также разбиение реального имени на собственно имя и остальную часть. После определения имени элемент хеша в %seen инкрементируется, отмечая тот факт, что мы нашли конкретное имя. Обратите внимание: оператор print в этом цикле не используется.
В цикле foreach осуществляется проход по всем ключам хеша %seen (именам из файла паролей) с присваиванием каждого из них по очереди переменной $_. Если значение, записанное в %seen по данному ключу, больше 1, значит, это имя уже встречалось. Оператор if проверяет, так ли это, и при необходимости выводит соответствующее сообщение.
5. Вот один из способов решения этой задачи:
while (<STDIN>) (
chomp;
($user, $gcos) = (split /:/)[0,4];
($real) = split /,/, $gcos;
($first) = split (/\s+/, $real);
$seen($first) .= " $user";
}
foreach (keys %names) (
$this == $names{$_);
if ($this =~ /. /) {
print "$_ is used by:?this\n";
} }
Эта программа похожа на ответ к предыдущему упражнению, но вместо того чтобы просто подсчитывать, сколько раз у нас встречалось определенное имя, мы присоединяем регистрационное имя пользователя к элементу хеша % names, указывая имя в качестве ключа. Так, для Фреда Роджерса (регистрационное имя mrrogers) $names {"Fred"} становится равным " mrrogers", а когда появляется Фред Флинтстоун (регистрационное имя fred), $names ( "Fred" } становится равным " mrrogers fred". После завершения цикла у нас имеется список имен и список регистрационных имен всех имеющих данное имя пользователей.
В цикле foreach, как и в ответе к предыдущему упражнению, осуществляется проход по полученному в результате хешу, но вместо того, чтобы проверять, больше ли единицы значение элемента хеша, мы должны проверить, есть ли в этом значении более одного регистрационного имени. Для этого нужно занести значение в скалярную переменную $this и посмотреть, есть ли в нем после какого-нибудь символа пробел. Если есть, то одному реальному имени соответствуют несколько регистрационных имен, которые и указываются в выдаваемом в результате сообщении.
Изучаем Perl
В этой главе:
Определение пользовательской функции
Вызов пользовательской функции
Возвращаемые значения
Аргументы
Локальные переменные в функциях
Полулокальные переменные, созданные при помощи функции local
Создаваемые операцией my() переменные файлового уровня
Упражнения
Функции"
1. Вот один из способов решения этой задачи:
sub card {
my %card_map;
@card_map(l..9} = qw (
one two three four five six seven eight nine );
my($num) = @_;
if ($card_map($num}) {
return $card_map($num};
) else (
return $num;
) } # driver routine:
while (0) {
chomp;
print "card of $_ is ", &card($ ), "\n";
)
Подпрограмма scard (названная так потому, что она возвращает название на английском языке для данного числа) начинается с инициализации хеша-константы, который называется %card_map. Значения ему присваиваются так, что, например, $card_map {6} равно six; это делает преобразование достаточно простым.
С помощью оператора if мы определяем, принадлежит ли значение заданному диапазону, отыскивая это число в хеше: если в хеше имеется соответствующий элемент, проверка дает значение "истина", и данный элемент, являющийся соответствующим именем числительным, возвращается. Если соответствующего элемента нет (например, когда $num равно 11 или -4), то поиск в хеше возвращает значение undef и выполняется ветвь else оператора if, возвращая исходное число. Весь цикл, задаваемый оператором if, можно заменить одним выражением:
$card map($num) || $num;
Если значение слева от | | истинно, то это — значение всего выражения, которое затем и возвращается. Если оно ложно (например, когда значение переменной $num выпадает из диапазона), то вычисляется правая часть операции | |, возвращая значение $num.
Подпрограмма-драйвер последовательно получает строки, отсекает символы новой строки и передает их по одной в программу &card, выводя результат.
2. Вот один из способов решения этой задачи:
sub card ( ...; } # из предыдущего ответа print "Enter first number: ";
chomp($first = <STDIN>) ;
print "Enter second number: "; , chomp($second = <STDIN>) ;
$message = card($first) . " plus " .
card($second) . " equals " .
card($first+$second) . ".\n";
print "\u$message";
Первые два оператора print приглашают ввести два числа, а операторы, следующие сразу же за ними, считывают эти значения в $first и $second.
Затем путем троекратного вызова &card — по одному разу для каждого значения и один раз для суммы — формируется строка $message.
После формирования сообщения его первый символ с помощью операции \и переводится в верхний регистр. Затем сообщение выводится на экран.
3. Вот один из способов решения этой задачи:
sub card f
my %card_map;
@card_map(0..9} = qw (
zero one two three four five six seven eight nine );
my($num) = @_;
my($negative) ;
if ($num < 0) {
$negative = "negative ";
$num = - $num;
) if ($card_map($num)) (
return $negative . $card_map($num};
} else (
return $negative . $num;
)
Здесь мы объявили массив %card_map, чтобы обнулять его значения.
Первый оператор if инвертирует знак переменной $num и присваивает переменной $negative в качестве значения слово negative, если задаваемое в качестве аргумента число меньше нуля. После действия оператора if значение $num всегда неотрицательное, но при этом в переменную $negative записывается строка negative, которая в дальнейшем используется как префикс.
Второй оператор if определяет, находится ли значение переменной $num (теперь положительное) в хеше. Если да, то полученное в результате значение хеша присоединяется к префиксу, который хранится в $ negative, и возвращается. Если нет, то значение, содержащееся в $negative, присоединяется к исходному числу.
Последний оператор if можно заменить выражением:
$negative . ($card_map{$num) || $num) ;
Изучаем Perl
В этой главе:
Оператор last
Оператор next
Оператор redo
Метки
Модификаторы выражений
Операции && и || как управляющие структуры
Упражнения
Разнообразные управляющие структуры "
1. Вот один из способов решения этой задачи:
sub card (} # из предыдущего упражнения
while О ( ## НОВОЕ ##
print "Enter first number: ";
chomp($first = <STDIN>) ;
last if $first eq "end"; ## НОВОЕ ##
print "Enter second number: ";
chomp($second = <STDIN>) ;
last if $second eq "end"; ## НОВОЕ ##
$message = Scard ($first) . " plus " .
card($second) . " equals " .
card($first+$second) . ".\n";
print "\u$message";
} ## НОВОЕ ##
Обратите внимание на появление цикла while и двух операций last. Вот так-то!
Изучаем Perl
В этой главе:
Что такое дескриптор файла
Открытие и закрытие дескриптора файла
Небольшое отступление: функция die
Использование дескрипторов файлов
Операции для проверки файлов
Функции stat и Istat
Упражнения
Дескрипторы файлов и проверка файлов"
1. Вот один из способов решения этой задачи:
print "What file? ";
chomp($filename = <STDIN>);
open(THATFILE, "$filename") ||
die "cannot open Sfilename: $!";
while (<THATFILE>) (
print "$filename: $_"; # предполагается, что $ заканчивается \п }
В первых двух строках дается приглашение ввести имя файла, который затем открывается с дескриптором т hat file. Содержимое этого файла считывается с помощью дескриптора и выводится в stdout.
2. Вот один из способов решения этой задачи:
print "Input file name: ";
chomp($infilename = <STDIN>);
print "Output file name: ";
chomp($outfilename = <STDIN>);
print "Search string: ";
chomp($search = <STDIN>);
print "Replacement string: ";
chomp($replace = <STDIN>);
open(IN,$infilename) II
die "cannot open $infilename for reading: $!";
## необязательная проверка существования файла
## $outfilename
die "will not overwrite $outfilename" if -e $outfilename;
open (OUT,"$outfilename") ||
die "cannot create $outfilename: $!";
while (<IN>) { # читать строку из файла IN в $_
s/$search/$replace/g; # change the lines
print OUT $_; # вывести эту строку в файл OUT ) close (IN);
close (OUT) ;
Эта программа основана на программе копирования файлов, описанной выше в этой главе. К новым особенностям здесь относятся приглашение вводить строки и команда подстановки в середине цикла while, а также проверка возможности уничтожения уже существующего файла.
Обратите внимание на то, что обратные ссылки в регулярном выражении работают, а вот обращение к памяти в заменяющей строке — нет.
3. Вот один из способов решения этой задачи:
while (о) (
chomp; t удалить символ новой строки
print "$_ is readable\n" if -r;
print "$_ is writable\n" if -w;
print "$_ is executable\n" if -x;
print "$_ does not exist\n" unless -e;
}
При каждом выполнении цикла while читается имя файла. После удаления символа новой строки файл проверяется (с помощью остальных операторов) на наличие различных прав доступа.
4. Вот один из способов решения этой задачи:
while (<>) (
chomp;
$аде = -М;
if ($oldest_age < $аде) ( $oldest_name = $_;
$oldest_age = $аде;
} > print "The oldest file is $oldest_name ",
"and is $oldest age days old.\n";
Сначала мы выполняем цикл для каждого считываемого имени файла. Символ новой строки отбрасывается, а затем с помощью операции -м вычисляется возраст файла в днях. Если возраст превышает возраст самого старого из файлов, которые мы до сих пор видели, мы запоминаем имя файла и его возраст. Первоначально $oldest_age = 0, поэтому мы рассчитываем на то, что имеется хотя бы один файл, возраст которого больше 0 дней.
По завершении цикла оператор print выдает отчет.
Изучаем Perl
В этой главе:
Что такое формат
Определение формата
Вызов формата
Еще о поледержателях
Формат начала страницы
Изменение в форматах установок по умолчанию
Упражнения
Форматы"
1. Вот один из способов решения этой задачи:
open(PW,"/etc/passwd") II die "How did you get logged in?";
while (<PW>) (
($user,$uid,$gcos) = (split /:/)[0,2,4];
($real) ° split /,/,$gcos;
write;
(
format STDOUT =
@“<““ @>””> @“““““““““““““““
$user, $uid, $real
Первая строка открывает файл паролей. В цикле while этот файл обрабатывается построчно. Для того чтобы можно было загрузить скалярные переменные, каждая строка разбивается на части; в качестве разделителя используется двоеточие. Реальное имя пользователя выбирается из поля GCOS. Последний оператор цикла while вызывает функцию write для вывода всех данных.
Формат дескриптора файла stdout определяет простую строку с тремя полями. Их значения берутся из трех скалярных переменных, значения которым присваиваются в цикле while.
2. Вот один из способов решения этой задачи:
# прибавить к программе из первой задачи... format STDOOT_TOP = Username User ID Real Name
Все, что нужно для добавления к предыдущей программе заголовков страниц,— это добавить формат начала страницы. Указанным выражением мы помещаем заголовки в столбцы.
Чтобы выровнять столбцы, мы скопировали текст формата stdout и, используя в нашем текстовом редакторе режим замены, заменили поля @<“ линиями ====.Это можно сделать благодаря существованию посимвольного соответствия между форматом и получаемым результатом.
3. Вот один из способов решения этой задачи:
# прибавить к программе из первой задачи.. . format STDOUT_TOP = Page @<“ $%
Username User ID Real Name
Здесь для получения заголовков страниц мы опять-таки ввели формат начала страницы. Этот формат содержит также ссылку на переменную $%, которая автоматически присваивает странице номер.
Изучаем Perl
В этой главе:
Перемещение по дереву каталогов
Развертывание
Дескрипторы каталогов
Открытие и закрытие дескриптора каталога
Чтение дескриптора каталога
Упражнения
Доступ к каталогам"
1. Вот один из способов решения этой задачи:
print "Where to? ";
chomp($newdir = <STDIN>) ;
chdir($newdir) II die "Cannot chdir to $newdir: $!";
foreach (<*>) { print "$_\n";
}
В первых двух строках запрашивается и считывается имя каталога.
С помощью третьей строки мы пытаемся перейти в каталог с указанным именем. В случае неудачи программа аварийно завершается.
В цикле foreach осуществляется проход по списку. Но что это за список? Это развертывание в списочном контексте, в результате которого мы получим список всех имен файлов, совпадающих с образцом (в данном случае он задан как *).
2. Вот один из способов решения этой задачи — с помощью дескриптора каталога:
print "Where to? ";
chomp ($newdir = <STDIN>) ;
chdir($newdir) ||
die "Cannot chdir to $newdir: $!";
opendir(DOT,".") |I
die "Cannot opendir . (serious dainbramage): $!";
foreach (sort readdir(DOT)) { print "$_\n";
) closedir(DOT) ;
Так же, как в предыдущей программе, мы запрашиваем новый каталог. Перейдя в него посредством команды chdir, мы открываем каталог, создавая дескриптор каталога dot. В цикле foreach список, возвращенный функцией readdir (в списочном контексте) сортируется, а затем просматривается с присваиванием переменной $_ значения каждого элемента.
А вот как сделать это с помощью развертывания:
print "Where to? ";
chomp($newdir = <STDIN>) ;
chdir($newdir) || die "Cannot chdir to $newdir: $!";
foreach (sort <* .*>) ( print "$_\n";
)
Да, это, по сути дела, еще одна программа из предыдущего упражнения, но мы вставили перед операцией развертывания операцию sort и дополнили образец символами .*, чтобы найти файлы, имена которых начинаются с точки. Операция sort нам нужна по той причине, что файл !fred должен стоять перед точечными файлами, a barney —после них, но простого способа, позволяющего расставить их в таком порядке при помощи shell, нет.
Изучаем Perl
В этой главе:
Удаление файла
Переименование файла
Создание для файла альтернативных имен: связывание ссылками
Создание и удаление каталогов
Изменение прав доступа
Изменение принадлежности
Изменение меток времени
Упражнения
Манипулирование файлами и каталогамиff
1. Вот один из способов решения этой задачи:
unlink @ARGV;
Да, именно так. Массив @argv — это список имен, подлежащих удалению. Операция unlink получает список имен, поэтому нам нужно лишь соединить два этих компонента, и дело сделано.
Конечно, здесь не реализован ни механизм уведомления об ошибках, ни опции -f и -i, ни другие подобные вещи, но это было бы уже слишком серьезно. Если вы это сделали — отлично!
2. Вот один из способов решения этой задачи:
($old, $new) ” @ARGV; # назвать их
if (-d $new) ( # новое имя — каталог, его нужно откорректировать ($basename = $old) =~ s#.*/##s; # получить название собственно
# каталога $old $new .= "/$basename"; # и добавить его к новому имени > rename($old,$new) [| die "Cannot rename $old to $new: $!";
Рабочая лошадка в этой программе — последняя строка, но все остальное тоже нужно на тот случай, если новое имя принадлежит каталогу.
Сначала мы даем наглядные имена двум элементам массива oargv. Затем, если имя $new — каталог, нам нужно откорректировать его, добавив в конец нового имени собственно имя каталога $old. Это значит, что переименование /usr/src/fred
в /etc фактически приводит к переименованию /usr/src/fred в /etc/fred.
Наконец, после добавления собственно имени каталога мы завершаем задачу вызовом rename.
3. Вот один из способов решения этой задачи:
($old, $new) = 3ARGV; # назвать их
if (-d $new) ( # новое имя — каталог, его нужно откорректировать ($basename = $old) =~ s#.*/##s; # получить название собственно
# каталога $old $new .= "/$basenaroe"; # и добавить его к новому имени } link($old,$new) || die "Cannot link $old to $new: $!";
Эта программа идентична предыдущей программе за исключением самой последней строки, потому что мы создаем ссылку, а не выполняем переименование.
4. Вот один из способов решения этой задачи:
if ($ARGV[0] eq "-s") ( # нужна символическая ссылка ;
$symlink++; # запомнить shift(@ARGV); # и отбросить флаг -s
> • • ! • .. , . ' .' . ($old, $new) = @ARGV; * назвать их
if (-d $new) ( # новое имя — каталог, его нужно откорректировать
($basename = $old) =~ s#.*/##s; # получить название собственно # каталога $old
$new .= "/$basename"; # и добавить его к новому имени > if ($symlink) ( # wants a symlink
symlink($old,$new) ;
) else ( # нужна жесткая ссылка
link($old,$new); , )
Средняя часть этой программы — такая же, как в предыдущих двух упражнениях. Новые здесь — несколько первых и несколько последних строк.
В первых строках осуществляется проверка первого аргумента программы. Если этот аргумент равен -s, то скалярная переменная $ symlink инкрементируется, получая в результате значение 1. Затем выполняется сдвиг массива @argv, в результате чего удаляется флаг -s. Если флаг -s отсутствует, то ничего не делается и $symlink остается равной undef. Сдвиг массива @argv выполняется достаточно часто, поэтому имя массива @argv является аргументом функции shift по умолчанию; то есть вместо
shift(@ARGV) ;
мы могли бы написать
shift;
Последние несколько строк проверяют значение $symlink. Оно будет равно либо 1, либо undef, и на основании этого для файлов выполняется либо операция symlink, либо link.
5. Вот один из способов решения этой задачи:
foreach $f (<*>) {
print "$f -> $where\n" if defined($where =• readlink($f));
}
Скалярной переменной $f присваивается по очереди каждое из имен файлов текущего каталога. Для каждого имени переменной $where присваивается значение, полученное в результате выполнения функции readlink () для данного имени. Если имя — не символическая ссылка, то операция readlink возвращает undef, давая значение "ложь" в
проверке if, a print пропускается. Если же операция readlink возвращает какое-то значение, то print выводит название символической ссылки и имя файла или директории, на которую она указывает.