Использование анонимного FTP
Если вам никогда не приходилось пользоваться анонимным FTP, разберите приведенный ниже пример сеанса с комментариями. Текст, набранный жир-ным шрифтом — это то, что вы должны вводить с клавиатуры; комментарии набраны курсивом. Символ % — это приглашение, которое вводить не нужно.
% ftp ftp.CPAN.org
(на самом деле такого узла нет)
Connected to ftp.CPAN.org 220 CPAN FTP server (Version wu-2.4(l) Fri Dee 1 00:00:00 EST 1995} ready.
Name (ftp.CPAN.orgiCPAN) : anonymous
331 Guest login ok, send your complete e-mail address as password. Password: camal@nutshall.ccm
(здесь введите свое пользовательское имя и имя своего хоста)
230 Guest login ok, access restrictions apply. ftp> cd pub/perl/CPAN/src 250 CWD command successful -
ftp> binary
(для сжатых файлов нужно задать двоичный режим передачи)
ftp> get latest.tar.gz 200 PORT command successful. 150 Opening BINARY mode data connection for FILE. 226 Transfer complete.
{повторите этот шаг для каждого из нужных вам файлов)
ftp> quit 221 Goodbye. %
Получив файлы, распакуйте их, а затем сконфигурируйте, постройте и инсталлируйте Perl:
'"., gunzip < latest.tar.gz I tar xvf -% cd perl5.004 (используйте реальное имя каталога) Теперь любую из следующих двух строк:
% sh configure
{строчная буква с — для автоматического конфигурирования)
% sh Configure
{прописная буква С - для конфигурирования вручную)
% make
(построить весь Perl)
'i. make test
(проверить, работает ли он)
% make install
(для этого вы должны быть привилегированным пользователем)
* Суффикс .tar.gz. означает, что это стандартный Internet-формат архива, созданного програм-мой tar.
Как получить Perl
Основной пункт распространения Perl — это Comprehensive Perl Archive Network, или CPAN (Сеть полных Perl-архивов). Эти архивы содержат не только исходный код, но и практически все материалы, которые вам когда-либо понадобятся для работы с Perl. CPAN зеркально копируется десятками узлов по всему свету. Главный узел — ftp.funet.fi (128.214.248.6). Вы можете найти и более близкий к вам узел CPAN, получив файл /pub/languages/perl/CPAN/MIR-RORS с этого узла. Можно также обратиться с помощью Web-броузера к сервису мультиплексирования CPAN по адресу www.perl.com. Если вы запросите с этого Web-сервера файл, имя которого начинается на /CPAN/, он соединит вас с ближайшим узлом CPAN, выбрав его по вашему доменному имени. Вот некоторые популярные адреса (URL) в CPAN:
http://www.perl.corn/CPAN/
http://www.perl.com/CPAN/README.html
http://www.perl.corn/CPAN/modules
http://www.perl.com/CPAN/ports
http://www.perl.corn/CPAN/doc
http://www.perl.com/CPAN/sre/latest.tar.Gz
Сервис мультиплексирования CPAN пробует соединить вас с локальной быстродействующей машиной через высокопроизводительный концентра-тор. Это, однако, получается не всегда, потому что доменные имена могут и не отражать схемы сетевых соединений. Например, ваше хост-имя может заканчиваться на .se, но при этом вам лучше будет соединяться не со Швецией, а с Северной Америкой. В этом случае вы можете самостоятельно выбрать узел по следующему URL:
http://www.perl.com/CPAN
Обратите внимание на отсутствие косой черты в конце этого URL. Если конечная косая опущена, мультиплексор CPAN выдает меню дублирующих серверов CPAN, из который вы можете выбрать подходящий. Если ваш Web-броузер поддерживает встроенные переменные cookies, то мультиплек-сор CPAN автоматически запомнит выбранный вами узел.
Ниже перечислены машины, на которых должен быть исходный код Perl и копии списка дублирующих серверов CPAN. Эти материалы можно полу-чить по анонимному FTP. (Попробуйте использовать не IP-адреса, а имена машин, поскольку IP-адреса могут измениться.)
ftp.perl.corn (199.45.129.30)
ftp.cs.Colorado.edu (131.211.80.17)
ftp.funet.fi (128.214.248.6)
ftp.cs.run.nl (131.211.80.17)
Местонахождение главного каталога зеркального сервера CPAN на этих машинах может быть разным, но скорее всего это нечто вроде /pub/perl/CPAN.
Где находятся файлы В главном каталоге CPAN вы увидите как минимум следующие подкаталоги:
authors
Этот каталог содержит многочисленные подкаталоги, по одному для каждого автора программного обеспечения. Например, если вы захотите найти знаменитый модуль CGI.pm Линкольна Штейна, будучи твердо уверены, что именно он его автор, то можете посмотреть в каталоге authors/Lincoln__Stein. Если бы вы не знали, что Штейн — автор этого модуля, то можно было бы посмотреть в каталоге модулей, который описывается ниже.
doc
Каталог, содержащий всевозможную документацию на Perl. Это вся официальная документация (man-страницы) в нескольких форматах (тек-стовый ASCII, HTML, PostScript и собственный формат Perl POD), а также сборники часто задаваемых вопросов и интересные дополнитель-ные документы.
modules
Каталог содержит отдельные модули, написанные на С, Perl или обоих этих языках. Расширения позволяют вам эмулировать и использовать функцио-нальные возможности других программных продуктов, например, графиче-ских средств Tk, UNIX-библиотеки curses и математических библиотек. Они позволяют также взаимодействовать с базами данных (Oracle, Sybase и др.) и создавать HTML-файлы и CGI-сценарии.
ports
Каталог содержит исходный код и (или) двоичные файлы для Perl-портов к операционным системам, не поддерживаемых непосредственно в стан-дартном дистрибутиве. Эти порты " результат усилий других авторов, и не все они могут функционировать так, как описано в нашей книге.
scripts
Набор разнообразных сценариев, собранных со всего мира. Если вам нужно узнать, как сделать что-либо, или если вы просто хотите посмотреть, как другие пишут программы, просмотрите этот каталог. Подкаталог nutshell содержит примеры, приведенные в нашей книге. (Эти тексты можно также получить на узле издательства O'Reilly & Associates, ftp,ora.com, в каталоге /published/oreMy/nutshell/leammg_perl2/.
src
В этом каталоге вы найдете исходный код стандартного дистрибутива Perl. Его текущая редакция всегда находится в файле src/lutest.tar.gz. Этот боль-шой файл содержит весь исходный код и полную документацию. Конфи-гурирование и инсталляция должны быть относительно простыми как в UNIX- и UNIX-подобных системах, так и в VMS и OS/2. Начиная с версии 5.004, Perl инсталлируется также в 32-разрядных Windows-системах.
Как распространяется Perl
Perl распространяется по одной из двух лицензий (на ваш выбор). Первая — стандартная форма GNU Copyleft. Коротко говоря, это означает, что если вы можете выполнять Perl в своей системе, то должны иметь доступ к полному исходному коду языка без дополнительной платы. Вторая фор-ма — Artistic License, которую некоторые (особенно юристы) находят менее угрожающей, нежели Copyleft.
В каталоге/^дистрибутива Perl вы найдете ряд программ-примеров. Есть и другие лакомые кусочки — можете посвятить их поиску один из дождливых дней. Изучите исходный код Perl (если вы — С-хакер с мазохистскими наклонностями). Взгляните на тестовый комплект. Посмотрите, как Configure определяет наличие системного вызова mkdir(l). Определите, как Perl выпол-няет динамическую загрузку С-модулей. В общем, делайте все, что вам по душе.
Как выбирать модули
Процесс выборки и построения отдельных модулей Perl протекает немного по-другому. Скажем, вы хотите построить и инсталлировать модуль CoolMod. Сначала нужно выбрать его, воспользовавшись для этого командой ^(1) или с помощью Web-броузера обратиться к сервису модулей из hffp://www.perLcom/, который всегда выбирает самую последнюю версию конкретного зарегистри-рованного модуля. Адрес, который нужно ввести в броузер, выглядит так: http://www.perl.com/cgi-bin/cpanmod?module=CoolMod
Получив этот файл, сделайте следующее:
% gunzip < CoolMod-2.34. tar .gz I tar xvf -% cd CoolMod-2 .34
'ъ perl Makefile.PL
(создает реальный Makefile)
^ make
(построить весь модуль)
^ make test
(проверить, работает ли он)
°J make install
(для этого вы, наверное, должны быть привилегированным пользователем)
После успешной инсталляции модуля CoolMod (он автоматически поме-щается в директорию Perl-библиотеки вашей системы) в ваших программах можно использовать use CooiMod; а вы сможете читать документацию этого модуля с помощью команды man CoolMod (или perldoc CoolMod).
Манипулирование файлами и каталогами
В зтой главе мы покажем, как можно манипулировать самими файлами, а не только содержащимися в них данными. При демонстрации процедуры доступа к файлам и каталогам мы будем пользоваться семантикой UNIX (a также POSIX и Linux). Есть и другие механизмы доступа к файловим системам, но описываемые здесь средства являются стандартними для современных файлових систем.
Удаление файла
Вы уже научились создавать в Per! файл, открывая его для вывода через дескриптор файла. Сейчас ми освоим опасную процедуру удаления файлов (очень кстати для тринадцатой глави, не правда ли?).
Perl-функция unlink (названная по имени системного вызова POSIX) удаляет одно из имен файла (которнй может иметь и другие имена). Когда удаляется последнее имя файла и ни в одном процессе он не открыт, удаляется и сам файл. Зто в точности соответствует тому, что делает UNIX-команда гт. Поскольку у файла часто бывает только одно имя (если ви не создавали жесткие сснлки), то в большинстве случаев удаление имени можно считать удалением файла. Приняв зто допущенне, покажем, как удалить файл fred, а затем удалить файл, имя которого вводится во время выполнения программы:
unlink ("fred"); # распрощаемся с файлом fred print "what file do you want to delete? ";
chomp($name = <STDIN>) ;
unlink ($name) ;
Функция unlink может принимать также список имен, подлежащих удалению:
unlink ("cowbird","starling"); # убьем двух зайцев unlink <*.о>; # как "rm *.o" в shell
Операция <*. o> выполняется в списочном контексте и создает список имен файлов, которые совпадают с образцом. Зто именно то, что нужно передать в unlink.
Функция unlink возвращает количество успешно удаленных файлов. Если указан только один аргумент и соответствующий ему файл удаляется, то возвращается единица; в противном случае возвращается нуль. Если заданы имена трех файлов, но удалить можно только два, то возвращается два. Установить, какие именно файлы были удалены, на оснований возвра-щенного значення невозможно, позтому если вам нужно определить, какой файл не удален, удаляйте их по одному. Вот как производится удаление всех обьектных файлов (имена которых заканчиваются на . о) с выдачей сообще-ния об ошибке для каждого файла, который удалить нельзя:
foreach $file (<*.o>) ( # пройти по списку .о-файлов
unlink($file) || warn "having trouble deleting $file: $!";
1
Если unlink возвращает 1 ( зто означает, что единственный указанный файл был удален), то функция warn пропускается. Если имя файла не может быть удалено, результат "О" означает "ложь", позтому warn выполняется. Абстрактно зто можно прочитать так: "удали зтот файл или сообщи мне о нем".
Если функция unlink дается без аргументов, то по умолчанию вновь используетея переменная $_. Так, приведеними выше цикл можно записать следующим образом:
foreach (<*.о>) ( # пройти по списку .о-файлов
unlink || warn "having trouble deleting $_: $!";
Переименование файла
В shell UNIX имя файла изменяется с помощью команды mv. В Perl зта операция обозначается как rename {$старое,$новое). Вот как следует переименовать файл fred в barney:
rename("fred","barney") II die "Can't rename fred to barney: $!";
Как и большинство других функций, при успешном выполнении rename возвращает значение "истина", позтому, чтобы узнать, сработала ли функция rename, нужно проверить зтот результат.
Когда пользователь вводит mv файл какой-то_каталог, команда /иуделает пару закулисных фокусов и создает полное путевое имя (или, другими словами, полное описание пуги к месту размещения файла). Функция rename зтого делать не умеет. В Perl зквивалентная операция выглядит так:
rename("файл","какой-то_каталог/файл");
Обратите внимание: в Perl нужно указать имя файла в новом каталоге явно. Кроме того, команда mv копирует файл, когда он переименовывается, с одного смонтированного устройства на другое (если вы используете одну из достаточно совершенных операционных систем). Функция rename не настолько умна, позтому вы получите сообщение об ошибке, означающее, что зту проблему нужно решать каким-то иным способом (возможно, посредством вызова команды mv c теми же именами). Модуль File::Find поддерживает функцию move.
Создание для файла альтернативных имен: связывание ссылками
Иногда пользователю нужно, чтобы у файла было два, три, а то и дюжина имен (как будто одного имени файлу не хватает!). Операция присвоєння файлу альтернативних имен называется создание ссылок. Две основних формы создания ссылок — ато создание жестких ссылок и создание симво-лических (или мягких) ссылок. Не все виды файлових систем подцерживают оба типа сеилок или хотя би один из них. В зтом разделе описани файловие системи, соответствующие стандарту POSIX.
Жесткие и символические ссылки
Жесткая ссылка на файл неотличима от его исходного имени; ни одна из жестких ссилок не является более "реальним именем" для файла, чем любая другая.
Операционная система следит за тем, сколько жестких ссылок обознача-ют файл в кажднй данний момент времени. Когда файл впервые создается, у него имеется одна ссылка. Каждая новая жесткая ссылка увеличивает зто число, а каждая удаленная — уменьшает. Когда исчезает последняя ссылка на файл и файл закрнвается, он прекращает своє существование.
Каждая жесткая ссылка на файл должна располагаться в той же файловой системе (обычно зто диск или часть диска). По зтой причино нельзя создать новую жесткую ссилку на файл, находящийся в другой файловой системе.
В большинстве систем применение жестких ссилок для каталогов огра-ничено. Чтобы структура каталогов имела вид дерева, а не произвольную форму, для каталога допускается наличие только одного имени от корня, ссылки из "точечного" файла на данный каталог, и семейства жестких ссылок "точка-точка" из каждого из его подкаталогов. Если ви попитаєтесь создать еще одну жесткую ссылку на каталог, то получите сообщение об ошибке (если только вы не привилегированный пользователь — тогда вам придется провести всю ночь за восстановлением своей поврежденной фай-ловой системи).
Символическая ссылка — зто файл особого вида, который содержит в качестве данных путевое имя. Когда зтот файл открывается, операционная система рассматривает его содержимое как заменяющие символы для дан-ного путевого имени и заставляет ядро еще немного порыскать по дереву каталогов, используя новое имя.
Например, если символическая ссылка fred содержит имя barney, то указание открыть файл fred — зто, на самом деле, указание открыть файл barney. Если barney — каталог, то fred/wilma обозначает Ьатеу/wilma.
Содержимое символической ссылки (i.e. имя, на которое указывает символическая ссылка) не обязательно должно обозначать существующий файл или каталог. В момент, когда создаетсяу/'erf, существование barney вовсе не обязательно. Более того, Ьатеу может никогда и не появиться! Содержимое символической ссылки может указывать на путь, который ведет за пределы текущей файловой системи, позтому можно создавать символиче-скую ссылку на файл, находящийся в другой смонтированной файловой системо.
Отслеживая новое имя, ядро может натолкнуться на другую символиче-скую ссылку. Зта новая ссылка содержит новые злементы отслеживаемого пуги. Одни символические ссылки могут указывать на другие символические ссылки. Как правило, допускается до восьми уровней символических ссылок, но на практико такое встречается редко.
Жесткая ссылка защищает содержимое файла от уничтожения (потому что она считается одним из имен файла). Символическая же ссылка не может уберечь содержимое файла от исчезновения. Символическая ссылка может указывать на другие смонтированные файловые системи, а жесткая — не может. Для каталога может бить создана только символическая ссылка.
Создание жесткиж и символических ссылок в Perl
В ОС UNIX жесткие ссылки создают с помощью команди In. Например, команда
In fred bigdumbguy
позволяет создать жесткую ссилку из файла fred (которнй должен существо-вать) на bigdumbguy. В Per! зто виражается так:
link("fred","bigdumbguy") || die "cannot link fred to bigdumbguy";
Функция link принимает два параметра — старое имя файла и новий псевдоним для зтого файла. Если ссылка создана успешно, link возвращает значение "истина". Как и команда mv, UNIX-команда In позволяет указывать в качестве псевдонима только каталог (без имени файла). Функция link (как и функция rename) не настолько умна, позтому вы должны указывать полное имя файла явно.
При создании жесткой ссылки старое имя не может быть именем каталога*, а новый псевдоним должен указывать на ту же файловую систему. (Зти ограничения частично обусловили необходимость создания символи-ческих ссылок.)
В системах, которые поддерживают символические ссылки, в команде In может использоваться опция -s, которая создает символическую ссылку. Например, если необходимо создать символическую ссылку из barney на neighbor (чтобы обращение к neighbor фактичесїзі было обращением к barney), следует использовать команду
In -s barney neighbor
В Perl для зтого применяется функция symlink:
symlinkf"barney","neighbor") || die "cannot symlink to neighbor";
Отметим, что barney не обязательно должен существовать — ни сейчас, ни в будущем. В этом случае обращение к neighbor возвратит нечто туманное вроде No such file or directory.
Когда вы вызываете Is -1 для каталога, содержащего символическую ссылку, вы получаете как имя зтой ссылки, так и информацию о том, куда она указывает. В Perl зту же информацию можно получить с помощью функции readlink, которая по принципу работы удивительно похожа на системний вызов с тем же именем: она возвращает имя, на которое указывает заданная символическая ссылка. Так, в результате выполнения операции
if (defined($х = readlink("neighbor"))) ( print "neighbor points at '$x'\n";
вы получите сведения о barney, если все нормально. Если выбранная символическая ссылка не существует, не может быть прочитана или вообще не является таковой, readlink возвращает undef (т.е. вданном случае "ложь") — именно позтому мы ее здесь и проверяем.
В системах, не поддерживающих символические ссылки, обе функции — и symlink, и readlink — потерпят неудачу и выдадут сообщения об ошибке. Perl может "спрятать" от вас некоторые зависящие от конкретной системи особенности, но некоторые все равно проявляются. Зто как раз одна из них.
* Если только вы не привелигированный пользователь и не любите забавляться с командой fsck, восстановливая поврежденную файловую систему.
Создание и удаление каталогов
Вы не смогли бы вьшолнить указанные операции (во всяком случае, в UNIX-системе), не зная о команде mkdir(\), которая создает каталоги, содержащие файлы и другие каталоги. В Perl єсть зквивалент зтой коман-ды — функция mkdir, которая в качестве аргументов принимает имя нового каталога и некое число, определяющее права доступа к созданному каталогу. Права доступа задаются как число, интерпретируемое во внутреннем формате прав доступа. Если вы не знакомы с внутренним форматом прав доступа, обратитесь к man-странице chmod(2). Если вам некогда с зтим разбираться, просто укажите права доступа как 0777, и все будет нормально*. Вот пример создания каталога с именем gravelpit:
mkdir ("gravelpit",0777) || die "cannot mkdir gravelpit: $!";
UNIX-команда rmdir(l) удаляет пустые каталоги. В Perl єсть ее зквивалент с тем же именем. Вот как можно сделать Фреда безработным:
rmdir ("gravelpit") I| die "cannot rmdir gravelpit: $!";
Хотя зти Perl-операции используют преимущества системных вызовов с такими же именами, они будут вьшолняться (хотя и чуточку медленнее) даже в системах, не поддерживающих такие вызовы. Perl вызывает утилиты mkdir и rmdir (или как там они называются у вас в системо) автоматически.
Изменение прав доступа
Права доступа к файлу или каталогу определяют, кто (в широком смысле слова) и что может делать с зтим файлом или каталогом. В UNIX общепри-нятый метод изменения прав доступа к файлу — применение команды chmod(l). (Если вы не знакомы с зтой операцией, обратитесь к ее man-странице.) В Perl права доступа изменяются с помощью функции chmod. Зта функция получает в качестве аргументов заданный восьмеричним числом режим доступа и список имен фаилов и пытается изменить права доступа ко всем зтим файлам в соответствии с указанным режимом. Чтобы сделать файлы fred и Ьатеу доступными в режимах чтения и записи для всех пользователей, нужно вьшолнить такую операцию:
chmod(0666,"fred","barney");
Режим 0666 обозначает чтение и запись для владельца, группы и прочих пользователей, т.е. как раз то, что нам нужно.
* В данном случае вы не создаете каталог с самими широкими правами доступа. Определить права доступа вам также поможет текущая маска доступа umask вашего процесса. В UNIX-системах см. описание комавды shell umask или man-страницу umask(2).
Функция chmod возвращает число файлов, для которых были успешно изменены права доступа (даже если в результате фактически ничего не изменилось). Таким образом, в отношении контроля ошибок она работает аналогично функции unlink. Позтому, чтобы изменить права доступа к файлам fred и Ьагпеу и выполнить контроль ошибок в каждом случае, необходимо использовать следующую конструкцию:
foreach $file ("fred","barney") f unless chmod (0666,$file) (
warn "hmm... couldn't chmod $file.\$!";
Изменение принадлежности
Каждый файл в файловой системе (обычный, каталог, файл устройства и т.д.) имеет владельца и группу. Зги параметры определяют, кому принадлежат права доступа, установленные для файла по категориям "владелец" и "группа" (чтение, запись и (или) выполнение). Владелец и группа определяются в момент создания файла, но при определенных обстоятельствах вы можете изменить их. (Зти обстоятельства зависят от конкретной разновидности UNIX, c которой вы работаете; подробности см. на man-странице chown.)
Функция chown получает идентификатор пользователя (UID), иденти-фикатор группы (GID) и список имен файлов и пытается изменить принад-лежность каждого из перечисленных файлов в соответствии с указанными идентификаторами. Успешному результату соответствует ненулевое значе-ние, равное числу файлов, принадлежность которых изменена (как в функ-циях chmod и unlink). Обратите внимание: вы одновременно меняете и владельца, и группу. Если какой-то из зтих идентификаторов вы менять не хотите, поставьте вместо него -1. Помните, что нужно использовать числовые UID и GID, а не соответствующие символические имена (хотя команда chmod и принимает такие имена). Например, если UID файла fred — 1234, а идентификатор группы stoners, которой зтот файл принадлежит по умолчанию,— 35, то в результате применения следующей команды файлы slate и granite переходят к пользователю fred и его группе:
chown(1234, 35, "slate", "granite"); # то же, что й
# chown fred slate granite
# chgrp stoners slate granite
В главе 16 вы узнаете, как преобразовать
fred в 1234 и stoners в 35.
Изменение меток времени
С кажднм файлом связан набор из трех меток времени. Мы вкратце упоминали о них, когда говорили о том, как получить информацию о файле:
зто время последнего доступа, время последнего изменения и время послед-него изменения индексного дескриптора. Первым двум моткам времени можно присвоить произвольные значення с помощью функции utime (ко-торая соответствует системному вызову utime в ОС UNIX). При установке двух зтих значений третья метка автоматически устанавливается в текущее время, позтому отдельного способа для ее установки не предусмотрено.
Зти значення устанавливаются во внутреннем формате времени, а имен-но в количестве секунд, прошедших после полуночи 1 января 1970 года по среднегринвичскому времени. Когда мы писали нашу книгу, зта цифра достигла 800 миллионов с небольшим. (Во внутреннем формате она пред-ставляется как 32-разрядное число без знака, и если все мы не перейдем на 64-разрядные (й более) машины, то переполнение наступит где-то в следую-щем столетии. У нас будут гораздо более серьезные проблеми в 2000-м году*.)
Функция utime работает аналогично функциям chmod и unlink. Она получает список имен файлов и возвращает число файлов, параметри времени которых были изменены. Вот что нужно сделать, чтобы файли fred и barney выглядели так, будто они изменялись в недавнем прошлом:
$atime = $mtime = 700_000_000; # некоторое время назад utime($atime,$mtime,"fred","barney")
Никакого "розумного" значення для меток времени нет: можно сделать так, чтобы файл выглядел сколь угодно старым, или чтобы казалось, будто он бил изменен в далеком будущем (зто полезно, если вы пишете научно-фантастические рассказы). Вот как, например с помощью функции time (которая возвращает текущее время как метку времени UNIX) можно сделать так, чтобы казалось, будто файл max_headroom был изменен спустя 20 минут после текущего момента времени:
$when = time() + 20*60; # 20 минут с текущего момента utime($when,$when,"max headroom");
* Perl- функции localtime nqmtime работаюттак, каквС: они возвращают год, изкоторого вычтена цифра 1900. В 2003-м году localtime выдаст год как 103.
Упражнения
Ответы к упражнениям см. в приложении А.
Напишите программу, которая работает как утилита гт, удаляя файлы, имена которых были заданы как аргументи командной строки при вызове программы. (Никакие опции команды ті вам для зтого не понадобятся.)
Обязательно проверьте зту программу с почти пустым каталогом, чтобы случайно не удалить нужные файлы! Помните, что аргументы командной строки при запуске программы извлекаются из массива @argv.
Напишите программу, которая работает как утилита mv, переименовывая первый аргумент командной строки во второй аргумент. (Никакие опции команды mv вам для зтого не нужны, и аргументов нужно всего два.) Можете также рассмотреть случай переименования, когда целевым обь-ектом является каталог.
Напишите программу, которая работает, как In, создавая жесткую ссьиіку из первого аргумента командной строки на второй аргумент. (Никакие опции команды In вам для зтого не нужны, и аргументов нужно всего два.)
Если у вас єсть символические ссылки, модифицируйте программу из предыдущего упражнения так, чтобы она работала с необязательным ключом -s.
Если у вас єсть символические ссылки, напишите программу, которая ищет в текущем каталоге все файлы, на которые єсть такие ссылки, и выводит их имена и значення ссылок так, как зто делает Is -1 (имя -> значение). Создайте в текущем каталоге несколько символических ссы-лок и проверьте программу.
| Назад | Вперед |
| Содержание | Предисловие | Введение | Ссылки | Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10 | Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19 | Приложение А | Приложение Б | Приложение В | Приложение Г | |
Массивы и списочные данные
Список и массив
Список — это упорядоченные скалярные данные. Массив -- переменная, которая содержит список. Каждый элемент массива — это отдельная скалярная переменная с независимым скалярным значением. Значения в списке упорядочены, т.е. расставлены в определенной последовательности, например от младшего элемента к старшему.
Массивы могут иметь любое число элементов. Минимально возможный массив не имеет элементов вообще, тогда как максимально возможный может заполнять всю наличную память. Это еще одно подтверждение принятой в Perl стратегии "отсутствия ненужных ограничений".
Литеральное представление
Списочный литерал (способ представления значения списка в программе) состоит из значений, отделенных друг от друга запятыми и заключенными в круглые скобки. Эти значения образуют элементы списка. Например:
(1,2,3) # массив из трех значений 1, 2 и 3 ("fred",4.5) # два значения — "fred" и 4,5
Элементы списка не обязательно должны быть константами. Это могут быть выражения, которые вычисляются при каждом использовании литерала. Например:
($а, 17; # два значения: текущее значение переменной $а и 17 ($b+$c,$d+$e) # два значения
Пустой список (список, не содержащий элементов) представляется пустой парой круглых скобок:
() # пустой список (нуль элементов)
Элемент списочного литерала может включать операцию конструктора списка. Это два скалярных значения, разделенных двумя точками. Данная операция создает список значений, начиная с левого скалярного значения и кончая правым скалярным значением, с шагом 1. Например:
(1 .. 5) # то же самое, что (1, 2, 3, 4, 5)
(1.2 .. 5.2) # то же самое, что (1.2, 2.2, 3.2, 4.2, 5.2)
(2 .. 6,10,12) # тоже самое, что (2,3,4,5,6,10,12)
($а .. $b) # диапазон, заданный текущими значениями переменных $а и $Ь
Если правый скаляр меньше левого, то список будет пустым, так как в обратном направлении отсчет вести нельзя. Если последнее значение не соответствует целому числу шагов, то список заканчивается там, где приращение на единицу привело бы к появлению числа, не принадлежащего заданному диапазону:
(1.3 .. 6.1) # то же самое, что (1.3,2.3,3.3,4.3,5.3)
Списочные литералы с множеством коротких текстовых строк со всеми этими кавычками и запятыми выглядят не очень привлекательно:
@а = ("fred","barney","betty","wilma"); # уф-ф!
Поэтому предусмотрена функция заключения в кавычки, которая создает список из разделенных пробелами слов, заключенных в круглые скобки*:
@а = qw(fred barney betty wilma); # так-то лучше! @а = qw(
fred
barney
betty
wilma ); # то же самое
Одно из возможных применений списочного литерала — в качестве аргумента функции print, с которой мы уже знакомы. Элементы списка выводятся на экран без промежуточных пробельных символов:
print("The answer is ",$a,"\n"); # трехэлементный списочный литерал
Этот оператор выводит на экран слова The answer is, затем пробел, значение переменной $а и символ новой строки. Другие способы использования списочных литералов мы рассмотрим позднее.
* Как и в функциях сопоставления с образцом, о которых мы узнаем позже, в качестве разделителя здесь вместо круглых скобок можно использовать любой символ, не относящийся к числу пробельных символов, букв и цифр.
Переменные
Переменная-массив содержит одно значение в виде списка (нуль или более скалярных значений). Имена переменных-массивов похожи на имена скалярных переменных. Единственное отличие состоит в первом символе —-это не знак доллара ($), а знак @. Например:
@fred # массив-переменная @fred @A_Very_Long_Array_Variable_Name @A_Very_Long_Array_Variable_Name_that_is_different
Отметим здесь, что переменная-массив @fred не имеет никакого отношения к скалярной переменной $fred. Для разных типов переменных Perl предусматривает отдельные пространства имен.
Переменная-массив, которой еще не присвоено значение, имеет значение (), т.е. пустой список.
Выражение может ссылаться на переменные-массивы в целом, а также проверять и изменять отдельные элементы таких массивов.
Операции над массивами и функции обработки массивов
Операции над массивами и функции обработки массивов манипулируют целыми массивами. Некоторые из них возвращают список, который затем можно использовать как значение в другой функции обработки массивов или присвоить переменной-массиву.
Присваивание
Вероятно, самая важная операция, проводимая над массивами -— операция присваивания, посредством которой переменной-массиву присваивается значение. Эта операция, как и скалярная операция присваивания, обозначается знаком равенства. Perl определяет тип присваивания (скалярное или для массива), анализируя, какой переменной присваивается значение — скалярной или массиву. Например:
@fred = (1,2,3); # массив fred получает трехэлементное литеральное значение Qbarney = @fred; # теперь оно копируется в @barney
Если переменной-массиву присваивается скалярное значение, оно становится единственным элементом этого массива:
@huh =1; #1 автоматически становится списком (1)
Имена переменных-массивов могут входить в списочный литерал. При вычислении значений такого списка Perl заменяет имена массивов текущими значениями, например:
@fred = qw(one two);
@barney = (4,5,@fred,6,7) ; # @barney превращается в (4,5,"one","two",6,7) @barney = (8,@barney); # в начале списка элементов Qbarney ставится 8
# и "last" в конце. Qbarney = (@barney,"last") ; # @barney превращается в
# (8,4,5,"one","two",6,7,"last")
Отметим, что вставленные подобным образом элементы массива находятся на том же уровне иерархии, что и остальная часть литерала: список не может содержать другой список в качестве элемента*.
Если списочный литерал содержит только ссылки на переменные (а не выражения), то этот литерал также можно рассматривать как переменную. Другими словами, такой списочный литерал можно использовать в левой части операции присваивания. Каждая скалярная переменная в списочном литерале принимает соответствующее значение из списка, стоящего в правой части операции присваивания. Например:
($а,$Ь,$с) = (1,2,3); # присвоить 1 переменной $а, 2 — переменной $Ь,
# 3 — переменной $с ($a,$b) = ($b,$a); # поменять местами $а и $Ь ($d,@fred) = ($a,$b,$c) # присвоить $d значение $а, a @fred — значение ($Ь,$с) ($e,@fred) = @fred; # переместить первый элемент массива @fred
# в переменную $е. В результате @fred = ($с),
# а $е = $Ь
Если число присваиваемых элементов не соответствует числу переменных, то лишние значения (стоящие справа от знака равенства) просто отбрасываются, а все лишние переменные (слева от знака равенства) получают значение undef.
Переменная-массив, входящая в литеральный список, должна стоять в нем последней, потому что переменные-массивы очень "прожорливы" и поглощают все оставшиеся значения. (Конечно, после нее можно поставить и другие переменные, но всем им будет присвоено значение undef.)
Если переменная-массив присваивается скалярной переменной, то присваиваемое число является размером массива, например:
@fred = (4,5,6); # инициализация массива @fred $а = @fred; # $а получает значение 3, текущий размер массива @fred
Размер возвращается и в том случае, если имя переменной-массива используется там, где должно быть скалярное значение. (В разделе "Скалярный и списочный контексты" мы увидим, что это называется использованием имени
* Хотя ссылка на список и может быть элементом списка, на самом деле это не означает использование списка в качестве элемента другого списка. Впрочем, работает это почти так же, что позволяет создавать многомерные массивы. См. главу 4 книги Programming Perl и man-страницу perllol( I).
массива в скалярном контексте.) Например, чтобы получить число на единицу меньшее, чем размер массива, можно использовать @fred-l, так как скалярная операция вычитания требует наличия скаляров в обеих частях. Обратите внимание на следующий пример:
$а = @fred; # переменной $а присваивается размер массива @fred ($а) = @fred; # переменной $а присваивается первый элемент @fred
Первое присваивание — скалярное, и массив @fred рассматривается как скаляр, поэтому значение переменной $а будет равно размеру массива. Второе присваивание — для массива (несмотря на то, что требуется всего одно значение), поэтому переменной $а в качестве значения присваивается первый элемент массива @fred, а остальные элементы просто отбрасываются.
В результате выполнения присваивания для массива мы получаем значение, представляющее собой список. Это позволяет делать "каскадиро-вание". Например:
@fred = (Qbarney = (2,3,4)); # @fred и @barney получают значения (2,3,4) @fred = @barney = (2,3,4); # то же самое
Обращение к элементам массива
До сих пор мы рассматривали массив в целом, добавляя и удаляя значения с помощью присваивания для массива. Многие полезные программы так и построены — с использованием массивов, но без обращения к их элементам. Perl, однако, предоставляет и традиционный способ обращения к элементам массива по их числовым индексам.
Элементы массива нумеруются последовательными целыми числами с шагом 1, начиная с нуля*. Первый элемент массива @fred обозначается как $fred[0]. Обратите внимание: при ссылке на элемент вместо знака @ в имени массива используется знак $ . Это объясняется тем, что обращение к элементу массива идентифицирует скалярную переменную (часть массива), которой в результате может быть присвоено значение или которая используется в выражении, например:
@fred = (7,8,9);
$b = $fred[0]; # присвоить $Ь значение 7 (первый элемент массива @fred) $fred[0] = 5; # теперь @fred = (5,8,9)
Точно так же можно обращаться к другим элементам:
$с = $fred[l]; # присвоить $с значение 8
$fred[2]++; # инкрементировать третий элемент массива @fred
$fred[l] +=4; # прибавить 4 ко второму элементу
($fred[0],$fred[l]) = ($fred[l],$fred[0]) ;
# поменять местами первые два элемента
* Значение индекса первого элемента можно изменить на что-нибудь другое (например, на "1"). Это, однако, повлечет за собой очень серьезные последствия: может запутать тех, кто будет сопровождать ваш код, и повредить программы, которые вы заимствуете у других. По этой причине настоятельно рекомендуем считать такую возможность отсутствующей.
Обращение к списку элементов одного массива (как в последнем примере) называется срезом" и встречается достаточно часто, поэтому для него есть специальное обозначение.
@fred[0,l] * то же, что и ($fred[0],$fred[l]) @fred[0,l] = @fred[l,0] # поменять местами первые два элемента @fred[0,l,2] = @fred[l,l,l] # сделать все три элемента такими, как второй @fred[l,2] = (9,10); # заменить последние два значения на 9 и 10
Обратите внимание: в этом срезе используется не $, а @. Это вызвано тем, что вы создаете переменную-массив (выбирая для этого часть массива), а не скалярную переменную (обращаясь к одному элементу массива).
Срезы работают также с литеральными списками и вообще с любой функцией, которая возвращает список:
@who = (qw(fred barney betty wilma))[2,31;
# как @х = qw(fred barney betty wilma); @who = @x[2,3]
Значения индексов в этих примерах — литеральные целые, но индекс может быть и любым выражением, которое возвращает число, используемое затем для выбора соответствующего элемента:
@fred ” (7,8,9) ;
$а = 2;
$b " $fred[$a); f как $fred[2], или 9
$с = $fred[$a-l]; # $c получает значение $fred[l], или 8
($с) = (7.8,9)[$а-1]; # то же самое, но с помощью среза
Таким образом, обращение к массивам в Perl-программах может производиться так же, как во многих других языках программирования.
Идея использования выражения в качестве индекса применима и к срезам. Следует помнить, однако, что индекс среза — список значений, поэтому выражение представляет собой выражение-массив, а не скаляр.
Sfred = (7,8,9); f как в предыдущем примере Bbarney =(2,1,0);
Obackfred = @fred[@barney];
# то же, что и @fred[2,l,0], или ($fred[2],$fred[l],$fred[0]), или (9,8,7)
Если обратиться к элементу, находящемуся за пределами массива (т.е. задав индекс меньше нуля или больше индекса последнего элемента), то возвращается значение undef. Например:
@fred = (1,2,3) ;
$barney = $fred[7]; # $barney теперь имеет значение undef
* Это перевод английского термина slice, использованный в данной книге. Более точно суть операции можно было бы выразить термином вырезка, но мы остановились на слове срез, чтобы не вызывать гастрономических ассоциаций у любителей мясных блюд — Прим. ред.
Присваивание значение элементу, находящемуся за пределами текущего массива, автоматически расширяет его (с присваиванием всем промежуточным значениям, если таковые имеются, значения undef). Например:
@fred = (1,2,3);
fred[3] = "hi"; # @fred теперь имеет значение (1,2,3,"hi") $fred[6] = "ho"; # @fred теперь имеет значение
# (1,2,3,"hi",undef,undef,"ho")
Присваивание значения элементу массива с индексом меньше нуля является грубой ошибкой, потому что происходит такое, скорее всего, из-за Очень Плохого Стиля Программирования.
Для получения значения индекса последнего элемента массива @fred можно использовать операцию $#fred. Можно даже задать это значение, чтобы изменить размер массива @fred, но это, как правило, не нужно, потому что размер массива увеличивается и уменьшается автоматически.
Использование отрицательного индекса означает, что следует вести обратный отсчет от последнего элемента массива. Так, последний элемент массива можно получить, указав индекс -1. Предпоследний элемент будет иметь индекс -2 и т.д. Например:
@fred = ("fred", "wilma", "pebbles", "dino");
print $fred(-l]; # выводит "dino" print $#fred; # выводит 3 print $fred[$#fred]; # выводит "dino"
Функции push и pop
Одним из распространенных вариантов использования массива является создание стека данных, где новые значения вводятся и удаляются с правой стороны списка. Эти операции применяются довольно часто, поэтому для них предусмотрены специальные функции:
push(@mylist,$newvalue); # означает Omylist =• (@mylist,$newvalue) $oldvalue = pop($mylist); # удаляет последний элемент из @mylist
Если в функцию pop введен пустой список, она возвращает undef, не выдавая, в соответствии с принятым в Perl этикетом, никакого предупреждающего сообщения.
Функция push также принимает список значений, подлежащих помещению в стек. Эти значения вводятся в конец списка. Например:
@mylist = (1,2,3);
push(@mylist,4,5,6) ; # @mylist = (1,2,3,4,5,6)
Отметим, что первый аргумент должен быть именем переменной-масси-ва, потому что для литеральных списков функции push и pop смысла не имеют.
Функции shift и unshift
Функции push и pop действуют в "правой" части списка (части со старшими индексами). Функции unshift и shift выполняют соответствующие действия в "левой" части списка (части с младшими индексами). Вот несколько примеров:
unshift(@fred,$a); # соответствует Bfred = ($a,@fred);
unshift (@fred,$a,$b,$c); # соответствует @fred = ($а,$b,$c,@fred);
$х = shift(@fred); # соответствует ($x,@fred) = @fred;
# с реальными значениями @fred = (5,6,7) ;
unshift(@fred,2,3,4); # @fred теперь имеет значение (2,3,4,5,6,7) $х = shift(@fred) ;
# $х получает значение 2, @fred теперь имеет значение (3,4,5,6,7)
Как и функция pop, функция shift, если в нее ввести пустую перемен-ную-массив, возвращает значение undef.
Функция reverse
Функция reverse изменяет порядок следования элементов аргумента на противоположный и возвращает список-результат. Например:
@а = (7,8,9) ;
@b = reverse(@a); t присваивает @Ь значение (9,8,7) @b == reverse (7,8,9); # делает то же самое
Обратите внимание: список-аргумент не изменяется, так как функция reverse работает с копией. Если вы хотите изменить порядок элементов "на месте", список-аргумент следует затем присвоить той же переменной:
@Ь = reverse (@b); t присвоить массиву @Ь его же значения,
# но расположить его элементы в обратном порядке
Функция sort
Функция sort сортирует аргументы так, как будто это отдельные строки, в порядке возрастания их кодов ASCII. Она возвращает отсортированный список, не изменяя оригинал. Например:
@х ° sort("small","medium","large") ;
# @х получает значение "large", "medium", "small" @у = (1,2,4,8,16,32,64) ;
@у = sort (@y); # @у получает значение 1, 16, 2, 32, 4, 64, 8
Отметим, что сортировка чисел производится не по их числовым значениям, а по их строковым представлениям (1,16, 2, 32 и т.д.). Изучив главу 15, вы научитесь выполнять сортировку по числовым значениям, по убыванию, по третьему символу строки и вообще каким угодно методом.
Функция chomp
Функция chomp работает не только со скалярной переменной, но и с массивом. У каждого элемента массива удаляется последний пробельный символ. Это удобно, когда вы, прочитав несколько строк как список отдельных элементов массива, хотите одновременно убрать из всех строк символы новой строки. Например:
@stuff = ("hello\n","world\n","happy days") ;
chomp(@stuff); # Sstuff теперь имеет значение ("hello","world","happy day")
Скалярный и списочный контексты
Как видите, каждая операция и функция предназначена для работы с определенной комбинацией скаляров или списков и возвращает скаляр или список. Если операция или функция рассчитывает на получение скалярного операнда, то мы говорим, что операнд или аргумент обрабатывается в скалярном контексте. Аналогичным образом, если операнд или аргумент должен быть списочным значением, мы говорим, что он обрабатывается в списочном контексте.
Как правило, это особого значения не имеет, но иногда в разных контекстах можно получить совершенно разные результаты. Например, в списочном контексте @fred возвращает содержимое массива Sfred, а в скалярном — размер этого массива. При описании операций и функций мы упоминаем эти тонкости.
Скалярное значение, используемое в списочном контексте, превращается в одноэлементный массив.
<STDIN> как массив
Одна из ранее изученных нами операций, которая в списочном контексте возвращает иное значение, чем в скалярном, — <stdin>. Как упоминалось выше, <stdin> в скалярном контексте возвращает следующую введенную строку. В списочном же контексте эта операция возвращает все строки, оставшиеся до конца файла. Каждая строка при этом возвращается как отдельный элемент списка, например:
$а = <STDIN>; # читать стандартный ввод в списочном контексте
Если пользователь, выполняющий программу, введет три строки и нажмет [Ctrl+D]* (чтобы обозначить конец файла), массив будет состоять из трех элементов. Каждый из них является строкой, заканчивающейся символом новой строки, и соответствует введенной пользователем строке.
* В некоторых системах конец файла обозначается нажатием клавиш [Ctrl+Z], а в других эта комбинация служит для приостановки выполняемого процесса.
Интерполяция массивов
Как и скаляры, значения массивов могут интерполироваться в строку, заключенную в двойные кавычки. Каждый элемент массива заменяется его значением, например:
Ofred = ("hello","dolly");
$у = 2;
$х = "This is $fred[l]'s place"; # "This is dolly's place" $x = "This is $fred($y-l]'s place"; # To же самое
Отметим, что индексное выражение вычисляется как обычное, как будто оно находится вне строки, т.е. оно предварительно не интерполируется.
Если вы хотите поставить после простой ссылки на скалярную переменную литеральную левую квадратную скобку, нужно выделить эту скобку так, чтобы она не считалась частью массива:
Ofred = ("hello","dolly"); # присвоить массиву @fred значение для проверки $fred = "right";
# мы пытаемся сказать "this is right[1]" $х = "this is $fred[l]"; t неправильно, дает "this is dolly" $x = "this is ${fred}[l]"; t правильно (защищено фигурными скобками) $х = "this is $fred"."[1]"; # правильно (другая строка) $х = "this is $fred\[l]"; t правильно (скобку защищает обратная косая)
Аналогичным образом может интерполироваться список значений перемен-ной-массива. Самая простая интерполяция — интерполяция всего массива, обозначенного именем (с начальным символом @). В этом случае элементы интерполируются по очереди, с разделением их пробелами, например:
@fred = ("а", "bb","ccc",1,2,3) ;
$а11 = "Mow for Sfred here!";
# $all получает значение "Now for a bb ccc 123 here!"
Можно также выбрать часть массива с помощью среза:
@fred = ("а","bb","ccc",1,2,3);
$а11 = "Now for @fred[2,3] here!";
# $all получает значение "Now for ccc 1 here!" $all = "Now for @fred[@fred[4,5]] here!"; ” то же самое
Опять-таки, если вы хотите поставить после ссылки на имя массива литеральную левую квадратную скобку, а не индексное выражение, можете использовать любой из описанных выше механизмов.
Упражнения
Ответы к упражнениям приведены в приложении А.
1. Напишите программу, которая читает список строковых значений, стоящих в отдельных строках, и выводит этот список в обратном порядке. Если вы читаете список на экране, то вам, вероятно, нужно будет выделить конец списка, нажав комбинацию клавиш "конец файла" (в UNIX или Plan 9 это, наверное, [Ctri+D], а в других системах чаще всего [Ctrl+Z]).
2. Напишите программу, которая читает число, затем список строковых значений (находящихся в отдельных строках), после чего выводит одну из строк списка в соответствии с указанным числом.
3. Напишите программу, которая читает список строк, а затем выбирает и выводит на экран случайную строку из этого списка. Чтобы выбрать случайный элемент массива @somearray, поместите в начало программы функцию
srand;
(она инициализирует генератор случайных чисел), а затем используйте
rand(@somearray)
там, где требуется случайное значение, меньшее размера массива @ some-array.
| Назад | Вперед |
| Содержание | Предисловие | Введение | Ссылки | Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10 | Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19 | Приложение А | Приложение Б | Приложение В | Приложение Г | |
Обозначения, принятые в книге
В нашей книге используются следующие обозначения:
Курсив
используется для имен файлов и команд. Курсивом также выделяются термины при первом употреблении.
Моноширинный шрифт
используется в примерах и обычном тексте для выделения операций, переменных и результатов работы команд и программ.
Моноширинный жирный
используется в примерах для выделения данных, которые вводятся поль-ювателем с терминала.
Моноширинный курсив
используется в примерах для выделения переменных, вместо которых в 1ависимости от контекста нужно подставлять значения. Например, пере-менную имя_файла необходимо заменить именем реального файла.
Сноски
используются для ввода дополнительных примечаний. Читая книгу в первый раз, не обращайте на них внимания. Иногда для упрощения изложения материала в основном тексте говорится не вся правда, а в сноске приводятся необходимые уточнения. Во многих случаях материал, данный в сноске, представляет собой более сложную информацию, которая в книге вообще может не рассматриваться.
Ответы к упражнениям
В этом приложении даны ответы к упражнениям, помещенным в конце каждой главы.
Глава 2 "Скалярные данные"
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 нет символа новой строки, поскольку мы считаем, что $с в любом случае заканчивается этим символом.
Глава 3 "Массивы и списочные данные"
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)]";
Первая строка запускает генератор случайных чисел. Вторая строка считывает группу строк. Третья строка выбирает случайный элемент из этой группы и выводит его на экран.
Глава 4 "Управляющие структуры"
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 поочередно присваиваются значения, равные этим элементам.
Глава 5 "Хеши"
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 повторные просмотры одних и тех же или похожих данных дают сопоставимые результаты.)
Глава 6 "Базовые средства ввода-вывода^
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
потому что символ конца строки завершает также имя переменной, защищая следующий символ от присоединения к имени.
Глава 7 "Регулярные выражения"
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 и посмотреть, есть ли в нем после какого-нибудь символа пробел. Если есть, то одному реальному имени соответствуют несколько регистрационных имен, которые и указываются в выдаваемом в результате сообщении.
Глава 8 "Функции"
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) ;
Глава 9 "Разнообразные управляющие структуры "
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. Вот так-то!
Глава 10 "Дескрипторы файлов и проверка файлов"
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 выдает отчет.
Глава 11 "Форматы"
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
Здесь для получения заголовков страниц мы опять-таки ввели формат начала страницы. Этот формат содержит также ссылку на переменную $%, которая автоматически присваивает странице номер.
Глава 12 "Доступ к каталогам"
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, нет.
Глава 13 "Манипулирование файлами и каталогами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 выводит название символической ссылки и имя файла или директории, на которую она указывает.
Глава 14 "Управление процессами"
1. Вот один из способов решения этой задачи:
if ('date' =~ /"S/) (
print "Go play!\n";
} else (
print "Get to work!\n";
}
Оказывается, команда date выводит букву s в качестве первого символа только по выходным (Sat или sun), что делает задачу тривиальной. Мы вызываем date, затем с помощью регулярного выражения смотрим, является ли первый символ буквой s. На основании результата мы выводим одно сообщение или другое.
2. Вот один из способов решения этой задачи:
open(PW,"/etc/passwd") ;
while (<PW>) {
chomp;
($user,$gcos) = (split /:/)[0,4];
($real) = split (/,/, $gcos); '
$real($user) = $real;
)• close(PW);
open(WHO,"who I") || die "cannot open who pipe";
while (<WHO>) (
($login, $rest) =/" (\S+) \s+(.*)/;
$login = $real($login) if $real($login);
printf "%-30s ts\n",$login,$rest;
}
В первом цикле создается хеш %real, ключи которого — регистрационные имена, а значения — соответствующие реальные имена. Этот хеш используется в следующем цикле для замены регистрационного имени на реальное.
Во втором цикле осуществляется просмотр результата выполнения команды who, которая вызвана при помощи дескриптора файла. Каждая строка полученного в результате выражения разбивается путем сопоставления с регулярным выражением в списочном контексте. Первое слово строки (регистрационное имя) заменяется реальным именем из хеша, но только если оно существует. После всего этого с помощью функции printf результат помещается в stdout.
Операции открытия дескриптора файла и начала цикла можно заменить строкой
foreach $_ ('who') (
с тем же результатом. Единственное различие состоит в том, что вариант программы с использованием дескриптора файла может приступить к работе, как только who начнет выдавать символы, тогда как в варианте с функцией who в обратных кавычках придется ждать завершения выполнения функции who.
3. Вот один из способов решения этой задачи:
open(PW,"/etc/passwd");
while (<PW>) (
chomp;
($user,$gcos) = (split /:/)[0,4];
($real) = split(/,/, $gcos);
$real($user} •= $real;
} close(PW) ;
open(LPR,"1Ipr") I I die "cannot open LPR pipe";
open (WHO,"who]") || die "cannot open who pipe";
while (<WHO>) {
# или заменить предыдущие две строки на foreach $_ ('who') (
($login, $rest) = /л (\S+) \s+(.*)/;
$login = $real($loginl if $real($login} ;
printf LPR "%-30s %s\n",$login,$rest;
}
Разница между этой программой и программой из предыдущего упражнения состоит в том, что мы добавили дескриптор файла lpr, открытый для процесса Ipr, и модифицировали оператор printf так, чтобы он посылал данные не в stdout, а в этот дескриптор.
4. Вот один из способов решения этой задачи:
sub mkdir f
'system "/bin/mkdir", @_;
}
Здесь команда mkdir получает аргументы прямо из аргументов подпрограммы. Однако возвращаемое значение должно подвергнуться операции логического отрицания, потому что ненулевой код выхода из system должен быть преобразован в значение "ложь" для вызывающей Perl-программы.
5. Вот один из способов решения этой задачи:
sub mkdir (
my($dir, $mode) = 8_;
('system "/bin/mkdir", $dir) && chmod($mode, $dir) ;
1
Сначала мы описываем локальные аргументы этой подпрограммы — $dir и $ mode. Затем мы вызываем mkdir для каталога с именем $dir. В случае успеха операция chmod присваивает этому каталогу соответствующие права доступа.
Глава 15 "Другие операции преобразования данных "
1. Вот один из способов решения этой задачи:
while (о) { chomp;
$slash = rindex ($_,"/");
if ($slash > -1) (
$head = substr($_,0,$slash);
$tail = substr($_,$slash+l);
} else (
($head,$tail) = ("", $_) ;
) print "head = '$head', tail = '$tail'\n";
>
Каждая строка, прочитанная операцией "ромб", сначала пропускается через операцию chomp, которая удаляет символ новой строки. Затем с помощью rindex () мы ищем в этой строке крайнюю справа косую черту. Следующие две строки разбивают строку на части, используя substr (). Если косой черты нет, то результат rindex равен -1, и этот вариант мы не рассматриваем. Последняя строка цикла выводит результат.
2. Вот один из способов решения данной задачи:
chomp(@nums = <STDIM>); # обратите внимание на особый случай
# использования chomp @nuros = sort ( $а <=> $b } @nums;
foreach (@nums) (
printf "%30g\n", $_;
}
В первой строке в массив @ nums вводятся все числа. Во второй строке этот массив сортируется в числовом порядке, для чего используется встроенный оператор. Цикл foreach обеспечивает вывод результатов.
3. Вот один из способов решения этой задачи:
open(PW,"/etc/passwd") || die "How did you get logged in?";
while “PW” (
chomp;
($user, $gcos) = (split /:/)[0,4];
($real) = split!/,/, $gcos) ;
$real($user} = $real;
($last) = (split /\s+/, $real)[-l];
$last{$user} = "\L$last";
} close(PW) ;
for (sort by_last keys %last) (
printf "%30s %8s\n", $real($_}, $_;
>
sub by_last ( ($last($a} cmp $last($b}) || ($a cmp $b). }
В первом цикле создается хеш %last, ключи которого — регистрационные имена, а соответствующие им значения — фамилии пользователей, и хеш %геа1, содержащий полные реальные имена пользователей. Все символы переводятся в нижний регистр, чтобы, к примеру, FLINT-STONE, Flintstone и flintstone стояли рядом друг с другом.
Во втором цикле выводится %геа1, упорядоченный по значениям %iast. Это делается с использованием определения сортировки, предоставленного подпрограммой by_last.
4. Вот один из способов решения этой задачи:
while (<>) (
substr($_,0,I) =~ tr/a-z/A-Z/;
substr($_,!) — tr/A-Z/a-z/;
print;
1
Для каждой строки, прочитанной операцией "ромб", мы используем две операции tr — по одной для разных частей строки. Первая операция tr переводит первый символ строки в верхний регистр, а вторая переводит все остальные символы в нижний регистр. Результат выводится.
Вот другое решение, с использованием только строковых операций с двойными кавычками:
while (О) {
print "\u\L$_";
}
Если вы самостоятельно нашли это решение, поставьте себе дополнительно пять баллов.
Глава 16 "Доступ к системным базам данныхff
1. Вот один из способов решения этой задачи:
while (@pw == getpwent) {
($user, $gid, $gcos) - @pw(0,3,6);
($real) = split /,/, $gcos;
$real($user) = $real;
$members($gid} .= " $user";
($last) = (split /\s+/, $real)(-l);
51ast($user) " "\L$last";
)
while (@gr - getgrent) (
($gnarae, $gid, $meinbers) = @gr[ 0,2,3);
$rnembers( $gid) .=“ " $merabers";
$gname($gid) - $gname;
)
for $gid (sort by_gname keys %gname) (
tall = ();
for (split (/\s+/, $members($gidl)) ( $all{$_)++ if length $_;
1
Omembers = () ;
foreach (sort by_last keys %all) (
push(@members, "$real($_} ($_)");
}
$meinberlist = join(", ", @members);
write;
)
sub by_gname ( $gname($al cmp $gname($bl; ) sub by_last ( ($last(a) cmp $last($b)) || ($a cmp $b); )
format STDOUT = @<<<< @<<<< A<<<<<<<<<<<<<<<<<<<
$gname($gid), "($gid)", $memberlist
^<<<<<<<<<<<<<<<<<<<
$memberlist
Да, в этом не так просто разобраться, но вам это придется сделать самостоятельно, чтобы проверить усвоенные знания.
Глава 17 "Работа с пользовательскими базами данных"
1. Вот один из способов решения этой задачи:
dbmopen(%ALIAS, "/etc/aliases", undef) II
die "No aliases!: $!";
while (($key,$value) - each(tALIAS)) (
chop($key,$value) ;
print "$key $value\n";
1
Первая строка открывает DBM псевдонимов. (В вашей системе DBM псевдонимов может находиться в каталоге /usr/lib/aliases — попробуйте этот вариант, если наш не сработает.) В цикле while осуществляется проход по DBM-массиву. Первая строка цикла служит для удаления символа NUL, которым завершались ключ и значение. Последняя строка цикла обеспечивает вывод результата.
2. Вот один из способов решения этой задачи:
# program 1:
dbmopen(%WORDS,"words",0644) ;
while (О) {
foreach $word (split(/\W+/)) ( $WORDS($word)++;
> } dbmclose(%WORDS) ;
Первая программа (записывающая) открывает DBM words в текущем каталоге, создавая файлы words, dir и words.pag. Цикл while получает каждую строку, используя операцию "ромб". Эта строка разбивается с помощью операции split и разделителя /\w+/, который обозначает нетекстовые символы. Затем производится подсчет всех слов, имеющихся в DBM-массиве, причем для осуществления прохода в цикле по всем словам используется оператор foreach.
# program 2:
dbmopen(%WORDS,"words",undef) ;
foreach $word (sort { SWORDS)$b} <=> SWORDS($a[ } keys %WORDS) ( print "Sword SWORDS(Sword)\n";
1 dbmclose(%WORDS) ;
Вторая программа также открывает DBM words в текущем каталоге. Сложная на первый взгляд строка foreach делает почти всю "грязную" работу. При каждом выполнении цикла переменной $word в качестве значения будет присваиваться следующий элемент списка. Этот список состоит из ключей хеша %words, рассортированных по их значениям (т.е. количеству повторений) в убывающем порядке. Для каждого элемента списка мы выводим слово и количество его повторений.
Глава 18 "Преобразование других программ в Perl-программы "
1. Вот один из способов решения этой задачи:
for (;;) (
($user,$home) = (getpwent)[0,7];
last unless $user;
next unless open(N,"$home/.newsrc");
next unless -M N < 30; ## added value :-) while (<N>) f
if (/^comp\ . lang\ .perl\ .announce: /) { print "$user is a good person, ", "and reads comp.lang.peri.announce!\n");
last;
} } }
Самый внешний цикл — это цикл for, который выполняется "вечно";
выход из этого цикла осуществляется посредством операции last, стоящей внутри него. При каждом выполнении цикла операция getpwent выбирает новое значение переменной $user (имя пользователя) и переменной $home (его начальный каталог).
Если значение $user пусто, осуществляется выход из цикла for. Следующие две строки ищут последний файл .newsrc в начальном каталоге пользователя. Если этот файл открыть нельзя или если он изменялся слишком давно, запускается следующая итерация цикла for.
В цикле while осуществляется чтение по одной строке из файла .newsrc. Если строка начинается с comp.lang.perl.announce:, то оператор print сообщает об этом, и осуществляется преждевременный выход из цикла
while.
Глава 19 "CG1-программирование"
1. Вот один из способов решения этой задачи:
use strict;
use CGI qw (:standard);
print header(), start_html("Add Me"It-print hi("Add Me") ;
if (paramO) {
my $nl = param('fieldl');
my $n2 = param('field2');
my $n3 = $n2 + $nl;
print p("$nl + $n2 = <strong>$n3</strong>\n") ;
} else (
print hr(), start_form();
print pC'First Number:", textfield("fieldl"));
print p("Second Number:", textfield("field2"));
print p(submit("add"), reset ("clear"));
print end_form(), hr () ;
} print end_html();
Если входных данных нет, то просто создается форма с двумя текстовыми полями (при помощи метода textfield). При наличии входной информации мы объединяем эти поля и выводим результат.
2. Вот один из способов решения этой задачи:
use strict;
use CGI qw(:standard);
print header(), start_html("Browser Detective");
print hi("Browser Detective"), hr();
my $browser = $ENV('HTTP_USER_AGENT'};
$_ '" $browser;
BROWSER:(
if (/msie/i) (
msie($_) ;
) elsif (/mozilla/i) (
netscape($_) ;
) elsif (/lynx/i) (
lynx($_);
1 else (
default($_) ;
> >
print end_html() ;
sub msie)
print pC'Internet Explorer: @_. Good Choice\n") ;
}
sub netscape (
print pC'Netscape: @_. Good Choice\n") ;
t
sub lynx {
print p("Lynx: @_. Shudder...");
(
sub default (
print pC'What the heck is a @_?");
}
Главный момент здесь — проверка переменной среды HTTP_USER_ AGENT на предмет наличия одного из значений, определяющих известный тип броузера (MS Internet Explorer, Netscape Navigator, Lynx). Такая операция проводится не на всех серверах, но на большинстве из них. Это хороший способ генерировать содержимое возвращаемого документа, ориентированное на возможности конкретного броузера. Обратите внимание: чтобы посмотреть, какой именно броузер применяется пользователем, мы выполняем только тривиальное строковое сопоставление (без учета регистра).
| Назад | Вперед |
| Содержание | Предисловие | Введение | Ссылки | Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10 | Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19 | Приложение А | Приложение Б | Приложение В | Приложение Г | |
Поддержка
Perl — это детище Ларри Уолла, и он все еще продолжает с ним нянчиться. Сообщения о дефектах и требования всевозможных улучшений, как правило, учитываются в следующих редакциях, но Ларри вовсе не обязан делать это. Тем не менее ему доставляет удовольствие получать отзывы от всех нас и осознавать, что Perl полезен всему миру. На прямую электронную почту обычно даются ответы (пусть даже и его автоответчиком), причем иногда персональные. Сейчас Ларри выступает в роли архитектора группы Perl 5 Porters — плеяды очень умных людей, внесший гигантский вклад в создание нескольких последних версий языка Perl. Если бы Ларри попал под автобус, то все очень долго грустили бы, но под руководством этой группы Perl все равно продолжал бы развиваться.
Если вы нашли какой-то дефект, можете воспользоваться Perl-програм-мой perlbug, которая обеспечивает сбор соответствующей информации и отсылает ее по электронной почте на узел perlbug@perl.com. Члены группы Perl 5 Porters читают эту почту (наряду с теми 20-100 сообщениями, которые они ежедневно посылают друг другу) и иногда отвечают, если это действи тельно дефект. Но стоит вам воспользоваться этим адресом просто для того. чтобы выразить благодарность, как вам выскажут недовольство вашим поведением, поэтому сведите светские разговоры к абсолютному минимуму и воздержитесь от вызова актеров на бис.
Вместо того чтобы писать непосредственно Ларри или посылать сообще-ние о дефекте, гораздо полезнее воспользоваться услугами диалоговой служ-бы Perl-поддержки, которые предоставляются через Usenet-телеконферен-цию сотр. lang.perl.misc. Если вы имеете возможность посылать и получать электронную почту по Internet, но к Usenet доступа не имеете, можете подключиться к этой телеконференции, послав запрос по адресу perl-users-request@cs.orst.edu, этот запрос попадет к человеку, который сможет соеди-нить вас с двунаправленным шлюзом электронной почты этой телеконфе-ренции и сообщить правила работы в ней.
Подписавшись на эту телеконференцию, вы ежедневно будете обнару-живать от 50 до 200 статей на всевозможные темы — от вопросов новичков до сложных аспектов переносимости и проблем сопряжения. Иногда там будут попадаться и довольно большие программы.
Эта телеконференция почти постоянно просматривается многими спе-циалистами по языку Perl. В большинстве случаев ответ на ваш вопрос дается спустя считанные минуты после попадания вопроса на один из основных концентраторов Usenet. Вы только попробуйте получить поддержку на таком уровне, да еще и бесплатно, у своего любимого поставщика программных продуктов! Если же вы хотите заключить контракт на коммерческую под-держку, обратитесь к сборнику часто задаваемых вопросов по Perl.
Пожалуйста, пишите нам
Комментарии и вопросы по этой книге направляйте, пожалуйста, в издательство по адресу:
О ' Reilly & Associates 101 Morris Street Sebastopol, CA 95472 тел. 1-800-998-9938 (в США и Канаде) тел. 1-707-829-0515 (международный или местный) факс 1-707-829-0104
Сообщения можно посылать и в электронной форме. Чтобы попасть в список рассылки или запросить каталог, пошлите сообщение по адресу nuts@ora.com.
Сообщения с техническими вопросами и комментариями по книге направляйте по адресу Ошибка! Закладка не определена..
| Назад | Вперед |
| Содержание | Предисловие | Введение | Ссылки |
Приветствую вас, начинающие волшебники. Надеюсь,
Внимание, класс! Внимание! Спасибо.
Приветствую вас, начинающие волшебники. Надеюсь, летние каникулы принесли вам море удовольствия и, наверное, показались слишком корот-кими. Позвольте мне быть первым, кто приветствует вас в Колледже кол-довства, а точнее — на этом вводном курсе по Волшебству Perl. Я не постоянный ваш преподаватель, но профессор Шварц немного задерживается и попросил меня, как создателя языка программирования Perl, выступить сегодня здесь с несколькими вступительными замечаниями.
Итак, давайте посмотрим, с чего начать. Для кого из вас этот курс — новый? Вижу, вижу. М-да, в свое время бывало и похуже. Редко, но бывало. Очень редко.
Что вы говорите? Это была шутка. Правда! Ну хорошо, хорошо. У этих новичков нет никакого чувства юмора...
Итак, о чем я буду говорить? Конечно, есть очень много вещей, о которых я мог бы поговорить. Я мог бы заняться самолюбованием и поговорить о себе, вспоминая все те причуды генетики и воспитания, которые привели меня к созданию языка Perl и вообще поставили меня в глупое положение. Это было бы очень увлекательно (по крайней мере для меня).
Я мог бы также поговорить о профессоре Шварце, без чьих постоянных усилий мир Perl существенно обеднел бы, вплоть до того, что этот курс не существовал бы как таковой.
Это было бы интересно, но у меня такое чувство, что к концу изучения данного курса вы узнаете о профессоре Шварце больше меня.
Наконец, я мог бы, отбросив саморекламу, поговорить просто о самом языке Perl, который, в конце концов, является предметом курса. А является ли? Гм-м...
При обсуждении этого курса мы, преподаватели, пришли к выводу, что он посвящен не столько Perl, сколько вам самим! Не нужно так удивляться, потому что Perl действительно посвящен вам самим, по крайней мере в некотором абстрактном смысле. Perl был создан для таких, как вы, такими, как вы, при участии таких, как вы. Волшебство Perl была соткано многими людьми, стежок за стежком, узор за узором, по довольно специфической форме в том числе и вашей души. Если вы считаете, что Perl несколько необычен, то причина — в этом.
Некоторые ученые, работающие в области информатики (в частности, редукционисты), будут отрицать это, но человеческий ум построен очень своеобразно. Ментальный рельеф нелинеен, и его нельзя отобразить на плоскую поверхность без существенных искажений. Тем не менее в послед-ние два десятка лет компьютерные редукционисты сначала склонялись перед Храмом Ортогональности, а затем поднимали головы, чтобы проповедовать идеалы аскетизма всем, кто хотел их слушать.
Их горячим, но ошибочным желанием было загнать ваш ум в рамки своего умственного стандарта, расплющить ваш образ мыслей, превратив его в некий плоский мир. Каким безрадостным было бы такое существование!
Ваш собственный здравый смысл, тем не менее, дал о себе знать. Вы и ваши идейные предшественники вышли за рамки этого невыносимо скуч-ного пейзажа и создали множество прекрасных магических компьютерных формул. (Некоторые из них временами действительно делали то, чего от них хотели.) Самые эффективные из этих формул были канонизированы как Стандарты, потому что они смогли осуществить нечто мистическое и вол-шебное, сотворить чудо, имя которому — ^Получить То, Что Вы Хотите".
Находясь в состоянии эйфории, вы не заметили, что компьютерные редукционисты продолжали пытаться расплющить ваши умы, пусть и на несколько более высоком уровне бытия. Поэтому вышел указ (я уверен, что вы слышали о нем), согласно которому магическим компьютерным форму-лам было разрешено творить только по одному чуду. "Делать что-то одно и делать хорошо" — вот что стало главным лозунгом, и одним росчерком пера программисты, работающие с командными процессорами (более известными как shell), были обречены всю жизнь бормотать и считать бусины четок на нитках (роль которых с недавних пор стали выполнять конвейеры).
Вот тогда я и внес свой маленький вклад в дело спасения мира. Как-то раз я перебирал в руках эти самые четки и размышлял о безнадежности (и случайности) моего существования, и вдруг мне пришла в голову одна мысль: а что, если расплавить несколько этих мистических бусинок и посмотреть, как изменится волшебная сила, если сделать из них одну большую бусину? Я зажег старую бунзеновскую горелку, выбрал свои любимые бусинки и сплавил их в одну большую. И что же — ее волшебство оказалось более сильным, чем сумма волшебства ее частей!
Странно, подумал я. Почему это сплав Прилежной Бусины Регулярных Выражений с Разрушительной Бусиной Гностической Интерполяции и Стеснительной Бусиной Простой Топологии Данных должен дать больше волшебства, чем дают эти бусины в совокупности, нанизанные на нитку? Я спросил себя: может быть, эти бусины могут обмениваться силой друг с другом, потому что им больше не нужно общаться между собой через эту тонкую ниточку? Может быть, конвейер сдерживает поток информации подобно тому, как вино не хочет течь через горлышко знаменитой бутылки доктора фон Неймана?
Эти вопросы потребовали (от меня) более тщательного исследования. Я сплавил полученную бусину еще с несколькими из моих любимых бусинок — и произошло то же самое, только эффект был сильнее. Это был настоящий комбинаторный взрыв потенциальных магических формул: Бейсик-Бусина Форматов Вывода и Лисп-Бусина Динамических Контекстов слились с С-рационализированной Бусиной Изобилия Операций, что дало мощный импульс силы, который распространился на тысячи машин по всему цивилизованному миру. Рассылка посвященного этому событию сообщения обошлась Сети в сотни, если не в тысячи, долларов. Очевидно, я либо что-то уже знал, либо подошел к этому знанию вплотную.
Итак, я собрался с духом и показал свою новую волшебную бусину некоторым из вас, а вы начали посылать мне свои любимые бусины, чтобы я их сплавил со своей. Волшебная сила выросла еще больше. Все происхо-дило так, словно Вычислительные Элементарные Частицы, связанные с каждой бусинкой, взаимодействовали от вашего имени и решали проблемы за вас. Откуда этот внезапный мир на земле и добрая воля к сотрудничеству? Может быть, причина в том, что эти бусинки были вашими любимыми? А может быть, я просто хороший подборщик бус? Скорее всего, мне просто повезло.
Как бы там ни было, волшебная бусина в конце концов превратилась в тот причудливый Амулет, который вы сегодня видите перед собой. Смотрите, как он блестит — почти как жемчуг*!
Это тоже шутка. Правда, уверяю вас! Ну хорошо, я тоже когда-то был новичком... Этот Амулет на самом деле не совершенен; при близком рас-смотрении видно, что это множество сплавленных бусинок. Да-да, я признаю это. Пусть он будет крайне безобразным, но не обращайте на это внимания. Самое главное — Волшебная Сила... Посмотрите, кто пришел! Мой добрый приятель Мерлин, то есть профессор Шварц, который сейчас начнет расска-зывать вам о том, как творить чудеса с этим маленьким Амулетом, если у вас есть желание выучить необходимые магические формулы. Я уверен, вы попали в хорошие руки. Потому что никто не может бормотать магические заклинания лучше, чем профессор Шварц. Правда, Мерлин?
Подведем итоги. Больше всего вам будет необходима смелость. Путь, на который вы встали, труден. Вы учите новый язык, полный таинственных рун и древних песен — легких и трудных, знакомых и незнакомых. У вас может возникнуть искушение бросить все и уйти, но подумайте: сколько времени вам понадобилось, чтобы выучить свой родной язык? Стоило ли этим заниматься? Я думаю, да. Закончили ли вы изучение языка? Думаю, нет.
Поэтому не ждите, что вам удастся познать все тайны языка Perl за секунду — это вам не орех и не маслина. Это, скорее, банан. Чтобы насладиться вкусом банана, вы не ждете, пока он будет съеден целиком, а наслаждаетесь каждым кусочком. Каждый кусочек побуждает вас откусить еще и еще.
Поэтому, переходя к плодам труда моего друга Мерлина, я призываю вас насладиться этим курсом. Курсом как вкусным плодом, конечно. Ну-ну, это тоже шутка...
Итак, профессор, я представляю вам ваш новый класс. Кажется, у этих учеников полностью отсутствует чувство юмора, но я надеюсь, вы как-нибудь справитесь.
Класс, я представляю вам профессора Рэндала Л. Шварца, доктора синтаксиса, чародея регулярных выражений и, конечно, еще одного Perl-ха-кера. Благословляю его, как и вас всех. Желаю вам выучить Perl. Желаю вам творить с его помощью доброе волшебство. Желаю, наконец, получить от Perl массу удовольствия. Да будет так!
Да сделаете вы так!
Ларри Уолл Сентябрь 1993 г.
* Игра слов: в английском языке Perl созвучно с pearl (жемчуг).— Прим. перев.
| Назад | Вперед |
| Содержание | Предисловие | Введение | Ссылки | Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10 | Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19 | Приложение А | Приложение Б | Приложение В | Приложение Г | |
Преобразование других программ в Perl-программы
Преобразование awk-программ в Perl-программы
Одна из самых замечательных особенностей Perl состоит в том, что он представляет собой семантическое надмножество (как минимум) языка awk. С практической точки зрения зто значит, что если вы можете сделать что-либо в awk, вы сможете сделать зто и в Perl. При зтом, однако, Perl не обладает синтаксической совместимостью с awk. Например, переменная NR (номер входной записи) awk представляется в Perl как $.
Если у вас єсть а^-программа и вы хотите выполнить ее Perl-вариант, можно осуществить механическое Преобразование зтой программы с помо-щью утилиты а2р, которая єсть в дистрибутиве Perl. Зта утилита конвертирует синтаксис awk в синтаксис Perl и может создать непосредственно выполняе-мый Perl-сценарий для подавляющего большинства ои^-программ.
Чтобы воспользоваться утилитой а2р, поместите свою aw/^-программу в отдельный файл и вызовите а2р с именем зтого файла в качестве аргумента или переадресуйте стандартный ввод а2р на ввод из зтого файла. В результате на стандартном виводе а2р вы получите нормальную Perl-программу. Например:
$ cat myawkprog
Bb.SIN { sum = 0 )
/llama/ ( sum += $2 )
END { print "The llama count is " sum }
$ a2p <myawkprog >myperlprog $ perl myperlprog somefile The llama count is 15 $
Можно также направить стандартний вывод a2p прямо в Perl, потому что интерпретатор Perl принимает программу со стандартного ввода, если полу-чает такое указание:
$ a2p <myawkprog I perl - somefile
The llama count is 15
$
Преобразованный для использования в Perl aw^-сценарий, как правило, выполняет идентичную функцию, часто с большей скоростью й, конечно, без каких-либо присущих awk ограничений на длину строки, количество параметров и т.д. Некоторые преобразованные Perl-программы могут выпол-няться медленнее; Perl-действие, зквивалентное данной awA-операции, не обязательно будет самым зффективным Perl-кодом, по сравнению с напи-санным вручную.
Вы, возможно, захотите оптимизировать Преобразованный Perl-код или добавить в Perl-версию программы новьге функциональные возможности. Зто сделать довольно просто, поскольку полученный Perl-код читается достаточно легко (учитывая, что перевод выполняется автоматически, сле-дует отметить зто как большое достижение).
В некоторых случаях перевод не выполняется механически. Например, сравнение "меньше чем" и для чисел, и для строк в awk выражается операцией <. В Perl для строк используется it, а для чисел — операция <. В большинстве случаев awk, как и утилита a2p, делает разумное предполо-жение относительно числового или строкового характера двух сравниваемых значений. Однако вполне возможно, что о каких-нибудь двух значеннях будет известно недостаточно много для того, чтобы определить, какое должно выполняться сравнение — числовое или строковое, позтому a2p использует наиболее вероятную операцию и помечает возможно ошибочную строку знаками #?? (Perl-комментарием) и пояснением. После преобразо-вания обязательно просмотрите результат на предмет наличия таких коммен-тариев и проверьте сделанные предположения. Более подробно о работе утилиты a2p рассказывается на ее man-странице. Если зтой утилиты в том каталоге, откуда вы вызываете Perl, нет, громко пожалуйтесь тому, кто инсталлировал вам Perl.
Преобразование sed-программ в Perl-программы
Может быть, вам покажется, что мы повторяємся, но угадайте, что мы сейчас скажем? А вот что: Perl — семантическое надмножество не только awk, но и sed.
С дистрибутивом поставляется конвертор sed-РетІ, который называется s2p. Как и а2р, s2p получает со стандартного ввода ,уе</-сценарий и направляет на стандартний вывод Perl-программу. В отличие от результата работы а2р, преобразованная программа редко ведет себя не так, как нужно, позтому вы вполне можете рассчитывать на ее нормальную работу (при отсутствии дефектов в самой s2p или Perl).
Конвертированные 5Єй/-программы могут работать быстрее или медлен-нее оригинала. Как правило, они работают значительно быстрее (благодаря хорошо оптимизированным Perl-программам обработки регулярних виражений).
Конвертированный waf-сценарий может работать с опцией -п или без нее. Опция -п имеет тот же смысл, что и соответствующий ключ для sed. Чтобы воспользоваться зтой опцией, конвертированный сценарий должен направить сам себя в препроцессор С, а зто несколько замедляет запуск. Если вы знаєте, что всегда будете вызывать конвертированный wdf-сценарий с опцией -п или без нее (например, при преобразовании wfif-сценария, используемого в больших shell-программах с известными аргументами), вы можете информировать утилиту s2p об зтом (посредством ключей -п и -р), и она оптимизирует сценарий под выбранный вами ключ.
В качестве примера, демонстрирующего високую универсальность и мощь языка Perl, отметим тот факт, что транслятор s2p написан на Perl. Если вы хотите увидеть, как Ларри программирует на Perl, взгляните на зтот транслятор — только сначала сядьте, чтобы не упасть (даже с учетом того, что зто очень древний код, относительно не изменившийся с версии 2).
Преобразование shell-сценариев в Perl-программы
Вы, наверное, подумали, что речь пойдет о конверторе, осуществляющем Преобразование shell-сценариев в Perl-программы?
А вот и нет. Многие спрашивают о такой программе, но проблема в том, что большинство из того, что делает сценарий, делается отнюдь не им самим. Большинство сценариев shell тратят практически все своє время на вызовы отдельных программ для извлечения фрагментов строк, сравнения чисел, конкатенации файлов, удаления каталогов и т.д. Преобразование такого сценария в Perl требовало бы понимания принципа работы каждой из вызываемых утилит или перекладывания вызова каждой из них на плечи Perl, что абсолютно ничего не дает.
Позтому лучшее, что вы можете сделать, ~ зто посмотреть на сценарий shell, определить, что он делает, и начать все с нуля, но уже на Perl. Конечно, вы можете провести на скорую руку транслитерацию — поместив основные части исходного сценария в вызовы system () или заключив их в обратные кавычки. Возможно, вам удастся заменить некоторые операции командами Perl: например, заменить system(rm /red) на unlink (fred) или цикл for shell на такой же цикл Perl. В большинстве случаев, однако, вы увидите, что зто несколько напоминает конвертирование программы, написанной на Коболе, в С (почти с тем же уменьшением количества символов й, как следствие, повышением степени неразборчивости текста программы).
Упражнение
Ответ см. в приложении А.
1. Преобразуйте следующий сценарий shell в Perl-программу:
cat /etc/passwd I
awk -F: '(print $1, $6(' |
while read user home
do
newsrc="$home/.nevsrc" if [ -r $newsrc ] then
if grep -s "'сотр\.lang\.perl\.announce: ' $newsrc then
echo -n "$user is a good person, ";
echo "and reads comp.lang.perl.announce!" fi fi done
| Назад | Вперед |
| Содержание | Предисловие | Введение | Ссылки | Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10 | Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19 | Приложение А | Приложение Б | Приложение В | Приложение Г | |
Приложение А
В этой главе:
Глава 2
Глава 3
Глава 4
Глава 5
Глава 6
Глава 7
Глава 8
Глава 9
Глава 10
Глава 11
Глава 12
Глава 13
Глава 14
Глава 15
Глава 16
Глава 17
Глава 18
Глава 19
Работа с пользовательскими базами данных
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 символов с произвольными двоичными значеннями, то все будет нормально.
Открытие и закрытие DBM-хешей
Чтобы связать DBM-базу данных с DBM-массивом, применяется функ-ция dbmopen, которая используется следующим образом:
dbmopen(%ИМЯ МАССИВА, "имя_ОВМ-фа{та", $режим}
Параметр %имя_массива — зто имя Perl-хеша. (Если в данном хеше уже єсть значення, они выбрасываются.) Хеш соединяется с DBM-базой данных, заданной параметром имя_овм-файла. Она обычно хранится на диске в виде пары файлов с именами имя_ОВМ-файла.сіи и имя_ОВМ-файла.рад.
Параметр $режим — зто число, которое соответствует битам прав доступа к названным двум файлам, если файлы создаются заново. Обычно оно указывается в восьмеричном формате; часто используемое значение 0644 предоставляет право доступа только для чтения всем, кроме владельца, который имеет право на чтение и запись. Если зти файлы существуют, данный параметр не действует. Например:
dbmopen(%FRED, "mydatabase", 0644); # открьггь %FRED на mydatabase
Зтот вызов связывает хеш %fred с файлами mydatabase. dir и ту database.pag, расположенными в текущем каталоге. Если зти файлы не существуют, они создаются с правами доступа 0644, которые модифицируются с учетом текущего значення, установленного командой umask.
Функция dbmopen возвращает значение "истина", если базу данных можно открыть или создать; в противном случае возвращается "ложь" — точно так же, как при вызове функции open. Если вы не хотите создавать файлы, используйте вместо параметра $режим значение undef. Например:
dbmopen(%A,"/etc/xx",undef) || die "cannot open DBM /etc/xx";
* Зто, по суги дела, просто особый случай использования общего механизма tie. Если вам понадобится что-нибудь более гибкое, обратитесь к man-страницам AnyDBM_File(3), DB_File(3) н perltie(l).
Если, как в данном случае, (^awibi/etc/xx.dirvi/etc/xx.pagoTKpbnb нельзя, то вызов dbmopen возвращает значение "ложь" без попытки создать зти файлы.
DBM-массив остается открытым в течение выполнения всей программы. Когда программа завершается, разрывается и связь с DBM-базой данных. Зту связь можно разорвать и способом, близким к закрытию дескриптора файла — с помощью функции dbmclose:
dbmclose(%A);
Как и функция close, dbmclose возвращает значение "ложь", если что-нибудь происходит не так, как надо.
Использование DBM-хеша
После открытия базы данных обращения к DBM-хешу преобразуются в обращения к базе данных. Изменение значення в хеше или ввод в него нового значення вызывает немедленную запись соответствующих злементов в файлы на диске. Например, после открытия массива %fred из предыдущего примера мы можем обращаться к злементам базы данных, вводить в нее новые злементы и удалять существующие:
$FRED{"fred"} = "bedrock"; # создать (или обновить) злемент delete $FRED("barney"}; # удалить злемент базн данных foreach $key (keys %FRED) ( # пройти по всем значенням print "$key has value of $FRED{$key)\n";
}
Последний цикл должен просмотреть весь файл на диске дважды: один раз для выборки ключей, а второй — для поиска значений, соответствующих зтим ключам. Если вы просматриваете DBM-хеш, то более зффективным способом с точки зрения зксплуатации диска является использование опе-рации each, которая делает всего один проход:
while (($key, $value) = each(%FRED) ) ( print "$key has value of $value\n";
}
Если вы обращаетесь к системним DBM-базам данных, например к базам данных, созданным системами sendmail и NIS, вы должны иметь в виду, что в плохо написанных С-программах в конце строк иногда стоит символ NUL (\0). Программам библиотеки DBM зтот NUL не нужен (они обрабатывают двоичные данные с помощью счетчика байтов, а не строки с символом NUL на конце), позтому он хранится как часть данных. В таком случае вы должны добавлять символ NUL в конец своих ключей и отбрасывать NUL, стоящий в конце возвращаемых значений, иначе данные не будут иметь смысла.
Например, чтобы найти имя merlyn в базе данных псевдонимов, можно сделать так:
dbmopen(%ALI, "/etc/aliases", undef) I I die "no aliases?";
$value °= $ALI {"merlyn\0" 1; # обратите внимание на добавленный NUL
chop ($value) ; # удалить добавленный NUL
print "Randal' s mail is headed for: $value\n"; # показать результат
В вашей версии UNIX база данных псевдонимов может храниться не в каталоге /etc, а в каталоге /usr/lib. Чтобы вияснить, где именно она хранится, придется провести маленькое расследование. Новые версии sendmail зтим NUL-дефектом не страдают. ''
Базы данных произвольного доступа с записями фиксированной длины
Еще одна форма хранения данных — файл на диске, предназначенный для записей фиксированной длины. В зтой схеме данные состоят из ряда записей одинаковой длины. Нумерация зтих записей либо не имеет значення, либо определяется по какой-нибудь схеме индексации.
Например, у нас может быть ряд записей со следующими данными:
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.
Упражнения
Ответы см. в приложении А.
1. Создайте программу, которая открывает базу данных псевдонимов send-mail и выводит на зкран все ее злементы.
2. Создайте две программы: одну для чтения данных с помощью опе-рации о, разбивки их на слова и обновлення DBM-файла с запомина-нием числа зкземпляров каждого слова, а вторую для открытия зтого DBM-файла и отображения результатов, рассортированных по количест-ву зкземпляров каждого слова в убывающем порядке. Вьшолните первую программу по отношению к нескольким файлам и посмотрите, осущест-вляет ли вторая программа правильную сортировку.
| Назад | Вперед |
| Содержание | Предисловие | Введение | Ссылки | Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10 | Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19 | Приложение А | Приложение Б | Приложение В | Приложение Г | |
Readme
Электронная библиотека "Internet Zone"
http://www.izone.com.ua/
*************************************
Авторские права на все материалы,
содержащиеся в библиотеке "Internet Zone"
принадлежат конкретным авторам
соответствующих опубликованных материалов.
-Электронная библиотека Internet Zone
является бесплатной и свободной для доступа.
-При размещении (копировании) где-либо
материалов библиотеки "Internet Zone",
или материалов журнала "Internet Zone", или
любых других материалов с нашего ресурса -
ОБЯЗАТЕЛЬНО нужно указывать
источник получения информации с явным
указанием адреса сайта: http://www.izone.com.ua/
Пример такой ссылки: допустим вы сделали себе
вэб-страничку в сети и решили разместить у себя
кое-что из наших материалов.
Нет проблем! Размещаете и ставите под (или над)
размещаемой статьей примерно такой текст:
Использованы материалы "Internet Zone"
http://www.izone.com.ua/
*******************************************
Посетите наш сайт http://www.izone.com.ua/
На нашем сайте представлены
программы,вэб-утилиты;
ресурсы для зарабатывания денег;
ресурсы, бесплатно предоставляющие
дисковое пространство, почтовые ящики,
короткие запоминающиеся адреса для вашего сайта
и многие другие сервисы.
У нас вы найдете различную техническую документацию,
номера журналов Internet Zone и т.д.
------------------------------------------
Copyright © 2000-2005 гг. "Internet Zone".
nikspase@hotmail.ru
nikspase@mail.ru
http://www.izone.com.ua/
Регулярные выражения
Основные понятия
Регулярное выражение представляет собой образец — шаблон — который сопоставляется со строкой. Сопоставление регулярного выражения со строкой дает либо успешный результат, либо неудачный. Иногда получение того или иного результата может быть единственной целью использования регулярного выражения, а иногда ставится задача замены совпавшего образца другой строкой.
Регулярные выражения используются многими программами, в частности, UNIX-командами, программами grep, sed, awk, ed, vi, emacs и даже различными shell. В каждой программе используется свой набор метасимволов (большей частью они совпадают). Perl — семантическое надмножество всех этих средств: любое регулярное выражение, которое можно записать в одной из подобных программ, может быть записано и на языке Perl, но не обязательно теми же символами.
Основные направления использования регулярных выражений
Если бы нам нужно было найти в каком-то файле все строки, содержащие строку abc, мы могли бы использовать команду grep:
grep abc somefile >results
В этом случае abc — регулярное выражение, которое команда grep сверяет с каждой входной строкой. Строки, соответствующие этому регулярному выражению, посылаются на стандартный вывод и попадают в файл results (так как в командной строке стоит оператор переадресации).
В Perl мы можем превратить строку abc в регулярное выражение, заключив ее между косыми:
if (/abc/) ( print $_;
}
Но что же сверяется с регулярным выражением abc в данном случае? Да наша старая подруга, переменная $_! Если регулярное выражение заключено между косыми (как в этом примере), то переменная $_ сверяется с регулярным выражением. Если значение переменной совпадает с регулярным выражением, операция сопоставления возвращает значение "истина". В противном случае она возвращает "ложь".
В данном примере предполагается, что переменная $_ содержит какую-то строку текста и выводится, если в любом месте этой строки обнаруживается последовательность символов abc (аналогичные действия производит приведенная выше команда grep. Однако в отличие от grep, которая оперирует всеми строками файла, данный фрагмент Perl-программы просматривает только одну строку). Чтобы обрабатывались все строки, добавьте операцию цикла:
while (о) (
if (/abc/) { print $_;
> )
А что, если мы не знаем, сколько символов b стоит между а и с? То есть что нужно делать, если мы хотим вывести на экран строку только в том случае, если она содержит символ а, за которым следует ни одного или более символов b и символ с? Работая с grep, мы написали бы так:
grep "ab*c" somefile >results
(Аргумент, содержащий звездочку, заключен в кавычки, потому что мы не хотим, чтобы shell обработал его так, как будто это метасимвол, встретившийся в имени файла. Чтобы звездочка сработала, ее нужно передать в grep как есть.) В Perl мы можем сделать то же самое:
while (о) {
if (/ab*c/) ( print $_;
) >
Как и в grep, такая запись обозначает последовательность, содержащую символ а, ни одного или более символов b и символ с.
Другие варианты сопоставления с образцом мы рассмотрим в разделе "Еще об операции сопоставления" после того, как поговорим обо всех видах регулярных выражений.
Еще одна простая операция, в которой используются регулярные выражения, — операция замены, посредством которой часть строки, соответствующая регулярному выражению, заменяется другой строкой. Операция замены похожа на команду s UNIX-утилиты sed: она состоит из буквы s, косой черты, регулярного выражения, еще одной косой, заменяющей строки и третьей косой черты:
s/ab*c/def/;
Переменная (в данном случае $_) сопоставляется с регулярным выражением (ab*c). Если сопоставление оказалось успешным, то соответствующая часть строки отбрасывается и заменяется строкой (def). Если сопоставление неудачно, ничего не происходит.
Позже, в разделе "Операция замены", мы рассмотрим множество опций операции замены.
Образцы
Регулярное выражение — это образец. Одни части образца обозначают отдельные символы. Другие части соответствуют группам символов. Сначала мы рассмотрим образцы, соответствующие одному символу, а затем образцы, при помощи которых в регулярном выражении обозначается группа символов.
Образцы, обозначающие один символ
Самый простой и самый распространенный символ, встречающийся в регулярных выражениях, — это одиночный символ, соответствующий самому себе. Другими словами, наличие буквы а в регулярном выражении требует наличия соответствующей буквы а в строке.
Следующий из самых известных символов сопоставления — точка ("."). Точка обозначает любой одиночный символ, кроме символа новой строки (\п). Например, образцу /а. / соответствует любая двухбуквенная последовательность, которая начинается с буквы а и не является последовательностью "а\п".
Класс символов
сопоставления задается списком символов, заключенных в квадратные скобки. Чтобы строка считалась совпавшей с образцом, в соответствующей ее части должен присутствовать один и только один из этих символов. Например, образцу
/[abode]
соответствует строка, содержащая любую из первых пяти строчных букв алфавита, тогда как образцу
/[aeiouAEIQU]
соответствует любая из первых пяти гласных, причем как строчных, так и прописных. Если вы хотите вставить в список правую квадратную скобку (]), поставьте перед ней обратную косую или же поставьте эту скобку на первое место в списке. Диапазоны символов (например, от а до z) можно приводить в сокращенной записи, указав конечные точки диапазона через дефис (-). Чтобы включить в список дефис как таковой, поставьте перед ним обратную косую или поместите его в конец. Вот еще несколько примеров:
[0123456789] # обозначает любую цифру
[0-9] # то же самое
[0-9\-] # обозначает цифры 0-9 или знак минус
[a-z0-9] # обозначает любую строчную букву или цифру
[a-zA-ZO-9_] # обозначает любую букву, цифру или знак подчеркивания
Существует также такое понятие, как отрицание класса символов: оно обозначается знаком л, который ставится сразу же за левой скобкой. Такому классу символов соответствует любой символ, отсутствующий в этом списке. Например:
["0-9]
# обозначает любой нецифровой символ
["aeiouAElOU] # обозначает любую негласную букву
["\"]
# обозначает любой символ, кроме символа "
Для удобства пользователя некоторые распространенные классы символов определены заранее. Они представлены в таблице 7.1.
Таблица 7.1. Предопределенные классы символов
Конструкция | Эквивалентный класс | Конструкция с отрицанием | Эквивалентный класс с отрицанием |
\d (цифра) \w (обычный символ) \s (пробельный символ) |
[0-9] [a-zA-ZO-9] [ \r\t\n\f] |
\d (нецифровые символы) \w (специальные символы) \s (непробельный символ) |
^0-9] [^a-zA-ZO-9] [" \r\t\n\f] |
Приведенные выше конструкции можно использовать при задании других классов символов:
[\da-fA-F] # соответствует одной шестнадцатеричной цифре
Образцы, обозначающие группу символов
Свою истинную силу регулярные выражения показывают, когда вам нужно сказать, например, "один и более из этих символов" или "до пяти из этих символов". Давайте посмотрим, как это делается.
Последовательность
Первый (и, вероятно, самый неочевидный) образец данного вида — последовательность. Например, образец abc соответствует букве а, за которой следует буква Ь, за которой идет буква с. Вроде бы просто, но название этому виду образца все равно нужно дать, чтобы в дальнейшем знать, о чем идет речь.
Множители
Мы уже встречались со звездочкой (*) в роли образца, обозначающего группу символов. Звездочка обозначает ни одного или более экземпляров стоящего непосредственно перед ней символа (или класса символов).
Есть еще два образца, работающих подобным образом: знак "плюс" (+), который обозначает один или более экземпляров стоящего непосредственно перед ним символа, и вопросительный знак (?), который обозначает ни одного или один экземпляр стоящего непосредственно перед ним символа. Например, регулярное выражение /fo+ba?r/ обозначает символ f, за которым следует один или более символов о, затем символ Ь, затем ни одного или один символ а и, наконец, символ г.
Однако все описанные выше образцы (множители) характеризуются "прожорливостью". Например, если множителю может соответствовать 5-10 символов, то каждый раз он будет выбирать десятисимвольную строку. Например,
$_ = "fred xxxxxxxxxx barney";
s/x+/boom/;
всегда заменяет словом boom все символы х (что в результате дает fred boom barney), а не только один или два, несмотря на то, что более короткий набор иксов соответствовал бы этому же регулярному выражению.
Если нужно сказать "от пяти до десяти" символов х, можно поставить пять иксов, а затем еще пять, дав после каждого из последних пяти вопросительный знак. Это, однако, выглядит уродливо. Есть более простой способ — применение общего множителя. Общий множитель состоит из пары фигурных скобок, между которыми заключены одно-два числа, например /х{5,10}. Необходимо найти символ, стоящий непосредственно перед скобками (в данном случае это буква х), повторяющийся указанное число раз (в рассматриваемом случае — от пяти до десяти)*.
Если второе число не указано (например, /х {5, } /), это означает "столько или больше" (в данном случае пять и более), а если выпущена и запятая (например, /х{5}/), это означает "ровно столько" (в данном случае пять символов х). Чтобы получить пять или менее символов х, нужно перед запятой поставить нуль: /х {0, 5} /.
Так, регулярное выражение /а. {5} b/ соответствует букве а, отделенной от буквы b любыми пятью символами, кроме символов новой строки, и все это может быть в любом месте строки. (Вспомните, что точка соответствует любому символу, кроме символа новой строки, а нам здесь нужно пять таких символов.) Эти пять символов не обязательно должны быть одинаковыми. (В следующем разделе мы увидим, как заставить их быть одинаковыми.)
Можно было бы вполне обойтись без *, + и ?, потому что эти образцы полностью эквивалентны образцам {0,},(!,} и {0,1}, но проще ввести один эквивалентный знак препинания, к тому же это более привычно.
Если в одном выражении используются два множителя, то "правило прожорливости" дополняется правилом "чем левее, тем прожорливее". Например:
$_ = "а ххх с хххххххх с ххх d";
/a.*c.*d/;
В этом случае первая комбинация ".*" в регулярном выражении соответствует всем символам до второй буквы с, несмотря на то, что положительный результат был бы достигнут даже при совпадении только символов, стоящих до первой буквы с. Сейчас это никакой роли не играет, но позднее, когда нам потребуется анализировать части, совпавшие с регулярным выражением, это будет очень важно.
Можно заставить любой множитель перестать быть "прожорливым" (т.е. сделать его ленивым), поставив после него вопросительный знак:
$_ = "а ххх с хххххххх с ххх d";
/a.*?c.*d/;
Здесь а. * ? с теперь соответствует минимальному числу символов между а и с, а не максимальному. Это значит, что с образцом совпадает часть
* Конечно, /\d(3}/ соответствует не только трехзначным числам, но и любому числу с количеством знаков больше трех. Чтобы задать именно трехзначное число, нужно использовать фиксирующие точки, которые рассматриваются ниже в разделе "Фиксирующие образцы".
строки до первой буквы с, а не до второй. Такой модификатор можно ставить после любого множителя (?,+,* и {m,n}).
Что, если строка и регулярное выражение несколько изменятся, скажем, так:
$_ ° "а ххх се хххххххх ci xxx d";
/a.*ce.*d/;
Символы .* в этом случае соответствуют максимально возможному числу символов, стоящих до следующей буквы с, но очередной символ регулярного выражения (е) не совпадает с очередным символом строки (i). В этом случае мы получаем автоматический поиск с возвратом: поиск начинается сначала и завершается остановкой в некоторой позиции до выбранной на первом этапе (в нашем случае — в позиции предыдущей с, рядом с е)*. Сложное регулярное выражение может включать множество уровней поиска с возвратом, в результате чего время выполнения значительно увеличивается. В данном случае превращение множителя в "ленивый" (с помощью вопросительного знака) упрощает задачу, которую должен выполнить Perl, поэтому рекомендуем хорошо изучить этот метод.
Круглые скобки как способ запоминания
Следующая групповая операция — пара круглых скобок, в которую заключается часть образца. При совпадении с образцом никаких изменений не происходит, просто совпавшая часть строки запоминается, и к ней можно впоследствии обращаться. Например, (а) продолжает соответствовать букве а, а ([a-z] ) — любой строчной букве.
Чтобы вызвать часть строки, которую программа запомнила, нужно поставить обратную косую и целое число. Образец такой конструкции обозначает последовательность символов, обозначенную ранее в паре круглых скобок под тем же номером (считая с единицы). Например,
/fred(.)barney\l/;
соответствует строке, состоящей из слова fred, любого символа, кроме символа новой строки, слова barney и еще одного такого же символа. Таким образом, данному образцу соответствует последовательность символов fredxbarneyx, a не fredxbarneyy. Сравните это с
/fred.barney./;
где два обозначенных точками символа могут быть одинаковыми или разными; роли это не играет.
Откуда взялась единица? Она обозначает первую заключенную в круглые скобки часть регулярного выражения. Если таких частей больше, чем одна,
* На самом деле для поиска буквы с в первой позиции понадобится больший объем поиска с возвратом в операции *, но описание этого процесса не представляет интереса, а работает он по такому же принципу.
то вторая часть (считая левые круглые скобки слева направо) обозначается как \2, третья — как \3 и т. д. Например,
/a(.)b(.)c\2d\l/;
обозначает а, какой-то символ (назовем его #1), b, еще один символ (назовем его #2), с, символ #2, d и символ #1. Таким образом, этот образец соответствует, в частности, строке axbycydx.
Запоминаемая часть может состоять не только из одного символа. Например,
/а(.*)Ь\1с/;
обозначает а, любое количество символов (даже нуль), b, ту же последовательность символов и, наконец, с. Следовательно, этот образец совпадет со строкой aFREDbFREDc и даже со строкой abc, но не со строкой аХХЬХХХс.
Дизъюнкция
Следующая групповая конструкция — дизъюнкция, т.е. а | b | с. Это значит, что данный образец соответствует только одному из указанных вариантов (в данном случае — а, b или с). Такая конструкция работает даже в том случае, если варианты содержат несколько символов, как в образце /song | blue/, что соответствует либо song, либо blue. (Для односимвольных альтернатив определенно лучше будет использовать класс символов, например, / [ abc ] /.)
Что, если бы мы хотели найти songbird или bluebird? Мы могли бы написать /songbird | bluebird/, но часть bird не хотелось бы указывать дважды. Из такой ситуации есть выход, однако вначале нам следует поговорить о приоритете группирующих образцов, который рассматривается ниже, в разделе "Приоритет".
Фиксирование образцов
Некоторые особые виды записи позволяют фиксировать образец относительно позиции в строке, в которой ищется соответствие. Обычно при сопоставлении образец "перемещается" по строке слева направо; сообщение о совпадении дается при первой же возможности. Фиксирующие точки позволяют гарантировать, что с образцом совпадают определенные части сравниваемой строки.
Первая пара фиксирующих директив требует, чтобы определенная часть символов, соответствующих образцу, была расположена либо на границе слова, либо не на границе слова. Фиксирующая директива \Ь требует, чтобы совпадение с образцом b происходило только на границе слова. Граница слова — это место между символами, которые соответствуют предопределенным классам \w или \w, либо между символами, которые соответствуют классу \w, а также начало или окончание строки. Отметим, что все это больше предназначено для работы с С, а не с английскими словами, но вполне применимо и к словам. Например:
/fred\b/; # соответствует слову fred, но не Frederick /\bmo/; # соответствует словам тое и mole, но не Eimo /\bFred\b/; # соответствует слову Fred, но не Frederick или alFred /\b\+\b/; # соответствует "х+у", но не "++" или " + " /abc/bdef/; # никогда не дает совпадения(границы там быть не может)
Аналогичным образом \в требует, чтобы в указанной точке границы слова не было. Например:
/\bFred\B/; # соответствует "Frederick", но не "Fred Flintstone"
Две другие фиксирующие точки требуют, чтобы определенная часть образца стояла рядом с концом строки. Символ л обозначает начало строки, если стоит в месте, где сопоставление с началом строки имеет смысл. Например, "а соответствует символу а в том и только в том случае, если а — первый символ в строке, aл соответствует двум символам, а и л, стоящим в любом месте строки. Другими словами, символ л утратил свое специальное значение. Если вы хотите, чтобы он имел буквальный смысл и в начале строки, поставьте перед ним обратную косую черту.
Символ $, как и л, фиксирует образец, но не по началу, а по концу строки. Другими словами, с$ соответствует символу с только в том случае, если он стоит в конце строки*. Знак доллара в любом другом месте образца, вероятно, будет интерпретироваться как представление скалярного значения, поэтому для того, чтобы использовать его в строке буквально, перед ним следует поставить обратную косую.
Поддерживаются и другие фиксирующие точки, включая \А, \2 и упреждающие фиксирующие точки, создаваемые с помощью комбинаций (?=...) и (?!...). Они подробно описаны в главе 2 книги Programming Perl и на man-странице perlre(Y).
Приоритет
Что произойдет, если объединить а | Ь*? Что будет отыскиваться — любое количество символов а или Ь или один символ а и любое количество Ь?
Групповые и фиксированные образцы, как и операции, имеют приоритет. Приоритет образцов (от высшего к низшему) приведен в таблице 7.2.
Таблица 7.2. Приоритет групповых регулярных выражений**
Наименование | Обозначение |
Круглые скобки Множители Последовательность и фиксация Дизъюнкция | ( ) (?: ) ? + * {m,n} ?? +? *? (m,n}? abc л $ \А \Z (?= ) (?! ) |
** Некоторые из этих символов в нашей книге не описываются. См. книгу Programming Perl или man-страницу perlreii(l).
Согласно этой таблице, специальный символ * имеет более высокий приоритет, чем |. В силу этого /а |Ь*/ интерпретируется как один символ а или любое число символов ь.
Что, если нам понадобится другое — например, "любое число символов а или Ь"? В этом случае нужно просто использовать пару круглых скобок. В нашем примере в скобки нужно заключить ту часть выражения, к которой должна относиться *, т.е. (а|Ь)*. Если вы хотите подчеркнуть, какое выражение вычисляется первым, можно дать избыточные круглые скобки:
а (Ь*).
Изменение приоритета с помощью круглых скобок одновременно активизирует режим запоминания для данного образца, как мы рассказывали выше. То есть эти круглые скобки учитываются, когда вы определяете, соответствует ли какой-то элемент \2, \3 и т.д. Если вы хотите использовать круглые скобки без включения режима запоминания, применяйте форму (?:...), а не (...). Она тоже позволяет указывать множители, но не изменяет значение счетчика подлежащих запоминанию лексем, используя, например, переменную $4 и т.п. Например,/(?: Fred |Wilma) Flintstone/ ничего не записывает в переменную $ 1; здесь просто предполагается группирование.
Вот еще несколько примеров регулярных выражений и действия круглых скобок:
abc* # соответствует ab, abc, abcc, abccc, abcccc, и т.д. (abc)* # соответствует "", ab, abc, abcabc, abcabcabc, и т.д. ^х |у # соответствует х в начале строки или у в любом месте л^x.^y) # соответствует х или у в начале строки а|be Id # либо а, либо be, либо d (alb)(с Id) # ас, ad, be или bd (song|blue)bird # songbird или bluebird
Еще об операции сопоставления
Мы уже рассматривали простейшие варианты использования операции сопоставления (регулярного выражения, заключенного между косыми). Теперь давайте изучим способы, которыми можно заставить эту операцию делать нечто иное.
Выбор другого объекта для сопоставления (операция :='•)
Обычно строка, которую нужно сопоставить с образцом, не находится в переменной $_, и помещать ее туда довольно утомительно. (Может быть, в переменной $__ уже хранится значение, которое вам не хочется терять.) Ничего страшного — здесь нам поможет операция =~. С ее помощью вы можете назначить для проведения операции сопоставления строку, хранящуюся в переменной, отличной от $_.
Эта переменная указывается справа от знака операции. Выглядит это так:
$а = "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"
Поскольку значения этим переменным присваиваются при каждом успешном сопоставлении, их нужно где-нибудь сохранить, если они вам впоследствии понадобятся*.
Операция замены
Мы уже говорили о простейшей форме операции замены: s/ регуляр-ное_выражение/новая_строка/. Пора рассмотреть несколько разновидностей этой операции.
Если вы хотите, чтобы замена выполнялась при всех возможных совпадениях, а не только при первом, добавьте в запись, задающую проведение операции замены, букву д, например:
$_ = "foot fool buffoon";
s/foo/bar/g; # $_ теперь содержит "bart barl bufbarn"
В заменяющей строке производится интерполяция переменных, что позволяет задавать эту строку во время выполнения:
$_ = "hello, world";
$new = "goodbye";
s/hello/$new/; # заменяет hello на goodbye
Символы сопоставления (метасимволы) в регулярном выражении позволяют выполнять сопоставление с образцом, а не просто с символами, трактуемыми буквально:
$_ = "this is a test";
s/(\w+()/<$l>/g; # $_ теперь содержит "<this> <is> <a> <test>"
Вспомните, что в $1 заносятся данные, полученные при совпадении с первой заключенной в круглые скобки частью образца.
Суффикс i (перед буквой g или после нее, если она есть) заставляет используемое в операции замены регулярное выражение игнорировать регистр, как и аналогичная опция в ранее рассмотренной нами операции сопоставления.
* О влиянии этих переменных на производительность рассказывается в книге Mastering Regular Expressions (издательство O'Reilly).
г ; Как и в операции сопоставления, можно выбрать другой разделитель, если косая черта неудобна. Для этого просто нужно использовать один символ три раза*:
s#fred#barney#; # заменить fred на barney, как в s/fred/barney/
Как и при сопоставлении, можно с помощью операции =~ указать другой объект для проведения замены. В этом случае объект должен быть таким, которому можно присвоить скалярное значение, — например, скалярной переменной или элементом массива:
$which = "this is a test";
$which =~ s/test/quiz/; # $which теперь содержит "this is a quiz"
$someplace[$here] =~ s/left/right/; # заменить элемент массива
$d{"t") =~ s/^/x /; # поставить "х " перед элементом массива
Функции 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, "");
* На самом деле образец по умолчанию — строка "", поэтому начальный пробельный разделитель игнорируется, но для нас вышесказанного пока достаточно.
Упражнения
'Ответы к упражнениям даны в приложении А.
1. Постройте регулярное выражение, которое соответствует:
а) минимум одному символу а, за которым следует любое число символов Ь;
б) любому числу обратных косых, за которым следует любое число звездочек (любое число может быть и нулем);
в) трем стоящим подряд копиям того, что содержится в переменной
$whatever;
г) любым пяти символам, включая символ новой строки;
д) одному слову, написанному два или более раз подряд (с возможно изменяющимся пробельным символом), где "слово" определяется как непустая последовательность непробельных символов.
2. а) Напишите программу, которая принимает список слов из stdin и ищет строку, содержащую все пять гласных (a,e,i,o ии). Запустите эту программу с /usr/dict/words* и посмотрите, что получится. Другими словами, введите
$ программа </usr/dict/words
б) Модифицируйте программу так, чтобы пять гласных должны были стоять по порядку, а промежуточные буквы значения не имели.
в) Модифицируйте программу так, чтобы все гласные должны были стоять в порядке возрастания, чтобы все пять гласных должны были присутствовать и чтобы перед буквой "а" не стояла буква "е", перед буквой "е" не стояла буква "i" и т.д.
3. Напишите программу, которая просматривает файл /etc/passwcf* (из stdin), выводя на экран регистрационное имя и реальное имя каждого пользователя. (Совет: с помощью функции split разбейте строку на поля, а затем с помощью sill избавьтесь от тех частей поля comment, которые стоят после первой запятой.)
4. Напишите программу, которая просматривает файл /etc/passwd (из stdin) на предмет наличия двух пользователей с одинаковыми именами и выводит эти имена. (Совет: после извлечения первого имени создайте хеш с этим именем в качестве ключа и числом его экземпляров в качестве значения. Прочитав последнюю строку stdin, ищите в этом хеше счетчики с показанием больше единицы.)
5. Повторите последнее упражнение, но с выдачей имен всех пользователей, зарегистрировавшихся под одинаковыми именами. (Совет: в хеше вместо числа экземпляров сохраните список регистрационных имен, записанных через пробелы. Затем ищите значения, содержащие пробел.)
* Словарь вашей системы может находиться не в каталоге /usr/dict/words; обратитесь к man-странице spell(l).
** Если используется NIS, то файл /etc/passwd в вашей системе будет содержать мало данных. Посмотрите, может быть, ypcat passwd даст больше информации.
| Назад | Вперед |
| Содержание | Предисловие | Введение | Ссылки | Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10 | Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19 | Приложение А | Приложение Б | Приложение В | Приложение Г | |
Сборник часто задаваемых вопросов
Часто задаваемые вопросы (FAQ) по Perl — это собрание вопросов и ответов, которые часто появляются в телеконференции comp.lang.perl.misc. Во многих отношениях это собрание можно рассматривать как дополнение к имеющимся книгам. Здесь разъясняются понятия, в которых пользователи, возможно, не разобрались, и сообщается оперативная информация о таких вещах, как последняя редакция и лучший источник для получения исходного кода Perl.
Этот сборник FAQ периодически публикуется в телеконференции comp.lang.perl.announce. Кроме того, его можно найти в Web по адресу http://www.perl.com/perl/faq.
Начиная с версии Perl 5.004, этот FAQ включен в документацию стан-дартного дистрибутива. Вот его основные разделы, каждый из которых оформлен как отдельная man-страница:
perlfaq
Структурный обзор FAQ.
perlfaq1
Очень общая, высокоуровневая информация о языке Perl.
perlfaq2
Где найти исходный код и документацию на Perl, вопросы поддержки, обучения и сопутствующие вопросы.
perlfaq3
Инструментарий программиста
perlfaq4
Обработка чисел, дат, строк, массивов, хешей и разнообразные аспекты обработки данных.
perlfaq5
Ввод-вывод, дескрипторы файлов, запись на диск, форматы, нижние колонтитулы.
perlfaq6
Сопоставление с образцами и регулярные выражения.
perlfaq7
Общие вопросы, которые нельзя отнести ни к одной из других категорий.
perlfaq8
Межпроцессное взаимодействие, управление пользовательским интер-фейсом: клавиатура, экран, координатно-указательные устройства.
perlfaq9
Сети, Internet и кое-что о Web.
Сетевые клиенты
Немногие компьютеры (и, соответственно, работающие на них пользователи) остаются в изоляции от остального компьютерного мира. Сети, когда-то бывшие достоянием в основном государственных научно-исследовательских лабораторий и факультетов вычислительной техники крупнейших университетов, сейчас доступны практически каждому — даже пользователю домашнего компьютера с модемом, устанавливающему по коммутируемой линии соединение с провайдером с помощью протоколов SLIP или РРР. Сейчас сети больше чем когда-либо используются в повседневной работе организациями и пользователями всех слоев общества — для обмена электронной почтой, планирования встреч, управления распределенными базами данных, доступа к информации предприятий, получения прогнозов погоды, чтения текущих новостей, разговоров с собеседниками из другого полушария, рекламирования продуктов и фирм через Web и т.д.
У всех этих приложений есть одна общая черта: они работают на основе TCP, фундаментального протокола, который обеспечивает взаимодействие между собой всех сетей, входящих в Internet*. И мы имеем в виду не только Internet. He считая брандмауэров, базовая технология везде одна, независимо от того, устанавливается соединение по Internet, соединение между офисами компании или соединение между кухней и подвалом вашего дома. Это удобно: для того чтобы ориентироваться во всех применениях Internet/intra-net, вы должны изучить только одну технологию.
Как же с помощью сети позволить приложению, работающему на одной машине, общаться с другим приложением, которое может функционировать на совершенно другой машине? Средствами Perl это сделать очень легко, но сначала вам, наверное, нужно немного узнать о том, как работает сеть на базе протокола TCP.
* На самом деле коммуникации в Internet обеспечиваются протоколом IP (Internet Protocol), а протокол TCP (Transmition Control Protocol) является протоколом более высокого уровня.
Даже если вы еще ни разу не работали в компьютерной сети, вы уже знаете о системе с установлением соединений: это — телефонная сеть. И пусть вас не смущают причудливые словосочетания вроде "программирование систем клиент/сервер". Видя слово "клиент", читайте "вызывающий абонент", а видя слово "сервер" — читайте "отвечающий абонент".
Звоня кому- то по телефону, вы выступаете в роли клиента. Тот, кто поднимает трубку на другом конце линии, является сервером.
Программисты, имеющие опыт работы на С, возможно, знакомы с гнездами (sockets) . Гнездо — это интерфейс к сети в том же самом смысле, что и дескриптор файла — это интерфейс к файлам в файловой системе. В частности, для тех простых программ, которые мы продемонстрируем ниже, вы можете пользоваться дескриптором гнезда так же, как дескриптором файла*.
Вы можете читать данные из гнезда, записывать в него данные, а также выполнять обе эти операции. Это объясняется тем, что гнездо — особый вид двунаправленного дескриптора файла, представляющего сетевое соединение. В отличие от обычных файлов, созданных посредством функции open, гнезда создаются с помощью низкоуровневой функции socket.
Давайте выжмем еще немного из нашей телефонной модели. Звоня на коммутатор большой компании, вы можете попросить соединить вас с конкретным отделом по названию (например, с отделом кадров) или по номеру (например, "дополнительный 213"). Представьте, что каждый сервис, работающий на компьютере,— это отдел большой корпорации. Иногда сервис имеет несколько имен, например http и www, но только один номер, например 80. Этот номер, связанный с именем сервиса, называется его портом. С помощью Perl-функций getservbyname иgetservbyport МОЖНО найти имя сервиса по номеру его порта и наоборот. Вот некоторые стандартные ТСР-сервисы и номера их портов:
Сервис | Порт | Назначение |
echo | 7 | Принимает все вводимые данные и воспроизводит их |
discard | 9 | Принимает все, но ничего не возвращает |
daytime | 13 | Возвращает текущую дату и местное время |
ftp | 21 | Сервер для обработки запросов пересылки файлов |
telnet | 23 | Сервер для интерактивных telnet-сеансов |
smtp | 25 | Простой протокол пересылки почты; демон-почтальон |
time | 37 | Возвращает число секунд, прошедших с начала 1900-го года (в двоичном формате) |
http | 80 | Сервер World Wide Web |
nntp | 119 | Сервер телеконференций |
* Почти так же; поиск по гнезду невозможен.
Хотя гнезда изначально разрабатывались для Berkeley UNIX, все возрастающая популярность Internet побудила практически всех поставщиков операционных систем включить в свои продукты поддержку гнезд для программирования систем клиент/сервер. Функция socket является относительно низкоуровневой, а мы в нашей книге рассматриваем в основном высокоуровневые средства Perl. Рекомендуем вам пользоваться более удобным модулем IO::Socket*, который мы будем применять во всех наших примерах. Это значит, что мы также будем применять некоторые объектно-ориентированные конструкции Perl. Краткое введение в эти конструкции дано в главе 19. Более подробное введение в объектно-ориентированное программирование на Perl приведено на man-странице perltoot(l) и в главе 5 книги Programming Perl.
Подробное рассмотрение TCP/IP выходит за рамки нашей книги, но мы можем представить хотя бы несколько простых клиентов. О серверах, которые более сложны, вы можете узнать в главе 6 книги Programming Perl и на man-странице perlipc(l).
Простой клиент
Для нашего простейшего клиента мы выберем довольно скучный сервис, который называется daytime ("время суток"). Сервер времени суток посылает установившему соединение клиенту одну строку данных, содержащую значение времени суток на этом удаленном сервере, а затем закрывает соединение.
Вот клиент:
#!/usr/bin/peri -w use 10::Socket;
$remote = 10::Socket::INET->new(
Proto => "tcp",
PeerAddr => "localhost",
PeerPort => "daytime(13)",
) or die "cannot connect to daytime port at localhost";
while ( <$remote> ) ( print )
Запустив эту программу, вы должны получить с сервера примерно следующее:
Thu May 8 11:57:15 1997
* IO::Socket входит в состав стандартного дистрибутива Perl версии 5.004. Если у вас более ранняя версия, получите этот модуль из CPAN, где вы найдете модули с простыми интерфейсами к следующим сервисам: DNS, ftp, Ident(RFC 931), NIS и NISPlus, NNTP, ping, POP3, SMTP, SNMP, SSLeay, telnet, time и др.
Вот что означают параметры конструктора new:
Proto
Протокол, который следует использовать. В данном случае возвращенный дескриптор гнезда будет подключен к TCP-гнезду, потому что нам нужно потоковое соединение, т.е. соединение, которое работает почти так же, как старый добрый файл. Не все гнезда такие. Например, с помощью протокола UDP можно создать дейтаграммное гнездо, используемое для передачи сообщений.
PeerAddr
Имя или Internet-адрес удаленного хоста, на котором работает сервер. Мы могли бы указать имя подлиннее, например www.perl.com, или адрес вроде 204.148.40.9. Однако для демонстрационных целей мы использовали специальное хост-имя localhost, которое всегда должно обозначать машину, на которой вы работаете. Имени localhost соответствует Internet-адрес 727.0.0.1.
PeerPort
Имя или номер порта сервиса, с которым мы хотим соединиться. В системах с нормально конфигурированным системным файлом серви-сов* мы могли бы обойтись просто именем daytime, но на всякий случай мы все же дали в круглых скобках номер порта (13). Использование одного номера без имени тоже сработало бы, но осторожные программисты стараются не использовать числа вместо констант.
Вы обратили внимание на то, как возвращаемое значение конструктора new используется в роли дескриптора файла в цикле while? Это то, что называется косвенным дескриптором файла — скалярная переменная, содержащая дескриптор файла. Его можно использовать точно так же, как обычный дескриптор. Например, так из него можно прочитать одну строку:
$line = <$handle>;
так — все остальные строки:
@lines = <$handle>;
а так — послать в него строку данных:
print $handle "some data\n";
* Системный файл сервисов в UNIX находится в каталоге /etc/services.
Клиент webget
Вот простой клиент, который устанавливает соединение с удаленным сервером и получает с него список документов. Этот клиент интереснее предыдущего, потому что перед получением ответа сервера он посылает на него строку данных.
#!/usr/bin/peri -w use 10::Socket;
unless (@ARGV > 1) ( die "usage: $0 host document ..." } $host = shift (OARGV);
foreach $document ( OARGV ) (
$remote == 10::Socket::INET->new( Proto => "tcp",
PeerAddr => $host,
PeerPort => "http (80)",
);
unless ($remote) ( die " cannot connect to http daemon on $host" )
$remote->autoflush(l) ;
print $remote "GET $document HTTP/I.0\n\n";
while ( <$remote> ) ( print )
-close $remote;
)
Подразумевается, что Web-сервер, на котором работает сервис http, использует свой стандартный порт (номер 80). Если сервер, с которым вы пытаетесь установить соединение, использует другой порт (скажем, 8080), то в качестве третьего аргумента конструктора new () нужно указать PeerPort => 8080. При работе с этим гнездом применяется метод autoflush, потому что в противном случае система буферизировала бы выходную информацию, которую мы ей передали. (Если у вас компьютер Macintosh, то нужно заменить все \п в коде, предназначенном для передачи данные по сети, на
\015\012.)
Соединение с сервером — это лишь первый этап процесса: установив соединение, вы должны начать говорить на языке этого сервера. Каждый сервер сети использует свой собственный маленький командный язык, и входные данные, подаваемые на сервер, должны быть сформулированы именно на этом языке. Начинающаяся словом GET строка, которую мы послали серверу, соответствует синтаксису протокола HTTP. В данном случае мы просто запрашиваем каждый из указанных документов. Да, мы действительно создаем новое соединение для каждого документа, несмотря на то, что это тот же самый хост. Именно так функционирует НТТР-серевер. (Последние версии Web-броузеров могут требовать, чтобы удаленный сервер оставлял соединение открытым на некоторое время, но сервер не обязан удовлетворять такой запрос.)
Мы назовем нашу программу webget. Вот как ее можно было бы выполнить:
shell prompt? webget www.peri.com /guanaco.html
HTTP/I.I 404 File Not Found
Date: Thu, 08 May 1997 18:02:32 GMT
Server: Apache/1.2b6
Connection: close
Content-type: text/html
<HEADXTITLE>404 File Not Found</TITLEX/HEAD>
<BODYXHl>File Not Found </H1>
The request URL /guanaco. html was not found on this server. <P>
</BODY>
Это, конечно, не очень интересно, потому что программа не нашла конкретный документ, однако длинный ответ не поместился бы на этой странице.
Чтобы ознакомиться с более развитой версией данной программы, вам нужно найти программу Iwp-request, входящую в состав модулей LWP из CPAN. (LWP мы вкратце рассмотрели в конце главы 19.)
Интерактивный клиент
Создать программу-клиент, которая просто читает все с сервера или посылает одну команду, получает один ответ, а затем завершает свою работу, очень легко. А как насчет создания чего-нибудь полностью интерактивного, вроде telnefi Мы имеем в виду приложение, которое позволяло бы вам набрать строку, получить ответ, набрать еще одну строку, вновь получить ответ и т.д. (В принципе, telnet обычно работает в символьном, а не в строковом режиме, но идею вы поняли.)
Этот клиент — более сложный, чем те два, с которыми мы имели дело до сих пор, но если вы работаете в системе, которая поддерживает мощный вызов fork, решение получится не слишком сложным. Установив соединение с тем сервисом, с которым вы хотите пообщаться, клонируйте свой процесс вызовом fork. Каждый из созданных идентичных процессов должен выполнить очень простое задание: родительский копирует все из гнезда на стандартный вывод, а порожденный одновременно копирует все со стандартного ввода в гнездо. Реализовать это с помощью только одного процесса было бы гораздо труднее, потому что легче написать два процесса для выполнения одной задачи, чем один процесс — для выполнения двух задач*.
Вот наш код:
#!/usr/bin/peri -w use strict;
use 10::Socket;
my ($host, $port, $kidpid, $handle, $line);
unless (8ARGV == 2 ) ( die "usage: $0 host port" ) ($host, $port) = 8ARGV;
# создать tcp-соединение с указанным хостом и портом $handle - 10::Socket::INET->new(Proto => "tcp",
PeerAddr => $host,
PeerPort => $port)
or die "can't connect to port $port on $host: $!";
$handle->autoflush(l); # и результат сразу же попадает туда print STDERR "[Connected to $host:$port]\n";
# разбить программу на два процесса-близнеца
die "can't fork: $!" unless defined ($kidpid = fork());
# блок if{( выполняется только в родительском процессе if($kidpid) (
# копировать данные из гнезда на стандартный вывод while (defined ($line = <$handle> )) f print STDOUT $line;
1
kill ("TERM",$kidpid); # послать в порожденный процесс сигнал SIGTERM >
# блок else(} выполняется только в порожденном процессе else 1
# копировать данные со стандартного ввода в гнездо while (defined ($line = <STDIN>)) ( print $handle $line;
} 1
* Принцип сохранения простоты — один из краеугольных камней не только философии UNIX, но и высококачественного проектирования программного обеспечения. Наверное, именно поэтому он распространился и на другие системы.
Функция kill в блоке if родительского процесса пошлет сигнал в наш порожденный процесс (в текущий момент работающий в блоке else), как только удаленный сервер закроет свою сторону соединения.
Что еще почитать о сетях
О сетях можно говорить и говорить, но мы дадим ссылки на источники, которые помогут вам перейти от разговоров к делу. В главе 6 книги Programming Perl и на man-странице perlipc(l) описано межпроцессное взаимодействие в общем; на man-странице IO::Socket(3) — объектная библиотека; на man-странице Socket(3) — низкоуровневый интерфейс к гнездам. Более опытным программистам рекомендуем книгу Unix Network Programming (by Richard Stevens, Addison-Wesley), в которой очень хорошо освещены все вопросы данной темы. Будьте, однако, осторожны: большинство книг по программированию гнезд написаны С-программистами.
| Назад | Вперед |
| Содержание | Предисловие | Введение | Ссылки | Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10 | Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19 | Приложение А | Приложение Б | Приложение В | Приложение Г | |
СGІ-программирование
Если в течение последних нескольких лет вы не сидели взаперти в деревянной хижине без злектричества, то вы наверняка слышали о World Wide Web. Web-адреса (больше известные как URL) сейчас можно найти везде: на рекламних плакатах и в титрах кинофильмов, на обложках журна-лов и на страницах других изданий, от газет до правительственных отчетов.
Многие из самых интересных Web-страниц включают разного рода формы, предназначенные для ввода данных пользователем. Вы вводите данные в такую форму и щелкаете на кнопке или рисунке. Зто действие запускает некую программу на Web-сервере, которая изучает введенные вами данные и генерирует новую выходную информацию. Иногда зта программа (широко известная как программа общего шлюзового интерфейса, или CGI-программа) представляет собой просто интерфейс к существующей базе данных; она преобразует введенные вами данные в нечто понятное для зтой базы данных, а выходную информацию базы данных — в нечто понятное для Web-броузера (обычно в HTML-форме).
CGI-программы не просто обрабатывают данные, введенные в форму. Они вызываются и тогда, когда вы щелкаете на графическом изображении, и фактически могут использоваться для отображения всего того, что "видит" ваш броузер. Web-страницы с CGI-поддержкой — зто не безликие и нудньге документы, а удивительно живые страницы с динамически изменяющимся содержимым. Именно динамическая информация делает Web интересньш и интерактивным источником информации, а не просто средством, предна-значенньш для чтения книги с терминала.
Независимо от того, во что вас, возможно, заставят поверить все зти отскакивающие шарики и прыгающие обьявления, Web содержит также большой обіем текста. Поскольку мы имеем дело с текстом, файлами, сетевыми коммуникациями и в некоторой степени с двоичными данными, то Perl идеально подходит для Web-программирования.
В зтой главе мы не только изучим основы CGI-программирования, но и параллельно получим определенные начальные сведения о гипертекстовых ссылках, библиотечных модулях и обьектно-ориентированном программи-ровании на Perl. В конце главы мы дадим начальные сведения о применении Perl для других видов Web-программирования.
Если рассматривать зту главу как отдельное пособие, следует отметить, что ее (как и любого другого документа обьемом меньше пары сотен страниц) недостаточно для изучения более сложных тем, затронутых здесь, в частности обьектно-ориентированного программирования и методов использования различных типов ссылок и запросов в WWW. Однако, будучи средством всего лишь предварительного ознакомления с тем, что вас ждет, представленные здесь примеры и пояснення к ним, возможно, побудят вас подробнее ознакомиться с затронутыми темами и дадут вам ориентиры для выбора соответствующих учебников. А если вы любите учиться на практико, они помогут вам сразу написать некоторые полезные программы на основе тех моделей, которые здесь представлены.
Мы предполагаем, что вы уже в основном знакомы с HTML.
Модуль CGI.pm
Начиная с версии 5.004, в состав стандартного дистрибутива Perl вклю-чается модуль CGI.pm, который все знает и все умеет*.
Зтот модуль, который написал Линкольн Штейн, автор хорошо извест-ной книги How to Setup and Maintain Your Web Site, превращает процедуру создания CGI-программ на Perl в легкую прогулку. Как и сам Perl, CGI.pm является платформо-независимым, позтому его можно использовать прак-тически с любой ОС, от UNIX и Linux до VMS; он работает даже в таких системах, как Windows и MacOS.
* Если у вас инсталлирована одна из более ранних версии Perl (но как минимум 5.001) и вы еще не собрались переходить на новую, просто получите CGI.pm из CPAN.
Если CGI.pm уже инсталлирован у вас в системо, вы можете прочесть его полную документацию, воспользовавшись любым из способов, которые вы используете для чтения man-страниц Perl, например с помощью команд тап(1) или perldoc(l) либо обратившись к HTML-варианту документации. Если ничего не получается, прочтите файл CGI.pm'. документация на модуль встроена в сам модуль, представленими в простом формате pod *.
Разрабатывая CGI-программы, держите зкземпляр man-страницы модуля CGI.pm под рукой. Она не только содержит описание функций зтого модуля, но и загружается вместе со всевозможными примерами и советами.
Ваша CGI-программа в контексте
На рис. 19.1 показаны взаимосвязи между Web-броузером, Web-сервером и CGI-программой. Когда вы, работая со своим броузером, щелкаете на какой-либо ссылке, помните, что с зтой ссьшкой связан универсальный локатор ресурса, URL (Uniform Resource Locator). Зтот URL указывает на Web-сервер и ресурс, доступний через данини сервер. Таким образом, броузер взаимодействует с сервером, запрашивая указаними ресурс. Если, скажем, ресурс представляет собой HTML-форму, предназначенную для заполнения, то Web-сервер загружает зту форму в броузер, который затем выводит ее на зкран, чтобы вы могли ввести требуемне данные.
Каждое предназначенное для ввода текста поле в зтой форме имеет имя (указанное в HTML-коде формы) и соответствующее значение, которым является все, что вы вводите в зтом поле. Сама форма связана (через HTML-директиву <form>) с CGI-программой, которая обрабатывает данные, введенные в форму. Когда вы, заполнив форму, щелкаете на кнопке Submit, броузер обращается к URL CGI-программы. Перед зтим он добав-ляет в конец URL так называемую строку запроса, которая состоит из одной или более пар имя=з наче ниє; каждое имя — зто имя поля, предназначенного для ввода текста, а каждое значение — данные, которые вы ввели. Таким образом, URL, в который броузер передает данные, введенные вами в форму, выглядит приблизительно так (строкой запроса считается все, что стоит после вопросительного знака):
http://www.SOMEWHERE.org/cgi-bin/some_cgi_prog?flavor=vanilla&size=double
Вы видите здесь две пары имя=значение. Такие пары отделяются друг от друга амперсандом (&). Работая с модулем CGI.pm, об зтой детали можете не беспокоиться. Компонент /cgi-bm/some_cgi_prog/WA рассмотрим немного позд-нее; на данный момент важно лишь то, что он содержит путь к CGI-программе, которая будет обрабатывать данные, введенные в HTML-форму.
* Pod сокращенно обозначает plain old documentation ("обычная старая документация"). Зто упрощенная форма представлення, испояьзуемая для всей документации на Perl. Принцип работы зтого формата изложен на man-странице perlpod(l), а некоторыс pod-трансляторы описанм на man-страницах pod2man(l), pod2html(l) и pod2text(l).
Рис. 19.1. Заполнение форми с привлечением CGI
Когда Web-сервер (в данном случае www.SOMEWHERE.org) получает URL от вашего броузера, он вызывает указанную CGI-программу и передает в нее в качестве аргументов пары имя=значение. Программа затем делает то, что должна делать, и (как правило) возвращает HTML-код серверу, который, в свою очередь, загружает его в броузер для представлення пользователю.
"Переговоры" между броузером и сервером, а также между сервером и CGI-программой ведутся в соответствии с протоколом, известным как HTTP. При написаний CGI-программы об зтом беспокоиться не нужно, т.к. модуль CGI.pm сам решает все вопросы, связанные с использованием протокола.
Способ получения CGI-программой ее аргументов (й другой информа-ции) от броузера через сервер описывается спецификацией Common Gateway Interface. Об зтом тоже не нужно беспокоиться; как вы вскоре увидите, CGI.pm автоматически обрабатывает зти аргументы, не требуя от вас каких-либо дополнительных действий.
Наконец, вам следует знать, что CGI-программы могут работать с любым HTML-документом, а не только с формой. Например, вы могли бы написать такой HTML-код:
Click <a href="http://www.SOMEWHERE.org/cgi-bin/fortune.cgi">here</a> to receive your fortune.
Зцесь fortune.cgi —
программа, которая просто вызывает программу fortune (в UNIX-системах). В данном случае в CGI-программу не вводятся никакие аргументы. Другой вариант: HTML-документ мог бы содержать две ссылки для пользователя — одну для получения предсказания судьбы, вторую для выяснения текущей даты. Обе ссылки могли бы указывать на одну и ту же программу — в одном случае с аргументом fortune, поставленным в URL после вопросительного знака, а в другом случае — с аргументом date. Зти HTML-ссылки выглядели бы так:
<а href="http://www.SOMEWHERE.org/agi-bin/fortune_or_date?fortune"> <a href="http://www.SOMEWHERE.org/cgi-bin/fortune_or_date?date">
CGI-программа (в данном случае fortune_or_date) определила бы, какой из двух возможных аргументов получен, и вьшолнила бы соответственно программу fortune или программу date.
Как видите, аргументи вовсе не должны иметь формат имя=значение, характерний для HTML-форм. Вы можете написать CGI-программу, которая будет делать практически все, что вы пожелаете, и можете передавать ей практически любые аргументы.
В зтой главе мы будем, главным образом, рассматривать применение HTML-форм. При зтом мы предполагаем, что вы уже знакомы с простими HTML-кодами.
Простейшая CGI-программа
Вот исходный код вашей первой CGI-программы. Она настолько проста, что в ней даже не пришлось использовать модуль CGI.pm:
#!/usr/bin/perlS -w
#самая легкая из CGI-програми print “END of Multiple Text;
Content-type: text/html
<HTML>
<HEAD>
<TITLE>Hello World</TITLE>
</HEAD>
<BODY>
<Hl>Greetings, Terrans!</Hl>
</BODY </HTML>
END_of_Multiline_Text
Каждый раз, когда зта программа вызывается, она выдает на зкран одно и то же. Зто, конечно, не особенно интересно, но позднее мы сделаем ее более занимательной.
Зта программка содержит всего один оператор: вызов функции print. Несколько забавно вьп-лядящий аргумент — зто так называемый here-доку-мент. Он состоит из двух знаков "меньше чем" и слова, которое мы назовем конечной лексемой. Для программиста, работающего с shell, написанное, возможно, будет похоже на переадресацию ввода-вывода, но на самом деле зто просто удобный способ взятия в кавычки строкового значення, зани-мающего несколько строк. Зто строковеє значение начинается на следующей строке программы и продолжается до строки, содержащей конечную лексему, которая должна стоять в самом начале зтой строки; ничего другого в зтой строке быть не должно. Неге-документы особенно полезны для создания HTML-документов.
Первая часть зтого строкового значення — определенно самая важная:
строка Content-Type задает тип генерируемой выходной информации. Сразу за ней идет пустая строка, которая не должна содержать пробелов и знаков табуляции.
У большинства новичков первые CGI-программы отказываются рабо-тать, потому что пользователи забывают об зтой пустой строке, отделяющей заголовок (нечто вроде заголовка сообщения злектронной почты) от следую-щего за ним необязательного тела*. После пустой строки следует HTML-документ, посылаемый в броузер пользователя, где он форматируется и ото-бражается.
Сначала добейтесь, чтобы ваша программа правильно вьшолнялась при вызове ее из командной строки. Зто необходимый, но не достаточный шаг для того, чтобы обеспечить функционирование вашей программы как сценария, работающего на сервере. Ошибки могут возникать и в других местах программы;
см. ниже раздел "Поиск и устранение ошибок в CGI-программах".
Если программа должным образом работает при вызове ее из командной строки, необходимо инсталлировать ее на компьютере-сервере. Приемлемые места размещения зависят от сервера, хотя для CGI-сценариев часто исполь-зуется каталог /usr/etc/httpd/cgi-bin/ и его подкаталоги. Обсудите зтот вопрос с Web-мастером или системным администратором.
После завершення инсталляции вашей программы в CGI-каталоге ее можно выполнять, указывая броузеру ее путевое имя в составе URL. Напри-мер, если ваша программа называется howdy, URL будет выглядеть так:
http://vww.SOMEWHERE.org/cgi-bin/howdy.
Сервери обычно позволяют использовать вместо ддинных путевых имен псевдонимы. Сервер, имеющий адрес www.SOMEWHERE.org, может запросто перевести cgi-bin/howdy, содержащийся в зтом URL, в нечто вроде usr/etc/httpd/ cgi-bin/howdy. Ваш системний администратор или Web-мастер может подска-зать, какой псевдоним следует использовать при обращении к вашей программе.
* Зтот заголовок необходим для протокола HTTP, о котором мы упоминали выше.
Передача параметров через CGI
Для передачи параметров в CGI-программы (точнее, в большинство CGI-программ) никакие формы не нужны. Чтобы убедиться в этом, замените URL на http://www.SOMEWHERE.org/cgi-bin/ice_creain?flavor=mint.
Когда вы "нацеливаете" свой броузер на этот URL, броузер не только просит Web-сервер вызвать программу ice_cream, но и передает в нее строку flavor=mint. Теперь дело программы — прочитать данную строку-аргумент и разобрать ее. Эта задача не так проста, как кажется. Многие программы пытаются решить ее и разобрать запрос самостоятельно, но большинство "самодельных" алгоритмов время от времени отказывают. Учитывая то, насколько сложно найти правильное решение такой задачи для всех возможных случаев, вам, наверное, не следует писать код самим, особенно при наличии отличных готовых модулей, которые выполняют этот хитрый синтаксический анализ за вас.
К вашим услугам — модуль CGI.pm, который всегда разбирает входящий CGI- запрос правильно. Чтобы вставить этот модуль в свою программу, просто напишите
use CGI;
где-нибудь в начале программы*.
Оператор use похож на оператор # include языка С тем, что в процессе компиляции извлекает код из другого файла. Но он допускает также использование необязательных аргументов, показывающих, к каким функциям и переменным из этого модуля вы хотели бы обращаться. Поместите их в список, следующий за именем модуля в операторе use,— и вы сможете обращаться к указанным функциям и переменным так, как будто они ваши собственные.
В данном случае все, что нам нужно использовать из модуля CGI.pm — это функция param () **.
Если аргументы не указаны, функция param () возвращает список всех полей, имевшихся в HTML-форме, на которую отвечает данный CGI-сце-нарий. (В текущем примере это поле flavor, а в общем случае — список всех имен, содержащихся в строках имя=значение переданной формы.) Если указан аргумент, обозначающий поле, то param () возвращает значение (или значения), связанные с этим полем. Следовательно, param (" flavor") возвращает "mint", потому что в конце URL мы передали ?flavor=mint.
* Имена всех Perl-модулей имеют расширение рт. Более того, оператор use подразумевает это расширение. О том, как создавать свои собственные модули, вы можете узнать в главе 5 книги Programming Perl или на man-странице perlmod(l).
** Некоторые модули автоматически экспортируют все свои функции, но, поскольку CGI.pm — это на самом деле объектный модуль, замаскированный под обычный, мы должны запрашивать его функции явно.
Несмотря на то что в нашем списке для оператора use имеется всего один элемент, мы будем использовать запись qw (). Благодаря этому нам будет легче впоследствии раскрыть этот список.
#!/usr/local/bin/perlS -w
# программа ответа на форму о любимом сорте мороженого (версия 1) use CGI qw(param);
print “END_of_Start;
Content-type: text/html
<HTML>
<HEAD>
<TITLE>Hello World</TITLE>
</HEAD>
<BODY>
<Hl>Greetings, Terrans!</H1> END_of_Start
my $favorite = param("flavor");
print "<P>Your favorite flavor is $favorite. print “All_Done;
</BODY>
</HTML> All Done
Как сократить объем вводимого текста
Вводить все равно приходится очень много, но в CGI.pm есть множество удобных функций, упрощающих набор. Каждая из этих функций возвращает строковое значение, которое вы будете выводить. Например, header () возвращает строковое значение, содержащее строку Content-type с последующей пустой строкой, start_html (строка) возвращает указанную строку как HTML-титул (название документа), hi (строка) возвращает указанную строку как HTML-заголовок первого уровня, а р (строка) возвращает указанную строку как новый HTML-абзац.
Мы могли бы перечислить все эти функции в списке, прилагаемом к оператору use, но такой список разросся бы до небывалых размеров. В CGI.pm, как и во многих других модулях, имеются так называемые директивы импорта — метки, которые обозначают группы импортируемых функций. Вам нужно лишь поставить желаемые директивы (каждая из которых начинается двоеточием) в начале своего списка импорта. В модуле CGI.pm имеются такие директивы:
: cgi
Импортировать все методы обработки аргументов, например param ().
: form
Импортировать все методы создания заполняемых форм, например text-field().
:html2
Импортировать все методы, которые генерируют стандартные элементы HTML 2.0.
:htmi3
Импортировать все методы, которые генерируют элементы, предложенные в HTML 3.0 (такие как <table>, <super> и <sub>).
:netscape
Импортировать все методы, которые генерируют расширения HTML, характерные для Netscape.
:shortcuts
Импортировать все сокращения, генерируемые HTML (т.е. "html2" + "html3" + "netscape").
:standard
Импортировать "стандартные" возможности: "html2", "form" и "cgi".
:all
Импортировать все имеющиеся методы. Полный список приведен в модуле CGI.pm, где определяется переменная %tags.
Мы будем использовать только директиву : standard. (Подробная информация об импортировании функций и переменных из модулей приведена в главе 7 книги Programming Perl, а также на man-странице Exporter 3).}
Вот как выглядит наша программа со всеми сокращениями, которые используются в CGI.pm:
#!/usr/local/bin/perlS -w
# cgi-bin/ice_cream # программа ответа на форму о любимом
t
сорте мороженого (версия 2) use CGI qw(:standard);
print header() ;
print start_html("Hello World"), hi ("Hello World");
my $favorite = param("flavor");
print p("Your favorite flavor is $favorite.");
print end_html();
Видите, насколько это проще? Вам не нужно беспокоиться о декодировании данных формы, о заголовках и HTML-тексте, если вы этого не хотите.
Генерирование формы
Если вам надоело вводить параметры своей программы в броузер — создайте заполняемую форму. К таким формам привыкли большинство пользователей. Компоненты формы, которые принимают вводимые пользователем данные, иногда называются 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. Результат обработки переданного с использованием формы запроса
Другие компоненты формы
Теперь, когда вы знаете, как создавать в форме простые текстовые поля и заполнять их, вам, наверное, интересно будет узнать, как создавать компоненты формы других типов — кнопки, отмечаемые блоки и меню.
Сейчас мы рассмотрим более развитую версию нашей программы. В частности, мы включили в нее новые компоненты формы: всплывающие меню, кнопку передачи (которая называется 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() должна быть сделана ссылка на массив, которую как раз и создают квадратные скобки. Считайте квадратные скобки удобным способом представления нескольких значений как одного.
Создание CGI-программы гостевой книги
Если вы внимательно изучили примеры, приведенные выше, то уже должны быть способны заставить работать простые CGI-программы. А как насчет более сложных? Одна из распространенных задач — создание CGT-программы для управления гостевой книгой, чтобы посетители вашего Web-узла могли записывать в нее свои собственные сообщения*.
* Как мы отметим ниже, это приложение можно было бы назвать программой Webchat (переговоров через Web).
Форма, используемая для создания гостевой книги, довольно проста, она даже проще, чем некоторые из наших форм, посвященных мороженому. Она, правда, имеет некоторые особенности, но не беспокойтесь, по мере продвижения к финишу мы преодолеем все трудности.
Вероятно, вы хотите, чтобы сообщения в гостевой книге сохранялись и по завершении посещения вашего узла тем или иным пользователем, поэтому вам нужен файл, куда они будут записываться. Гостевая CGI-программа (вероятно) работает не под вашим управлением, поэтому у нее, как правило, не будет права на обновление вашего файла. Следовательно, первое, что необходимо сделать,— это создать для нее файл с широкими правами доступа. Если вы работаете в UNIX-системе, то можете сделать (из своего shell) для инициализации файла программы гостевой книги следующее:
touch /usr/tmp/chatfile chmod 0666 /usr/tmp/chatfile
Отлично, но как обеспечить одновременную работу с программой гостевой книги нескольких пользователей? Операционная система не блокирует попытки одновременного доступа к файлам, поэтому если вы будете недостаточно осторожны, то получите файл, в который записывают сообщения все пользователи одновременно. Чтобы избежать этого, мы используем Perl-функцию flock, позволяющую пользователю получить монопольный доступ к файлу, который мы разрешаем обновить. Это будет выглядеть примерно так:
use Fcnti qw(:flock); # импортирует LOCK_EX, LOCKJ3H, LOCK_NB flock(CHANDLE, LOCK_EX) || bail ("cannot flock $CHATNAME: $!");
Аргумент lock_ex функции flock — вот что позволяет нам обеспечить монопольный доступ к файлу*.
Функция flock представляет собой простой, но универсальный механизм блокировки, несмотря на то, что его базовая реализация существенно изменяется от системы к системе. Она не возвращает управление до тех пор, пока файл не будет разблокирован. Отметим, что блокировки файлов носят чисто рекомендательный характер: они работают только тогда, когда все процессы, обращающиеся к файлу, соблюдают эти блокировки одинаково. Если три процесса соблюдают блокировки, а один не соблюдает, то не функционирует ни одна из блокировок.
* В версиях Perl до 5.004 вы должны превратить в комментарий use Fcnti и в качестве аргумента функции flock использовать просто 2.
Объектно-ориентированное программирование на Perl
Наконец пришло время научить вас пользоваться объектами и классами — и это важнее всего. Хотя решение задачи построения вашего собственного объектного модуля выходит за рамки данной книги, это еще не повод для того, чтобы вы не могли использовать существующие объектно-ориентированные библиотечные модули. Подробная информация об использовании и создании объектных модулей приведена в главе 5 книги Programming Perl и на man-странице perltoot(l).
Мы не будем углубляться здесь в теорию объектов, и вы можете просто считать их пакетами (чем они и являются!) удивительных, великолепных вещей, вызываемых косвенно. Объекты содержат подпрограммы, которые делают все, что вам нужно делать с объектами.
Пусть, например, модуль CGI.pm возвращает объект $query, который представляет собой входные данные пользователя. Если вы хотите получить параметр из этого запроса, вызовите подпрограмму par am () :
$query->param("answer");
Данная запись означает: "Выполнить подпрограмму param () с объектом $query, используя "answer" как аргумент". Такой вызов в точности соответствует вызову любой другой подпрограммы, за исключением того что вы используете имя объекта, за которым следует синтаксическая конструкция ->. Кстати, подпрограммы, связанные с объектами, называются методами.
Если вы хотите получить значение, возвращенное подпрограммой param (), воспользуйтесь обычным оператором присваивания и сохраните это значение в обычной переменной $he_said:
$he_said = $query->param("answer");
Объекты выглядят как скаляры; они хранятся в скалярных переменных (таких как переменная $ query в нашем примере), и из них можно составлять массивы и хеши. Тем не менее их не следует рассматривать как строки и числа. По сути дела, это особый вид ссылок, их нельзя рассматривать как обычные ссылки. Объекты следует трактовать как особый, определяемый пользователем тип данных.
Тип конкретного объекта известен как его класс. Имя класса обычно состоит из имени модуля без расширения рт, и к тому же термины "класс" и "модуль" часто используются как эквиваленты. Таким образом, мы можем говорить о CGI-модуле или о CGI-классе. Объекты конкретного класса создает и контролирует модуль, реализующий этот класс.
Доступ к классам осуществляется путем загрузки модуля, который выглядит точно так же, как любой другой модуль, за исключением того что объектно-ориентированные модули обычно ничего не экспортируют. Вы можете рассматривать класс как фабрику, которая производит совершенно новые объекты. Чтобы класс выдал один из таких объектов, нужно вызвать специальный метод, который называется конструктор.
Вот пример:
$query = CGI->new(); # вызвать метод new() в классе "CGI"
Здесь мы имеем дело с вызовом метода класса. Метод класса выглядит точно так же, как метод объекта (о котором мы говорили секунду назад), за исключением того что вместо использования объекта для вызова метода мы используем имя класса, как будто он сам — объект. Метод объекта говорит "вызвать функцию с этим именем, которая относится к данному объекту", тогда как метод класса говорит "вызвать функцию с этим именем, которая относится к данному классу".
Иногда то же самое записывается так:
$query = new CGI; # то же самое
Вторая форма по принципу действия идентична первой. Здесь меньше знаков препинания, благодаря чему в некоторых случаях она более предпочтительна. Однако она менее удобна в качестве компонента большого выражения, поэтому в нашей книге мы будем использовать исключительно первую форму.
С точки зрения разработчика объектных модулей, объект — это ссылка на определяемую пользователем структуру данных, часто на анонимный хеш. Внутри этой структуры хранится всевозможная интересная информация. Воспитанный пользователь, однако, должен добираться до этой информации (с целью ее изучения или изменения), не рассматривая объект как ссылку и не обращаясь непосредственно к данным, на которые он указывает, а используя только имеющиеся методы объектов и классов. Изменение данных объекта другими средствами — это нечестная игра, после которой о вас обязательно станут говорить и думать плохо. Чтобы узнать о том, что представляют собой и как работают вышеупомянутые методы, достаточно прочитать документацию на объектный модуль, которая обычно прилагается в pod-формате.
Объекты в модуле CGI.pm
CGI-модуль необычен в том смысле, что его можно рассматривать либо как традиционный модуль с экспортируемыми функциями, либо как объектный модуль. Некоторые программы пишутся гораздо легче с помощью объектного интерфейса к модулю CGI.pm, нежели с помощью процедурного интерфейса к данному модулю. Наша гостевая книга — одна из таких программ. Мы получаем доступ к входной информации, которую пользователь ввел в форму, через CGI-объект и можем, при желании, с помощью этого же объекта генерировать новый HTML-код для отправки обратно пользователю.
Сначала, однако, нам нужно создать этот объект явно. Для CGI.pm, как и для многих других классов, метод, который позволяет создавать объекты,— это метод класса new () *.
* В отличие от C++ Perl не считает new ключевым словом; вы совершенно свободно можете использовать такие методы-конструкторы, как gimme_another() или fred.0. Тем не менее большинство пользователей в итоге приходят к тому, что называют свои конструкторы во всех случаях new ().
Данный метод конструирует и возвращает новый CGI-объект, соответствующий заполненной форме. Этот объект содержит все данные, введенные пользователем в форму. Будучи вызванным без аргументов, метод new () создает объект путем чтения данных, переданных удаленным броузером. Если в качестве аргумента указан дескриптор файла, он читает этот дескриптор, надеясь найти в нем данные, введенные в форму в предыдущем сеансе работы с броузером.
Через минуту мы покажем вам эту программу и поясним ее работу. Давайте предположим, что она называется guestbook и находится в каталоге cgi-bin. Хоть она и не похожа ни на один из тех сценариев, которые мы рассмотрели выше (в которых одна часть выводит HTML-форму, а вторая читает данные, введенные в форму пользователем, и отвечает на них), вы увидите, что она, тем не менее, выполняет обе эти функции. Поэтому отдельный HTML-документ, содержащий форму гостевой книги, нам не нужен. Пользователь мог бы сначала запустить нашу программу, просто щелкнув мышкой на такой ссылке:
Please sign our <А HREF="http://www.SOMEWHERE.org/cgi-bin/guestbook">guestbook</A>.
Затем программа загружает в броузер HTML- форму и, на всякий случай, предыдущие сообщения гостей (в ограниченном количестве), чтобы пользователь мог их просмотреть. Пользователь заполняет форму, передает ее, и программа читает то, что передано. Эта информация добавляется в список предыдущих сообщений (хранящийся в файле), который затем вновь выводится в броузер, вместе со свежей формой. Пользователь может продолжать чтение текущего набора сообщений и передавать новые сообщения, заполняя предлагаемые формы, столько раз, сколько сочтет необходимым.
Вот наша программа. Перед тем как разбирать ее поэтапно, вы, возможно, захотите просмотреть программу целиком.
#!/usr/bin/peri -w
use 5.004;
use strict; # установить объявления и взятие в кавычки use CGI qw(:standard); # импортировать сокращения согласно :standard use Fcnti qw(:flock); # импортирует LOCK_EX, LOCKJ3H, LOCK_NB
sub bail ( # функция обработки ошибок
my $error = "@ ";
print hi("Unexpected Error"), p($error), end html;
die $error;
!
my(
$CHATNAME, # имя файла гостевой книги $MAXSAVE, # какое количество хранить $TITLE, # название и заголовок страницы @cur, # все текущие записи
Sentry, # одна конкретная запись ) ;
$TITLE = "Simple Guestbook";
$CHATNAME = "/usr/tmp/chatfile"; # где все это в системе находится $MAXSAVE =10;
print header, start_html($TITLE), hi ($TITLE);
$cur ” CGI->new(); # текущий запрос if ($cur->param("message")) ( # хорошо, мы получили сообщение
• $cur->param("date", scalar localtime); # установить текущее время Sentries = ($cur); # записать сообщение в массив }
# открыть файл для чтения и записи (с сохранением предыдущего содержимого) open(CHANDLE, "+< $CHATNAME") II bail("cannot open $CHATNAME: $!");
# получить эксклюзивную блокировку на гостевую книгу
# (LOCK_EX == exclusive lock)
flock(CHANDLE, LOCK_EX) || bail("cannot flock $CHATNAME: $!");
# занести в $MAXSAVE старые записи (первой — самую новую) while (!eof(CHANDLE) && Sentries < $MAXSAVE) (
$entry = CGI->new(\*CHANDLE); t передать дескриптор файла по ссылке
push Sentries, $entry;
}
seek(CHANDLE, 0, 0) 11 bail("cannot rewind $CHATNAME: $!");
foreach $entry (Sentries) (
$entry->save(\*CHANDLE); # передать дескриптор файла по ссылке } truncate(CHANDLE, tell(CHANDLE)) || bail("cannot truncate $CHATNAME: $!");
close(CHANDLE) || bail ("cannot close $CHATNAME: $!");
print hr, start form; # hr()проводит горизонтальную линию: <HR> print p("Name:", $cur->textfield(
-NAME => "name")) ;
print p("Message:" $cur->textfield(
-NAME => "message",
-OVERRIDE => 1, # стирает предыдущее сообщение
-SIZE => 50)) ;
print p(submit("send"), reset("clear"));
print end_form, hr;
print h2("Prior Messages");
foreach $entry (Sentries) f
printf("%s [%s]: %s",
$entry->param("date"),
$entry->param("name"),
$entry->param("message")) ;
print br() ;
} print end_html;
На рис. 19.5 вы видите изображение, которое появляется на экране после запуска этой программы.
Рис. 19.5. Форма простой гостевой книги
Обратите внимание на то, что программа начинается с оператора
usa 5.004;
Если вы хотите запускать ее с помощью более ранние версии Perl 5, то нужно превратить в комментарий строку
use Fcnti qw(:flock)
и заменить lock_ex в первом вызове flock на z.
Поскольку каждое выполнение программы приводит к возврату HTML-формы в броузер, который обратился к программе, то программа начинается с задания HTML-кода:
print header, start_html($TITLE), hi($TITLE) ;
Затем создается новый CGI-объект:
$cur = CGI->new(); # текущий запрос
if ($cur->param("message")) ( # хорошо, мы получили сообщение
$cur->param("date", scalar localtime); # установить текущее время
Sentries = ($cur); # записать сообщение в массив
>
Если нас вызывают посредством заполнения и передачи формы, то объект $cur должен содержать информацию о тексте, введенном в форму. Форма, которую мы предлагаем (см. ниже), содержит два поля ввода: поле имени для ввода имени пользователя и поле сообщения для ввода сообщения. Кроме того, приведенный выше код ставит на введенные в форму данные (после их получения) метку даты. Передача в метод param() двух аргументов — это способ присваивания параметру, заданному первым аргументом, значения, указанного во втором аргументе.
Если нас вызывают не посредством передачи формы, а выполняя щелчок мышью на ссылке Please sign our guestbook, то объект запроса, который мы создаем, будет пуст. Проверка if даст значение "ложь", и в массив Sentries никакой элемент занесен не будет.
В любом случае мы переходим к проверке наличия записей, созданных ранее в нашем сохраняемом файле. Эти записи мы будем считывать в массив @entries. (Вспомните о том, что мы только что сделали текущие данные, если они имеются в форме, первым элементом этого массива.) Но сначала мы должны открыть сохраняемый файл:
open(CHANDLE, "+< $CHATNAME") || bail("cannot open $CHATNAME: $!");
Эта функция открывает файл в режиме неразрушающего чтения-записи. Вместо open можно использовать sysopen (). При таком способе посредством единственного вызова открывается старый файл, если он существует (без уничтожения его содержимого), а в противном случае создается новый файл:
# нужно импортировать две "константы" из модуля Fcnti для sysopen use Fcnti qw( 0_RDWR 0_CREAT );
sysopen(CHANDLE, $CHATFILE, 0_RDWRI0_CREAT, 0666) || bail "can't open $CHATFILE: $!";
Затем мы блокируем файл, как описывалось выше, и переходим к считыванию текущих записей из $ мах save в Sentries:
flock(CHANDLE, LOCK_EX) 11 bail("cannot flock $CHATNAME: $!");
while (!eof(CHANDLE) &S Sentries < $MAXSAVE) {
$entry = CGI ->new(\*CHANDLE); # передать дескриптор файла по ссылке
push Sentries, $entry;
}
Функция eof — встроенная Perl-функция, которая сообщает о достижении конца файла. Многократно передавая в метод new () ссылку на дескриптор сохраняемого файла*, мы выбираем старые записи, по одной при каждом вызове. Затем мы обновляем файл так, чтобы он включал новую запись, которую мы (возможно) только что получили:
seek(CHANDLE, 0, 0) || bail("cannot rewind $CHATNAME: $!");
foreach $entry (Sentries) {
$entry->save(\*CHANDLE); # передать дескриптор файла по ссылке } truncate(CHANDLE, tell (CHANDLE)) || bail("cannot truncate $CHATNAME: $!");
close (CHANDLE) || bailC'cannot close $CHATNAME: $!");
Функции seek, truncate и tell —встроенные Perl-функции, описания которых вы найдете в любом справочнике по языку Perl. Здесь seek переставляет указатель позиции в начало файла, truncate усекает указанный файл до заданной длины, a tell возвращает текущее смещение указателя позиции в файле по отношению к началу файла. Назначение этих строк программы — сохранить в файле только самые последние записи $maxsave, начиная с той, которая была сделана только что.
Метод save () обеспечивает собственно создание записей. Его можно вызвать здесь как $entry->save, поскольку $entry — это CGI-объект, созданный с помощью CGl->new ().
Формат записи сохраняемого файла выглядит следующим образом (запись завершается знаком "=", стоящим в отдельной строке):
ИМЯ1=ЗНАЧЕНИЕ1 ИМЯ2=ЗНАЧЕНИЕ2 ИМЯЗ=ЗНАЧЕНИЕЗ
Теперь пора возвратить обновленную форму броузеру и его пользователю. (Это будет, конечно, первая форма, которую он видит, в том случае, если он щелкнул на ссылке Please sign our guestbook.) Сначала некоторые предварительные действия:
print hr, start_form; # hr() проводит горизонтальную линию: <HR>
Как мы уже упоминали, CGI.pm позволяет нам использовать либо прямые вызовы функций, либо вызовы методов через CGI-объект. В нашей программе для создания базового HTML-кода мы обратились к простым вызовам функций, а для задания полей ввода формы продолжаем пользоваться методами объектов:
print pC'Name:", $cur->textfield( -NAME => "name")) ;
print p("Message:" $cur->textfield(
-NAME => "message",
-OVERRIDE => 1, # стирает предыдущее сообщение
-SIZE => 50)) ;
print p(submit("send"), reset("clear"));
print end_form, hr;
* Фактически она представляет собой glob-ссылку, а не ссылку на дескриптор файла, но в данном случае это почти то же самое.
Метод textfieid() возвращает поле ввода текста для нашей формы. Первый из двух приведенных выше вызовов генерирует HTML-код поля ввода текста с HTML-атрибутом, NAME="name", а второй — создает поле с атрибутом NAME="message" .
Компоненты формы, создаваемые модулем CGI.pm, по умолчанию устойчивы: они сохраняют свои значения до следующего вызова. (Но лишь в течение одного "сеанса" работы с формой, считая с того момента, когда пользователь щелкнул на ссылке Please sign our guestbook.) Это значит, что поле name = "name", созданное в результате первого вызова textfield(), будет содержать значение имени пользователя, если он уже хотя бы один раз в этом сеансе заполнял и передавал форму. Таким образом, поле ввода, которое мы сейчас создаем, будет иметь следующие HTML-атрибуты:
NAME="name" VALUE="Sam Smith"
Совсем другое дело — второй вызов text field (). Мы не хотим, чтобы поле сообщения содержало значение старого сообщения. Поэтому пара аргументов -override => 1 указывает: "Выбросить предыдущее значение этого текстового поля и восстановить значение по умолчанию". Пара аргументов -size => 50 задает размер (в символах) отображаемого поля ввода. Помимо показанных здесь, могут быть и другие необязательные пары аргументов: -DEFAULT => 'начальное значение' И -MAXLENGTH => п, где n — максимальное число символов, которое может принять данное поле.
Наконец, к удовольствию пользователя, мы выводим текущий перечень сохраняемых сообщений, включающий, естественно, то, которое он только что передал:
print h2("Prior Messages");
foreach $entry (Sentries) {
printf("%s [%s]: %s",
$entry->param("date"),
$entry->param("name"),
$entry->param("message"));
print br ();
} print end_html;
Как вы, без сомнения, догадываетесь, функция h2 задает HTML-заголовок второго уровня. В остальной части кода мы просто последовательно формируем текущий список сохраняемых записей (это тот же список, который мы ранее записали в сохраняемый файл), выводя из каждой дату, имя и сообщение.
Пользователи могут работать с этой формой гостевой книги, непрерывно набирая сообщения и нажимая кнопку передачи. Это имитирует электронную доску объявлений, позволяя пользователям видеть новые сообщения друг друга сразу же после их передачи. Общаясь друг с другом подобным образом, пользователи многократно вызывают одну и ту же CGI-программу;
предыдущие значения компонентов формы автоматически сохраняются до следующего вызова. Это особенно удобно при создании многоступенчатых форм — например, тех, которые используются в так называемых приложениях "тележек для покупок", когда вы, перемещаясь по виртуальному магазину, последовательно "делаете покупки" и форма все их запоминает.
Поиск и устранение ошибок в CGI-программах
CGI-программы, запускаемые с Web-сервера, функционируют в совершенно иной среде, нежели та, в которой они работают при вызове из командной строки. Конечно, вы всегда должны добиваться, чтобы ваша CGI-программа нормально работала при вызове ее из командной строки*, но это не гарантирует нормальную работу программы при вызове с Web-сервера.
Помогут вам разобраться в этом сборник часто задаваемых вопросов по CGI и хорошая книга по CGI-программированию. Некоторые из источников перечислены в конце главы.
Вот краткий перечень проблем, часто встречающихся в CGI-программи-ровании. При возникновении почти каждой из них выдается раздражающе-бесполезное сообщение 500 Server Error, с которым вы вскоре познакомитесь и которое возненавидите.
• Если, посылая HTML-код в броузер, вы забыли о пустой строке между HTTP-заголовком (т.е. строкой Content-type) и телом, этот HTML- код работать не будет. Помните, что перед тем, как делать все остальное, нужно ввести соответствующую строку Content-Type (и, возможно, другие HTTP-заголовки) и совершенно пустую строку.
• Серверу необходим доступ к сценарию с правом чтения и выполнения, поэтому он, как правило, должен иметь режим 0555, а лучше 0755. (Это характерно для UNIX.)
• Каталог, в котором находится сценарий, сам должен быть выполняемым, поэтому присвойте ему режим доступа 0111, а лучше 0755. (Это характерно для UNIX.)
• Сценарий должен быть инсталлирован в соответствующем конфигурации вашего сервера каталоге. В некоторых системах, например, это может быть /usr/etc/httpd/cgi-Ып.
Советы по отладке в режиме командной строки приведены в документации на CGI.pm.
• Возможно, в имя файла вашего сценария понадобится вводить определенное расширение, например cgiwivipl. Мы возражаем против подобной настройки WWW-сервера, предпочитая устанавливать разрешение на выполнение CGI для каждого каталога отдельно, но в некоторых конфигурациях она может быть необходима. Автоматически позволять, чтобы все заканчивающиеся на .cgi файлы были исполняемыми, рискованно, если FTP-клиентам разрешается производить запись во всех каталогах, а также при "зеркальном" копировании чужой структуры каталогов. В обоих случаях выполняемые программы могут внезапно появиться у вас на сервере без ведома и разрешения Web-мастера. Это означает также, что все файлы, имена которых имеют расширение cgi или р1, нельзя будет вызывать через обычный URL. Данный эффект может иметь самые разные последствия — от нежелательных до катастрофических.
• Помните: расширение р1 означает, что это Perl-библиотека, а не исполняемый Perl-файл. Не путайте эти понятия, иначе вашей судьбе не позавидуешь. Если у вас безусловно должно быть уникальное расширение для разрешения выполнения Perl-программ (потому что ваша операционная система просто не настольно умна, чтобы использовать нечто вроде записи #!/usr/bin/perl), мы предлагаем использовать расширение^. Это, однако, не избавит вас от других только что упомянутых нами проблем.
• Конфигурация вашего сервера требует особого разрешения на выполнение CGI для каталога, в который вы поместили свой CGI-сценарий. Убедитесь в том, что разрешены и GET, и POST. (Ваш Web-мастер знает, что это значит.)
• Web-сервер не выполняет ваш сценарий при запуске с вашим идентификатором пользователя. Убедитесь в том, что файлы и каталоги, к которым обращается сценарий, открыты для всех пользователей, для которых Web-сервер выполняет сценарии, таких как, например, nobody, wwwuser или httpd. Возможно, вам понадобится заранее создать такие файлы и каталоги и присвоить им самые широкие права доступа для записи. При работе в среде UNIX это делается посредством команды chmod a+w. Предоставляя широкий доступ к файлам, всегда будьте настороже.
• Всегда запускайте свой сценарий с Perl-флагом -w, чтобы иметь возможность получать предупреждения. Они направляются в файл регистрации ошибок Web-сервера, который содержит сообщения об ошибках и предупреждения, выдаваемые вашим сценарием. Узнайте у своего Web-мастера путь к этому файлу и проверяйте его на предмет наличия проблем. О том, как лучше обрабатывать ошибки, можно узнать и из описания стандартного модуля CGLCarp.
• Убедитесь, что версии программ и пути к каталогам с Perl и всем используемым вами библиотекам (вроде CGI.pm) на компьютере, где работает Web-сервер, соответствуют ожидаемым.
• В начале своего сценария включите режим autoflush для дескриптора файла stdout, присвоив переменной $ | значение "истина", например 1. Если вы применили модуль FileHandle или любой из модулей ввода-вывода (скажем, IO::File, IO::Socket и т.д.), то можете использовать с этим дескриптором файла метод, имя которого легко запомнить: auto-flush ():
use FileHandle;
STDOUT->autoflush(l) ;
• Проверяйте возвращаемое значение каждого системного вызова, который производит ваша программа, и в случае неудачного исхода выполняйте соответствующее действие.
Perl и Web: не только CGI-программирование
Perl используется не только в CGI-программировании. Среди других направлений его применения — анализ файлов регистрации, управление встроенными функциями и паролями, "активными" изображениями, манипулирование изображениями*. И все это — лишь верхушка айсберга.
Специализированные издательские системы
Коммерческие издательские Web- системы могут сделать трудные вещи легкими, особенно для непрограммистов, но они не столь гибки, как настоящие языки программирования. Без исходного кода в руках вы всегда ограничены чьими-то решениями: если что-то работает не так, как вам хотелось бы, ничего сделать уже нельзя. Независимо от того, сколько великолепных программ предлагается потребителю на рынке, программист всегда будет нужен для решения тех особых задач, которые выходят за рамки стандартных требований. И, конечно, прежде всего кто-то должен писать ПО издательских систем.
Perl — идеальный язык для создания специализированных издательских систем, приспособленных под ваши уникальные потребности. С его помощью можно одним махом преобразовать необработанные данные в мириады HTML-страниц. Perl применяется для формирования и сопровождения узлов по всей World Wide Web. The Perl Journal (www.tpj.com) использует Perl для создания всех своих страниц. Perl Language Home Page (www.perl.com) содержит около 10000 Web-страниц, которые автоматически сопровождаются и обновляются различными Perl-программами.
* Perl-интерфейс к графической библиотеке gd Томаса Баутелла содержится в модуле GD.pm, который можно найти в CPAN.
Встроенный Perl
Самый быстрый, самый дешевый (дешевле бесплатного уже ничего быть не может) и самый популярный Web-сервер в Internet, Apache, может работать с встроенным в него Perl, используя модуль mod_perl из CPAN. С этим модулем Perl становится языком программирования для вашего Web-сервера. Вы можете писать маленькие Perl-программы для обработки запросов проверки полномочий, обработки сообщений об ошибках, проведения регистрации и решения любых других задач. Они не требуют запуска нового процесса, потому что Perl теперь встроен в Web-сервер. Еще более привлекателен для многих тот факт, что при работе с Apache вам не нужно запускать новый процесс всякий раз, когда поступает CGI-запрос. Вместо этого создается новый поток, который и выполняет предкомпилированную Perl-программу. Это значительно ускоряет выполнение ваших CGI-программ; обычно работа замедляется из-за вызовов fork/exec, а не из-за большого объема самой программы.
Другой способ ускорить выполнение CGI — использовать стандартный модуль CGI::Fast. В отличие от описанного выше встроенного интерпретатора Perl, такая схема выполнения CGI не требует наличия Web-сервера Apache. Подробности см. на man-странице модуля CGI::Fast.
Если Web-сервер у вас работает под Windows NT, вам определенно следует посетить Web-сервер ActiveWare, www.acftveware.cow. Там можно найти не только готовые двоичные файлы Perl для Windows-платформ*, но и PerlScript и PerlIS. Пакет PerlScript — это механизм разработки сценариев ActiveX, который позволяет встраивать Perl-код в ваши Web-страницы так, как это делается средствами JavaScript и VBScript. Пакет PerlIS — это динамически связываемая библиотека интерфейса ISAPI, которая выполняет Perl-сценарии непосредственно из IIS и других ISAPI-совместимых Web-серверов, давая значительный выигрыш в производительности.
Автоматизация работы в Web с помощью LWP
Ставили ли вы когда-нибудь перед собой задачу проверить Web-документ на предмет наличия "мертвых" ссылок, найти его название или выяснить, какие из его ссылок обновлялись с прошлого четверга? Может быть, вы хотели загрузить изображения, которые содержатся в каком-либо документе, или зеркально скопировать целый каталог? Что будет, если вам придется проходить через proxy-сервер или проводить переадресацию?
* Стандартный дистрибутив Perl версии 5.004 предусматривает инсталляцию под Windows, при этом предполагается, что у вас есть компилятор С (и это предположение, как правило, оправдывается).
Сейчас вы могли бы сделать все это вручную с помощью броузера, но, поскольку графические интерфейсы совершенно не приспособлены для автоматизации программирования, это был бы медленный и утомительный процесс, требующий большего терпения и меньшей лености*, чем присущи большинству из нас.
Модули LWP (Library for WWW access in Perl — библиотека для доступа к WWW на Perl) из CPAN решают за вас все эти задачи — и даже больше. Например, обращение в сценарии к Web-документу с помощью этих модулей осуществляется настолько просто, что его можно выполнить с помощью одностроковой программы. Чтобы, к примеру, получить документ /perl/in-dex.html с узла www.perl.com, введите следующую строку в свой shell или интерпретатор команд:
peri -MLWP::Simple -e "getprint 'http://www.perl.com/perl/index.html'"
За исключением модуля LWP: :Simple, большинство модулей комплекта LWP в значительной степени объектно-ориентированы. Вот, например, крошечная программа, которая получает URL как аргументы и выдает их названия:
#!/usr/local/bin/peri use LWP;
$browser = LWP::UserAgent->new(); # создать виртуальный броузер $browser->agent("Mothra/126-Paladium:); # дать ему имя foreeach $url (@ARGV) ( # ожидать URL как аргументы
# сделать GET-запрос по URL через виртуальный броузер
$webdoc = $browser->request(HTTP::Request->new(GET => $url));
if($webdoc->is success) ( # нашли
print STDOUT "$url: :, $result->title, "\n";
} else { # что-то не так
print STDERR "$0: Couldn't fetch $url\n";
” }
Как видите, усилия, потраченные на изучение объектов Perl, не пропали даром. Но не забывайте, что, как и модуль CGI.pm, модули LWP скрывают большую часть сложной работы.
Этот сценарий работает так. Сначала создается объект — пользовательский агент (нечто вроде автоматизированного виртуального броузера). Этот объект используется для выдачи запросов на удаленные серверы. Дадим нашему виртуальному броузеру какое-нибудь глупое имя, просто чтобы сделать файлы регистрации пользователей более интересными. Затем получим удаленный документ, направив HTTP-запрос GET на удаленный сервер. Если результат успешный, выведем на экран URL и имя сервера; в противном случае немножко поплачем.
* Помните, что по Ларри Уоллу три главных достоинства программиста есть Леность, Нетерпение и Гордость.
Вот программа, которая выводит рассортированный список уникальных ссылок и изображений, содержащихся в URL, переданных в виде аргументов командной строки.
#!/usr/local/bin/peri -w use strict;
use LWP 5.000;
use URI::URL;
use HTML::LinkExtor;
my($url, $browser, %saw);
$browser ” LPW::UserAgent->new(); # создать виртуальный броузер fо reach $url ( @ARGV ) (
# выбрать документ через виртуальный броузер
my $webdoc = $browser->request (HTTP: :Request->new (GET => $url).);
next unless $webdoc->is_success;
next unless $webdoc->content_type eq 'text/html';
# не могу разобрать GIF-файлы
my $base = $webdoc->base;
# теперь извлечь все ссылки типа <А ...> и <IMG ..•> foreach (HTML::LinkExtor->new->parse($webdoc->content)->eof->links)( my($tag, %links) = @$_;
next unless $tag eq "a" or $tag eq "img";
my $link;
foreach $link (values %links) (
$saw{ uri($link,$base)->abs->as_string }++;
} } ) print join("\n",sort keys %saw), "\n";
На первый взгляд все кажется очень сложным, но вызвано это, скорее всего, недостаточно четким пониманием того, как работают различные объекты и их методы. Мы не собираемся здесь давать пояснения по этим вопросам, потому что книга и так получилась уже достаточно объемной. К счастью, в LWP можно найти обширную документацию и примеры.
Дополнительная литература
Естественно, о модулях, ссылках, объектах и 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.
Упражнение
Ответ см. в приложении А.
1. Напишите программу для создания формы, содержащей два поля ввода, которые при передаче данных формы объединяются.
2. Напишите CGI-сценарий, который определяет тип броузера, делающего запрос, и сообщает что-нибудь в ответ. (Совет: воспользуйтесь переменной среды HTTP_USER_AGENT.)
| Назад | Вперед |
| Содержание | Предисловие | Введение | Ссылки | Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10 | Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19 | Приложение А | Приложение Б | Приложение В | Приложение Г | |
Скалярные данные
Что такое скалярные данные
Скаляр — это простейший вид данных, которыми манипулирует 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 |
Скалярные операции
Оператор обозначает проведение определенной операции, благодаря которой создается новое значение (результат) из одного или нескольких других значений (операндов). Например, символ + обозначает операцию, потому что при его использовании берутся два числа (операнда, например, 5 и 6) и создается новое значение (11, результат).
Вообще говоря, операции и выражения Perl представляют собой надмножество операций и выражений, имеющихся в большинстве других АЛГОЛ-и Паскаль-подобных языков программирования (вроде С или Java). При выполнении операции предполагается наличие или числовых, или строковых операндов (либо сочетания операндов этих типов). Если вы введете строковый операнд там, где должно быть число, или наоборот. Perl автоматически преобразует этот операнд, используя весьма нечетко сформулированные правила, которые мы подробно рассмотрим ниже в разделе "Преобразование чисел в строки и обратно".
Операции над числами
В Perl применяются обычные операции сложения, вычитания, умножения, деления и т.д. Например:
2+3 #2 плюс 3, или 5 5.1-2.4 #5,1 минус 2,4, или приблизительно 2,7 3 * 12 #3 умножить на 12 = 36 14/2 #14 делить на 2, или 7 10.2/0.3 # 10,2 делить на 0,3, или приблизительно 34 10/3 # всегда означает деление чисел с плавающей запятой, # поэтому результат приблизительно равен 3,3333333...
Кроме того, в Perl используется ФОРТРАН-подобная операция возведения в степень, по которой многие тоскуют в Паскале и С. Эта операция обозначается двумя звездочками, например 2**3 равно двум в степени три, или восьми. (Если этот результат "не помещается" в число с плавающей запятой двойной точности, например, отрицательное число возводится в нецелую степень или большое число возводится в большую степень, выдается сообщение об ошибке — fatal error)
Perl поддерживает также операцию деления с остатком. Значение выражения 10 % 3 — остаток от деления 10 на 3, или 1. Оба значения сначала сокращаются до целых, т.е. 10.5 % 3.2 вычисляется как 10 % 3.
Операции логического сравнения следующие: <, <=, ===, >==, >, !=. Эти операции сравнивают два значения в числовом формате и возвращают значение "истина" (true) или "ложь" (false). Например, операция 3 > 2 возвращает значение "истина", потому что три больше, чем два, тогда как операция 5 != 5 возвращает "ложь", потому что утверждение, что пять не равно пяти — неправда. Определение значений "истина" и "ложь" рассматривается позже, а пока можно считать, что "истина" — это единица, а "ложь" — нуль. (Вы еще увидите эти операции в табл. 2.2.)
Вас, вероятно, удивило слово "приблизительно" в комментариях к примерам, которые мы привели в начале этого раздела. Разве при вычитании 2, 4 из 5,1 не получается точно 2, 7? На уроке математики, может быть, и получается, но в компьютерах, как правило, — нет. В вычислительной технике получается приближенное значение, которое точно лишь до определенного числа десятичных разрядов. Числа в компьютерах хранятся не так, как их представляет себе математик. Впрочем, если вы не делаете чего-нибудь сверхсложного, то, как правило, увидите именно те результаты, которых ожидаете.
Сравнивая приведенные ниже операторы, вы увидите, что в действительности компьютер получил в результате вышеупомянутого вычитания (функция print f описывается в главе б):
printf ("%.51\n", 5.1 - 2.4)
# 2.699999999999999733546474089962430298328399658203125
print(5.1 - 2.4, "\n") ;
# 2.7
Не обращайте на это особого внимания: стандартный формат функции print для вывода на экран чисел с плавающей запятой обычно скрывает такие незначительные неточности представления. Если это создает какую-то проблему, следует воспользоваться объектными модулями Math::BigInt и Math::BigFloat — в них реализована арифметика с бесконечной точностью для целых и чисел с плавающей запятой, только выполняются эти операции несколько медленнее. Подробности вы найдете в главе 7 книги Programming Perl и в интерактивной (сетевой) документации на эти модули.
Операции над строками
Строковые значения можно конкатенировать операцией ".". (Да, это именно одна точка.) Данная операция изменяет строки-операнды не более, чем операция 2+з изменяет 2 или 3. Строку-результат (более длинную) можно использовать в дальнейших вычислениях или сохранить в переменной.
"hello" . "world" # то же самое, что "helloworld" •hello world' . "\n" # то же самое, что "hello world\n" "fred" . " " . "barney" # то же самое, что "fred barney"
Отметим, что конкатенацию нужно задавать явно при помощи знака ".", а не просто располагать два значения рядом друг с другом.
Еще одна группа операций над строками -— операции сравнения. Эти операции похожи на соответствующие операции ФОРТРАНа, например it (меньше чем) и т.д. Операции сравнения строк сравнивают ASCII-значения символов строк обычным способом. Полный набор операций сравнения (как для чисел, так и для строк) приведен в таблице 2.2.
Таблица 2.2. Операции сравнения чисел и строк
Сравнение | Числовое | Строковое |
Равно | == | eq |
Не равно | ! = | пе |
Меньше чем | < | It |
Больше чем | > | gt |
Меньше чем или равно | <= | le |
Больше чем или равно | >== | ge |
Если вы уже имеете опыт программирования на shell в среде UNIX, вы, возможно, заметили, что эти числовые и строковые операции сравнения приблизительно противоположны тому, что подразумевается под ними в UNIX-команде test. В частности, для числового сравнения используется eq, а для строкового применяется знак =.
Есть еще одна строковая операция — операция повторения строки, знак которой — строчная буква х. В результате выполнения этой операции возвращается столько конкатенированных копий левого операнда (строки), сколько указано в правом операнде (числе). Например:
"fred" х 3 # "fredfredfred" "barney" х (4+1) # "barney" x 5, или
# "barneybarneybarneybarneybarney" (3+2) х 4 #5х4, или "5" х 4, т.е. "5555"
Последний пример стоит объяснить подробнее. Круглые скобки указывают на то, что эту часть выражения нужно вычислить первой. (Круглые скобки здесь работают так же, как в обычной математике.) Однако в операции повторения строки левый операнд должен быть строкой, поэтому число 5 преобразуется в односимвольную строку "5" (правила этого преобразования мы рассмотрим позднее). Эта строка копируется четыре раза, что в итоге дает четырехсимвольную строку 5555. Если бы мы поменяли операнды местами, то у нас было бы пять копий строки 4, т.е. 44444. Это показывает, что повторение строки — некоммутативная операция.
При необходимости число копий (правый операнд) сначала усекается до целого значения (4, 8 превращается в 4). Если указать число копий, меньшее единицы, в результате выполнения операции получится пустая строка (строка нулевой длины).
Приоритет и ассоциативность операций
Разным операциям присваивается разный приоритет Это позволяет избежать неоднозначности такого рода, когда две операции пытаются манипулировать тремя операндами. Например, что выполнять в первую очередь в выражении 2+3*4 — сложение или умножение? Если сначала выполнить сложение, мы получим 5*4, или 20. Если же сначала выполнить умножение (как нас учили на уроках математики), то мы получим 2+12, или 14. К счастью, в Perl выбран стандартный математический вариант, т.е. умножение выполняется первым. Поэтому мы говорим, что умножение имеет более высокий приоритет, чем сложение.
Порядок выполнения операций, определяемый приоритетом, можно изменить с помощью круглых скобок. Выражение, заключенное в скобки, вычисляется в первую очередь, и лишь затем выполняется операция, стоящая за скобками (так, как нас учили на уроках математики). Следовательно, если бы нам захотелось выполнить сложение до умножения, необходимо было бы записать (2+3)*4, что дало бы в результате 20. Если вы хотите напомнить, что умножение выполняется перед сложением, можете добавить "декоративные", функционально бесполезные круглые скобки: 2+ (3*4).
При выполнении операций сложения и умножения* очередность их выполнения определяется достаточно просто, но, скажем, при конкатенации строк в сочетании с возведением в степень могут возникнуть проблемы. Единственно верный путь разрешить их —- обратиться к официальной, не предполагающей никаких исключений, таблице приоритетов Perl-операций. (Это табл. 2.3. Отметим, что некоторые из операций нами еще не описаны;
более того, они могут так и не появиться на страницах этой книги. Пусть, однако, этот факт не отпугнет вас.) Операции, аналогичные операциям С, имеют тот же приоритет, что и в этом языке.
Таблица 2.3. Ассоциативность и приоритет операций: от высокого к низкому
Ассоциативность | Операция |
Слева | Операции над списками (справа налево) |
Слева | -> (вызов метода, разыменование) |
Неассоциативные | ++ ~ (автоинкремент, автодекремент) |
Справа | ** (возведение в степень) |
Справа | ! ~ \ + - (логическое отрицание, побитовое отри |
цание, операция ссылки, унарное сложение, унарное вычитание) | |
Слева | =~ !~ (совпадает, не совпадает) |
Слева | * / % х (умножение, деление, деление с остатком, повторение строки) |
Слева | + - . (сложение, вычитание, конкатенация строк) |
Неассоциативные | Именованные унарные операции (например, chomp) |
Слева | & (побитовое И) |
Слева | | А (побитовое ИЛИ, побитовое исключающее ИЛИ) |
Слева |
&& (логическое И) |
Слева | 1 | (логическое ИЛИ) |
Неассоциативные | . . ... (не включающие или включающие граничные значения диапазоны) |
Справа | ?: (операция выбора, if-then-else) |
Справа | = += -= *= и т.д. (присваивание и присваивание с вычислением) |
Слева | , => (запятая и запятая-стрелка) |
Неассоциативные | Операции над списками (слева направо) |
Справа | not (логическое НЕ) |
Слева | and (логическое И) |
Слева | or xor (логическое ИЛИ, логическое исключающее ИЛИ) |
В этой таблице каждая операция имеет более высокий приоритет, чем перечисленные за ней, и более низкий приоритет, чем перечисленные перед ней.
Операции одного уровня приоритетов разрешаются в соответствии с правилами ассоциативности. Как и приоритет, ассоциативность определяет порядок выполнения операций, если две операции с одинаковым приоритетом пытаются манипулировать тремя операндами:
2 ** з ** 4 # 2 ** (3 ** 4) , или 2 ** 81, или около 2.41е24 72/12/3 # (72 / 12) / 3, или 6/3, или 2 30 / 6 * 3 # (30/6) *3, или 15
В первом случае операция ** имеет ассоциативность справа, поэтому круглые скобки подразумеваются справа. Операции * и / имеют ассоциативность слева, поэтому круглые скобки подразумеваются слева.
Преобразование чисел в строки и обратно
Если строковое значение используется как операнд в операции с числами (например, в операции сложения), Perl автоматически преобразует эту строку в эквивалентное числовое значение, как если бы оно было введено в виде десятичного числа с плавающей запятой*. Нечисловые окончания и начальные пробельные символы игнорируются, поэтому " 123. 4 5 f red" (с начальным пробелом) без какого-либо предупреждения преобразуется в 123. 45**. Самый крайний из подобных случаев — когда нечто, не являющееся числом, вообще без предупреждения преобразуется в нуль (как строка fred, которую мы использовали здесь как число).
Если же, наоборот, там, где должно быть строковое значение, вводится числовое (например, в операции конкатенации строк), это числовое значение конвертируется в строку, которая была бы выведена на экран вместо него. Например, если вы хотите конкатенировать строку х с результатом умножения 4 на 5, это можно записать как
"X" .(4*5) f то же самое, что "X" . 20, или "Х20"
(Помните, что круглые скобки заставляют сначала выполнить 4*5, а затем выполнять операцию конкатенации.)
Другими словами, вам не нужно (в большинстве случаев) беспокоиться о том, что указано в качестве операнда — строка или число, поскольку Perl выполняет все необходимые преобразования самостоятельно.
* Шестнадцатеричные и восьмеричные значения этому автоматическому преобразованию не подлежат. Для интерпретации таких значений следует использовать функции hex и oct.
** Если в командной строке не указана опция -w. В целях обеспечения безопасности всегда задавайте эту опцию.
Скалярные переменные
Имя переменной — это имя контейнера, который содержит одно или более значений. Имя переменной остается постоянным на протяжении всей программы, но значение (или значения), содержащееся в этой переменной, как правило, в ходе выполнения программы непрерывно изменяется.
Скалярная переменная содержит одно скалярное значение (представляющее собой число, строку или ссылку). Имена скалярных переменных состоят из знака доллара и буквы, за которой могут следовать еще несколько букв, цифр и знаков подчеркивания*. Строчные и прописные буквы различаются:
переменная $А и переменная $а — разные. Все буквы, цифры и знаки подчеркивания в имени переменной имеют значение. Так, переменная
$a_very_long_variable_that_ends_in_l
отличается от переменной
$a_very_long_variable_that_ends_in_2
Имена переменных следует, как правило, подбирать так, чтобы они имели какой-то смысл в связи со значением переменной. Например, имя $xyzl23, вероятно, не очень описательно, в отличие от $line_length.
Скалярные операции и функции
Самая распространенная операция, выполняемая над скалярной переменной — присваивание, которое представляет собой способ задания значения этой переменной. Операция присваивания в Perl обозначается знаком равенства (почти как в С и ФОРТРАНе). С левой стороны ставится имя переменной, с правой — присваиваемое ей значение или выражение, при помощи которого это значение вычисляется, например:
$а =17; # присвоить переменной $а значение 17
$b = $а + 3; # присвоить $Ь текущее значение $а плюс 3 (20)
$b == $b * 2; # присвоить $b значение $b, умноженное на 2 (40)
Обратите внимание: в последней строке переменная $Ь используется дважды: один раз для получения значения (в правой части), а второй — для указания, куда поместить вычисленное выражение (в левой части). Это допустимый, надежный и довольно распространенный прием. Распространен он настолько, что через минуту мы увидим, как все это можно записать в сокращенном виде.
Возможно, вы заметили, что скалярные переменные всегда предваряются знаком $. В shell знак $ используют для получения значения, а при присваивании нового значения его не указывают. В Java и С этот символ
* Их количество ограничено числом 255. Надеемся, этого достаточно. 2. Скалярные данные
В этой таблице каждая операция имеет более высокий приоритет, чем перечисленные за ней, и более низкий приоритет, чем перечисленные перед ней.
Операции одного уровня приоритетов разрешаются в соответствии с правилами ассоциативности. Как и приоритет, ассоциативность определяет порядок выполнения операций, если две операции с одинаковым приоритетом пытаются манипулировать тремя операндами:
2 ** 3 ** 4 # 2 ** (3 ** 4) , или 2 ** 81, или около 2.41е24 72/12/3 # (72 / 12) / 3, или 6/3, или 2 30 / 6 * 3 # (30/6)*3, или 15
В первом случае операция ** имеет ассоциативность справа, поэтому круглые скобки подразумеваются справа. Операции * и / имеют ассоциативность слева, поэтому круглые скобки подразумеваются слева.
Преобразование чисел в строки и обратно
Если строковое значение используется как операнд в операции с числами (например, в операции сложения), Perl автоматически преобразует эту строку в эквивалентное числовое значение, как если бы оно было введено в виде десятичного числа с плавающей запятой*. Нечисловые окончания и начальные пробельные символы игнорируются, поэтому<< 12 3.4 5 f red" (с начальным пробелом) без какого-либо предупреждения преобразуется в 123. 45**. Самый крайний из подобных случаев — когда нечто, не являющееся числом, вообще без предупреждения преобразуется в нуль (как строка fred, которую мы использовали здесь как число).
Если же, наоборот, там, где должно быть строковое значение, вводится числовое (например, в операции конкатенации строк), это числовое значение конвертируется в строку, которая была бы выведена на экран вместо него. Например, если вы хотите конкатенировать строку х с результатом умножения 4 на 5, это можно записать как
"X" .(4*5) # то же самое, что "X" . 20, или "Х20"
(Помните, что круглые скобки заставляют сначала выполнить 4*5, а затем выполнять операцию конкатенации.)
Другими словами, вам не нужно (в большинстве случаев) беспокоиться о том, что указано в качестве операнда — строка или число, поскольку Perl выполняет все необходимые преобразования самостоятельно.
* Шестнадцатеричные и восьмеричные значения этому автоматическому преобразованию не подлежат. Для интерпретации таких значений следует использовать функции hex и oct.
** Если в командной строке не указана опция -w. В целях обеспечения безопасности всегда задавайте эту опцию.
Скалярные переменные
Имя переменной — это имя контейнера, который содержит одно или более значений. Имя переменной остается постоянным на протяжении всей программы, но значение (или значения), содержащееся в этой переменной, как правило, в ходе выполнения программы непрерывно изменяется.
Скалярная переменная содержит одно скалярное значение (представляющее собой число, строку или ссылку). Имена скалярных переменных состоят из знака доллара и буквы, за которой могут следовать еще несколько букв, цифр и знаков подчеркивания*. Строчные и прописные буквы различаются:
переменная $А и переменная $а — разные. Все буквы, цифры и знаки подчеркивания в имени переменной имеют значение. Так, переменная
$a_very_long_variable_that__end.s__in__l
отличается от переменной
$a_very_long_variable_that__ends_in_2
Имена переменных следует, как правило, подбирать так, чтобы они имели какой-то смысл в связи со значением переменной. Например, имя $xyzl23, вероятно, не очень описательно, в отличие от $line_length.
Скалярные операции и функции
Самая распространенная операция, выполняемая над скалярной переменной — присваивание, которое представляет собой способ задания значения этой переменной. Операция присваивания в Perl обозначается знаком равенства (почти как в С и ФОРТРАНе). С левой стороны ставится имя переменной, с правой — присваиваемое ей значение или выражение, при помощи которого это значение вычисляется, например:
$а =17; # присвоить переменной $а значение 17
$b = $а + 3; # присвоить $Ь текущее значение $а плюс 3 (20)
$b = $b * 2; # присвоить $b значение $b, умноженное на 2 (40)
Обратите внимание: в последней строке переменная $ Ь используется дважды: один раз для получения значения (в правой части), а второй — для указания, куда поместить вычисленное выражение (в левой части). Это допустимый, надежный и довольно распространенный прием. Распространен он настолько, что через минуту мы увидим, как все это можно записать в сокращенном виде.
Возможно, вы заметили, что скалярные переменные всегда предваряются знаком $. В shell знак $ используют для получения значения, а при присваивании нового значения его не указывают. В Java и С этот символ
* Их количество ограничено числом 255. Надеемся, этого достаточно.
вообще опускается. Если вы постоянно присваиваете одной или нескольким переменным новые значения, то неминуемо будете делать ошибки. (Наше решение заключалось в том, чтобы прекратить писать программы на shell, awk и С, но для вас этот путь может быть неприемлем.)
Скалярное присваивание не только является операцией, его можно использовать и в качестве значения, аналогично тому как это делается в С. Другими словами, выражение $а=3 имеет значение, аналогично тому как имеет некоторое значение выражение $а+3. Значением является та величина, которая присваивается, т.е. $а=3 имеет значение 3. Хотя на первый взгляд это может показаться несуразным, использование присваивания как значения полезно, если вы хотите присвоить переменной промежуточное значение в выражении или просто скопировать одно значение в несколько переменных. Например:
$Ь = 4 + ($а =3); # присвоить 3 переменной $а, затем прибавить к 4,
# в результате чего $Ь получит значение 7 $d = ($с = 5); # скопировать 5 в $с, а затем и в $d $d = $с = 5; # то же самое, но без круглых скобок
Последнее выражение работает, потому что присваивание имеет ассоциативность справа.
Операции присваивания с вычислением
Выражения типа $ а == $а + 5 (где переменная стоит по обе стороны от оператора присваивания) встречаются довольно часто, поэтому в Perl применяется сокращенный метод записи операции изменения переменной — операция присваивания с вычислением. Почти все двоичные операции, с помощью которых вычисляются значения, имеют соответствующую форму с добавленным знаком равенства. Например, следующие две строки эквивалентны:
$а = $а + 5; # без операции присваивания с вычислением $а += 5; # с операцией присваивания с вычислением
Эквивалентны и эти строки:
$Ь = $Ь * 3;
$b *= 3;
В каждом из вышеприведенных случаев данная операция вызывает изменение существующего значения переменной определенным способом, а не просто замену этого значения результатом вычисления какого-то нового выражения.
Другой распространенной операцией присваивания является операция конкатенации строк:
$str = $str . " "; # добавить пробел к $str $str .= " "; # то же самое, но с операцией присваивания
Почти все двоичные операции, записанные таким образом, допустимы. Например, операция возведения в степень записывается как * * =. Так, $ а * * = 3 означает "возвести число, содержащееся в переменной $а, в третью степень и поместить результат обратно в $а".
Как и простая операция присваивания, эти операции также могут быть использованы в качестве значения, которым является новое значение переменной. Например:
$а = 3;
$b = ($а += 4); # $а и $b теперь равны 7
Автоинкремент и автодекремент
Казалось бы, для прибавления единицы к $а можно просто записать $а += 1, но Perl идет на шаг дальше и сокращает даже эту запись. Операция ++ (автоинкремент) прибавляет к своему операнду единицу и возвращает ин-крементированное значение, например:
$а += 1; # с операцией присваивания ++$а; # с префиксным автоинкрементом $d = 17;
$е = ++$d; # и $е, и $d теперь равны 18
Здесь операция ++ используется как префиксная, т.е. знак операции стоит слева от операнда. Автоинкремент можно записывать и в суффиксной форме (справа от операнда). В этом случае результатом выражения является старое значение переменной, которое она имела до инкрементирования. Например:
$с = 17;
$d = $с++; # $d равно 17, а $с равно 18
Поскольку значение операнда изменяется, операнд должен быть скалярной переменной, а не просто выражением. Не стоит рассчитывать, что ++16 получит значение 17, и нельзя сделать так, чтобы ++($а+$Ь) каким-то образом стало больше, чем сумма $а и $Ь.
Операция автодекремента (") похожа на операцию автоинкремента, но здесь не прибавляется единица, а вычитается. Как и операция автоинкремента, операция автодекремента имеет две формы — префиксную и суф-фиксную. Например:
$х = 12;
--$х; # $х теперь равно 11
$у = $х"; # $у равно 11, а $х - 10
Операции автоинкремента и автодекремента выполняются и над значениями с плавающей запятой. Автоинкрементирование переменной со значением 4,2 дает, как и следовало ожидать, 5,2*.
* Операция автоинкрементирования выполняется даже над строками. См. по данному вопросу книгу Programming Perl или man-страницу perlop(\).
Функции chop и chomp
Весьма полезной иногда бывает встроенная функция chop. Эта функция принимает один аргумент, указываемый в круглых скобках — имя скалярной переменной — и удаляет из строкового значения этой переменной последний символ. Например:
$х = "hello world";
chop($x); # $x теперь имеет значение "hello worl"
Обратите внимание: значение аргумента здесь меняется, отсюда и требование к наличию скалярной переменной, а не просто скалярного значения. Писать chop (' suey'), чтобы превратить аргумент в 'sue', не имеет смысла, потому что места для хранения этого значения нет. Кроме того, можно ведь и просто написать: ' sue '.
Возвращаемое значение для этой функции — отброшенный символ (в приведенном выше примере с "hello world" это буква d). Следующий код, очевидно, неверен:
$х == chop($x); # НЕВЕРНО: заменяет $х ее последним символом chop($x); # ВЕРНО: как и выше, удаляет последний символ
Если в функции chop задать пустую строку, то она ничего не сделает и ничего не возвратит, не выдавая сообщения об ошибке и вообще никак не реагируя*. Большинство операций в Perl имеют разумные граничные условия; другими словами, вы можете использовать их в пределах накладываемых ограничений и за ними, причем часто без какой-либо реакции с их стороны. Некоторые утверждают, что это один из фундаментальных недостатков Perl, а другие пишут первоклассные программы, нисколько не утруждая себя заботой о соблюдении этих ограничений. Вам решать, к какому лагерю присоединяться.
При усечении уже усеченной строки отбрасывается еще один символ. Например:
$а = "hello world\n";
chop $a; # теперь $а имеет значение "hello world" chop $a; # оп-ля! теперь $а имеет значение "hello worl"
Если вы не уверены в том, есть ли в конце переменной символ новой строки, можете использовать несколько более безопасную операцию chomp, которая удаляет только символ новой строки**, например:
$а == "hello world\n";
chomp ($a); # теперь $а имеет значение "hello world" chomp ($a); # ага! никаких изменений в $а не произошло
* Если вы не используете соответствующий здравому смыслу ключ -w.
** Или иное значение, которое задано переменной $ \ в качестве разделителя входных записей.
Интерполяция скаляров в строках
Если строковый литерал взят в двойные кавычки, в нем необходимо выполнить интерполяцию переменных (помимо проверки на наличие управляющих последовательностей с обратной косой). Это значит, что строка просматривается на предмет наличия имен скалярных переменных* — а именно комбинаций знака доллара с буквами, цифрами и знаками подчеркивания. Если ссылка на переменную имеется, она заменяется текущим значением этой переменной (или пустой строкой, если значение переменной еще не присвоено). Например:
$а = "fred";
$b = "some text $a"; # $b имеет значение "some text fred" $c = "no such variable $what"; # $c имеет значение "no such variable "
Текст, который заменяет переменную, не просматривается, т.е. даже при наличии в нем знаков доллара никакие дальнейшие замены не производятся:
$х = *$fred'; # буквально знак доллара и слово "fred" $у = "hey $х"; # значение — 'hey $fred'; двойной подстановки нет
Если необходимо предотвратить подстановку значения вместо имени переменной, необходимо либо изменить эту часть строки так, чтобы она стояла в одинарных кавычках, либо поставить перед знаком доллара обратную косую, которая отменяет его специальное значение:
$fred = 'hi';
$barney = "a test of " . '$fred'; # буквально: 'a test of $fred' $barney2= "a test of \$fred"; # то же самое
В качестве имени переменной будет взято самое длинное из возможных имен, имеющих смысл в этой части строки. Это может вызвать проблему, если вы хотите сразу же после заменяемого значения дать какой-то постоянный текст, который начинается с буквы, цифры или знака подчеркивания. Проверяя строку на предмет наличия имен переменных, Perl посчитал бы эти символы дополнительными символами имени — а как раз этого вам и не нужно. В Perl имеется разделитель имени переменной. Просто заключите имя переменной в фигурные скобки. Другой вариант — завершить эту часть строки и начать другую часть строки знаком операции конкатенации:
$fred = "pay"; $fredday = "wrong!";
$barney = "It's $fredday"; # не день зарплаты payday, a "It's wrong!" $barney = "It's ${fred}day"; # теперь $barney получает значение "It's payday" $barney2 = "It's $fred"."day"; # еще один способ $barney3 = "It's " . $fred . "day"; # и еще один
* И переменных-массивов, но этот вопрос мы будем рассматривать в главе 3 Массивы и списочные данные.
Для изменения регистра букв, задействованных в интерполяции переменных, можно использовать специально предназначенные для этого строковые управляющие последовательности*. Например:
$bigfred = "\Ufred"; # $bigfred имеет значение "FRED"
$fred = "fred"; $bigfred = "\U$fred"; # то же самое
$capfred = "\u$fred"; # $capfred имеет значение "Fred"
$barney = "\LBARNEY"; # $barney теперь имеет значение "barney"
$capbarney = "\u\LBARNEY"; # $capbarney теперь имеет значение "Barney"
$bigbarney = "BARNEY"; $capbarney = "\u\L$bigbarney"; # то же самое
Как видите, изменяющие регистр строковые управляющие последовательности хранятся до тех пор, пока не окажут свое воздействие, поэтому, несмотря на то, что первая буква слова barney не стоит непосредственно за символами \и, она становится прописной благодаря воздействию \и.
Термин " интерполяция переменных" иногда заменяют термином "интерполяция двойных кавычек", потому что интерполяция выполняется в строках, заключенных в двойные кавычки. (А также в строках, заключенных в обратные кавычки, которые мы рассмотрим в главе 14.)
<STDIN> как скалярное значение
Если вы — типичный хакер, то, вероятно, давно уже задаетесь вопросом:
а как же ввести в Perl-программу какое-либо значение? Вот самый простой способ. Каждый раз, когда там, где требуется скалярное значение, вы используете дескриптор <STDIN>, Perl читает следующую полную строку текста со стандартного ввода (до первого символа новой строки) и использует ее в качестве значения этого дескриптора. Термином "стандартный ввод" может обозначаться многое, но если вы не делаете в своей программе ничего необычного, это означает терминал пользователя, вызвавшего вашу программу (вероятнее всего, ваш собственный терминал). Если строки, которую можно было бы прочитать, еще нет (типичный случай, если только вы заранее не набрали полную строку), Perl-программа останавливается и ждет, пока вы не введете какие-нибудь символы и вслед за ними символ перехода на новую строку.
В конце строкового значения дескриптора <STDIN> обычно стоит символ новой строки. Чаще всего от этого символа нужно избавляться сразу же (ведь между hello Hhello\n — большая разница). Здесь и приходит на помощь знакомая нам функция chomp. Типичная последовательность ввода выглядит примерно так:
$а = <STDIN>; # читаем текст chomp($a); # избавляемся от надоедливого символа новой строки
* Вы, возможно, придете к выводу, что легче воспользоваться функциями
uc, ucfirst, lc И Icfirst.
Общепринятое сокращение этих двух строк выглядит так:
chomp($a = <STDIN>) ;
Присваивание внутри круглых скобок продолжает относиться к $а даже после того, как этой переменной уже присвоено значение. Таким образом, функция chomp оперирует с переменной $а. (Это вообще справедливо для операции присваивания; присваиваемое выражение можно использовать везде, где необходима переменная, а действия относятся к переменной, стоящей слева от знака равенства.)
Вывод с помощью функции print
Итак, мы вводим значения с помощью дескриптора <stdin>. А как их вывести из программы? С помощью функции print. Эта функция принимает значения, стоящие в скобках, и выдает их без всяких украшений на стандартный вывод. Стандартный вывод — это опять-таки ваш терминал (если вы не делаете в программе ничего необычного). Например:
print("hello world\n"); # выводится hello world с символом новой строки print "hello world\n"; # то же самое
Обратите внимание на второй пример, где показана форма функции print без круглых скобок. Использовать скобки или нет — это, главным образом, вопрос стиля и быстроты набора, но есть несколько случаев, где скобки обязательны во избежание двусмысленности.
Мы увидим, что для функции print можно задавать список значений, но поскольку про списки мы еще не говорили, отложим этот вопрос до главы 6.
Значение undef
Что будет, если использовать скалярную переменную до того, как ей присвоено значение? Ничего серьезного, в общем-то, не произойдет. До присваивания значения переменные имеют значение undef. При использовании в качестве числа это значение выглядит как нуль, а при использовании в качестве строки —- как пустая строка нулевой длины. Впрочем, при работе с ключом -w вы получите предупреждение — это хороший способ вылавливания ошибок программирования.
Многие операции возвращают undef, когда их аргументы выходят за пределы диапазона или не имеют смысла. Если вы не делаете ничего особенного, вы получите в подобном случае нуль или пустую строку без серьезных последствий. На практике это едва ли представляет собой проблему.
Одна из уже знакомых нам операций, которая в определенных обстоятельствах возвращает undef — это операция <stdin>. Обычно она возвращает следующую из прочитанных строк; если же строк для чтения больше нет (например, когда вы нажали на терминале клавиши [Ctri+D] или когда в файле больше нет данных), то <stdin> возвращает значение undef. В главе 6 мы посмотрим, как осуществить проверку в данной ситуации и выполнить специальные действия, если данных для чтения больше нет.
Упражнения
Ответы к упражнениям приведены в приложении А.
1. Напишите программу, которая вычисляет длину окружности с радиусом 12, 5. Длина окружности равна 2я (около 2 * 3,141592654) радиусам.
2. Модифицируйте программу из предыдущего упражнения так, чтобы она запрашивала и принимала значение радиуса окружности у пользователя.
3. Напишите программу, которая запрашивает и считывает два числа, после чего выводит на экран результат перемножения этих чисел.
4. Напишите программу, которая считывает строку и число и выводит на экран строку столько раз, сколько задано числом, причем каждый раз с новой строки. (Совет: используйте оператор х.)
| Назад | Вперед |
| Содержание | Предисловие | Введение | Ссылки | Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10 | Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19 | Приложение А | Приложение Б | Приложение В | Приложение Г | |
Сообщения о дефектах
В том невероятном случае, если вы наткнетесь на дефект не в вашей собственной программе, а в самом Perl, постарайтесь проверить его на мини-мальном по объему контрольном примере, а затем документировать с помощью программы perlbug, которая поставляется вместе с исходным кодом Perl.
Телеконференции Usenet
Телеконференции по Perl — неиссякаемый источник информации (прав-да, иногда беспорядочной) о языке. Телеконференция comp.lang.perl.an-nounce — с низким трафиком, используется для публикации объявлений, связанных с Perl. Эти объявления часто касаются выпуска новых версий, исправления ошибок, новых расширений и модулей, часто задаваемых вопросов (FAQ).
В телеконференции сотр.lang.perl.misc, уровень трафика в которой очень высок, обсуждается практически все — от технических вопросов и филосо-фии Perl до Perl-игр и Perl-поэзии. Как и сам Perl, эта телеконференция слывет полезной, и ни один вопрос не считается слишком глупым, чтобы его нельзя было задавать*.
В телеконференции comp.lang.perl.tk обсуждаются вопросы использования популярного набора инструментальных средств Tk, входящего в состав Perl. Телеконференция comp.lang.perl.modules посвящена разработке и использова-нию Perl-модулей — самого лучшего средства получения многократно ис-пользуемого кода. Когда вы будете читать эти строки, возможно, уже появятся и другие телеконференции.
Есть еще одна телеконференция, к материалам которой вы, может быть, захотите обратиться (по крайней мере если занимаетесь CGI-программиро-ванием в Web) — сотр.mfosystems.www.authonng.cgi. Хотя, строго говоря, эта конференция и не посвящена Perl как таковому, большинство обсуждаемых там программ написаны на этом языке. Именно к ее материалам следует обращаться по вопросам, связанным с применением Perl в World Wide Web.
* Разумеется, некоторые вопросы слишком просты, чтобы на них отвечать, особенно те, на которые уже даны ответы в FAQ.
Темы, которых мы не коснулись
Как ни странно, даже при таком объеме книги некоторые вопросы все равно остались незатронутыми. Данное приложение содержит полезную дополнительную информацию.
Назначение этого раздела — не обучить вас тем вещам, которые перечислены здесь, а просто дать их перечень. За дальнейшей информацией обращайтесь к книге Programming Perl, на man-страницы perl(l) и perlfaq(l), к HTML-документам, имеющимся в каталоге doc архива CPAN, и к материалам телеконференций Usenet.
Полное межпроцессное взаимодействие
Да, Perl может оказать значительную помощь в создании сети. Кроме потоковых портов TCP/IP, которые мы рассматривали в приложении В, Perl поддерживает также (если ваша система готова к этому) доменные порты UNIX, передачу сообщений по протоколу UDP, совместно используемую память, семафоры, именованные и неименованные каналы и обработку сигналов. Стандартные модули перечислены в главе 6 книги Programming Perl и на man-странице perlipc(l), а модули разработки третьих фирм — в разделе каталога модулей CPAN, посвященном сетям.
Да, на основе Perl можно организовать сетевой обмен с использованием портов TCP/IP, доменных портов UNIX и обеспечить работу совместно используемой памяти и семафоров в системах, которые поддерживают эти возможности. Дальнейшие сведения см. на man-странице perlipc(l).
Отладчик
В Perl есть чудесный отладчик, работающий на уровне исходного кода. О нем рассказывается на man-странице perldebug(l).
Командная строка
При запуске интерпретатора Perl можно указывать множество ключей командной строки. См. man-страницу perlrun(\').
Другие операции
Помимо упомянутых в книге, используются и другие операции. Например, операция "запятая". Есть также операции манипулирования битами &, I, л и ~, трехместная операция ?\ :, операции . . и ... и другие.
Имеются также вариации операций, например, допускается использование модификатора д в операции сопоставления. Об этом и многом другом рассказывается на man-странице perlop(l).
Другие функции
В Perl очень много функций. Мы не собираемся здесь их все перечислять, потому что самый простой способ узнать о них — это прочитать раздел о функциях в книге Programming Perl и man-страницу perlfunc(l). Вот некоторые из наиболее интересных функций.
Функции grep и тар
Функция grep выбирает элементы из списка аргументов на основании результата выражения, которое многократно проверяется на истинность, при этом переменная $_ последовательно устанавливается равной каждому элементу списка:
@bigpowers = grep $_ > 6, 1, 2, 4, 8, 16; # получает (8, 16) @b_names = grep /^Ъ/, qw(fred barney betty wilma) ;
Stextfiles = grep -T, <*>;
Функция map похожа на grep, но вместо выбора и отклонения элементов из списка аргументов она просто выдает результаты вычисления выражения (вычисляемого в списочном контексте):
@more = map $_ + 3, 3, 5, 7; # получает 6, 8, 10 @square s == map $_ * $_, 1. .10; # квадраты первых 10 чисел @that = map "$_\n", Sthis; # как unchop
Ctriangle - map !..$_, 1..5; # 1,1,2,1,2,3,1,2,3,4,1,2,3,4,5 %sizes = map ( $_, -s ) <*>; # хеш файлов и размеров
Операция eval (и
s///e)
Да, вы можете создать фрагмент кода во время работы программы и выполнить его при помощи функции eval точно так же, как в shell. Это по-настоящему полезная возможность, благодаря которой можно выполнить оптимизацию периода компиляции (например, получить компилированные регулярные выражения) во время выполнения. С ее помощью можно также вылавливать во фрагменте кода ошибки, которые в противном случае будут фатальными: фатальная ошибка внутри eval просто приводит к выходу из этой функции и выдает пользователю код ошибки.
Вот, например, программа, которая читает строку Perl-кода, вводимую пользователем, а затем выполняет ее так, как будто это часть Perl-программы:
print "code line: ";
choptScode = <STDIN” ;
eval $code; die "eval: $@" if $@;
Perl-код можно вставить в заменяющую строку при выполнении операции замены с помощью флага е. Это удобно, если вы хотите включить в заменяющую строку нечто сложное, например, вызов подпрограммы, которая возвращала бы результаты поиска в базе данных. Вот цикл, который инкрементирует значение первой колонки для ряда строк:
while (о) (
s/"
(\S+)/$l+l/e; f $1+ 1 is Perl code, not a string
printf;
}
Функция evai используется также как механизм обработки исключений:
eval {
some_hairy_routine_that_might_die(Oargs);
};
if ($0) (
print "oops... some_hairy died with $@";
1
Здесь массив $@ пуст до тех пор, пока блок eval работает нормально, а в противном случае этот блок выдает "сообщение о смерти".
Из этих трех конструкций (eval "строка", eval { БЛОК } и s///e) только первая действительно соответствует вашим представлениям об eval из shell. Остальные две компилируются "на ходу", что влечет за собой незначительное снижение производительности.
Предопределенные переменные
Вы уже знакомы с несколькими предопределенными переменными, например, с переменной $_. Их гораздо больше. В дело их обозначения вовлечены почти все знаки препинания. Здесь вам поможет man-страница perlvar(\), а также модуль English на странице perlmod(Y).
Обработка таблицы символов с помощью *FRED
Вы можете сделать b псевдонимом для а с помощью операции *Ь = *а. Это значит, что $а и $Ь обозначают одну и ту же переменную, равно как @а и @ь, и даже дескрипторы файлов и форматы а и b. Вы можете также объявить *Ь локальной для данного блока с помощью операции local (*b), что позволит вам иметь локальные дескрипторы файлов, форматы и другие вещи. Это весьма экстравагантно, но иногда бывает весьма полезно.
Дополнительные возможности регулярных выражений
Регулярные выражения могут иметь "расширенный" синтаксис (в котором пробельные символы не обязательны, поэтому регулярное выражение может разбиваться на несколько строк и содержать обычные Perl-комментарии), а также иметь положительное и отрицательное "упреждение". Этот синтаксис кажется несколько уродливым, но не пугайтесь и обратитесь к книге Programming Perl или man-странице perlre(l). Все это и многое другое разъясняется в книге Фридла (Friedl) Mastering Regular Expressions (издательство O'Reilly & Associates).
Пакеты
Если над вашим проектом работает много людей или если вы — большой оригинал, вы можете создать пространство имен переменных с помощью пакетов. Пакет — это просто скрытый префикс, который ставится перед именами большинства переменных (кроме тех, которые создаются операцией ту). Изменяя этот префикс, вы получаете разные переменные. Вот небольшой пример:
$а = 123; # это на самом деле $main::a $main::a++; # та же переменная, теперь 124 package fred; t теперь префикс — fred $а = 456; # это $fred::a
print $a - $roain::a; t выводит 456-124
package main; t возврат к первоначальному значению по умолчанию
print $а + $fred::a; t выводит 124+456
Таким образом, любое имя с явным именем пакета используется как есть, а все остальные имена считаются принадлежащими текущему пакету, который принят по умолчанию. Пакеты локальны для текущего файла или блока, и вы всегда можете использовать пакет main в начале файла. Подробности см. на man-странице perlsub(\).
Встраиваемость и расширяемость
"Внутренности" Perl определены достаточно хорошо для того, чтобы встраивание компилятора-интерпретатора Perl в другое приложение (так, как это уже сделано с Web-сервером Apache и текстовым редактором vi) или расширение Perl путем добавления к нему произвольного кода, написанного на С (или имеющего С-подобный интерфейс), стало относительно несложной задачей. Более того, почти третья часть диалоговой документации на Perl посвящена именно вопросам встраивания и расширения этого языка. Подробно эти аспекты освещены на man-страницах perlembed(l), perlapio(l), perlxs( 1), perlxstut( 1), perlguts( 1) и perlcall( 1).
Поскольку Perl можно получить бесплатно, вы можете написать собственную программу создания электронных таблиц, используя встроенный Perl для вычисления выражений в ее ячейках и не платя ни цента за всю эту мощь. Радуйтесь.
Вопросы безопасности
Perl создавался с учетом требований безопасности. Посмотрите в главе 6 книги Programming Perl или на man-странице perlsec(l) материал о "проверке пороков" (taint checking). "Проверка пороков" — это такой вид защиты, когда вы доверяете автору программы, а не лицу, которое ее выполняет — как это часто бывает в UNIX с программами, в которых применяется идентификатор пользователя, и во всех системах с программами, запускаемыми сервером. Модуль Safe, который описан на man-странице Safe(3) и в главе 7 книги Programming Perl, обеспечивает нечто совершенно иное — защиту, необходимую при выполнении (как в случае с eval) непроверенного кода.
Операторы swithch и case
Нет, таких операторов в Perl нет, но их легко создать с помощью базовых конструкций. См. главу 2 книги Programming Рег1и man-страницу perlfunc(\).
Прямой ввод-вывод: sysopen, sysread, syswrite, sysseek
Иногда средства ввода-вывода Perl оказываются слишком высокоуровневыми для стоящей перед вами задачи. В главе 3 книги Programming Perl и на man-странице perlfunc(l) рассматривается вопрос непосредственного использования базовых системных вызовов для ввода-вывода.
Компилятор Perl
Хотя мы говорим, что Perl компилирует ваш код перед тем, как его выполнять, эта скомпилированная форма не является "родным" объектным кодом. Компилятор Perl, написанный Малькольмом Бити, может создать из вашего Perl-сценария независимый байтовый код или компилируемый С-код. Ожидается, что в версию Perl 5.005 будет включена возможность генерации собственного кода. Подробности см. в материале, представленном на man-странице perlfaq(3).
Поддержка баз данных
Да, Perl может обеспечить непосредственное взаимодействие с коммерческими серверами баз данных, включая Oracle, Sybase, Informix и ODBC, не считая многих других. Соответствующие модули расширения вы найдете в разделе баз данных в каталоге модулей CPAN.
Сложные структуры данных
Используя ссылки, вы можете создавать структуры данных произвольной степени сложности. Вопросы их разработки рассматриваются в главе 4 книги Programming Perl и на man-страницах perllol(l), perldsc(l) и perlref(l). Если вы предпочитаете объектно-ориентированную структуру данных, обратитесь к главе 5 вышеупомянутой книги и man-страницам perltoot(\) и perlobj(l).
Указатели на функции
Perl может сохранять и передавать указатели на функции посредством записи вида \&funcname, а также вызывать их косвенно посредством записи &$funcptr ($args). Можно даже писать функции, которые создают и возвращают новые анонимные функции, как в языках Lisp и Scheme. Такие анонимные функции часто называют замыканиями (closures). Подробности см. в главе 2 книги Programming Perl и на man-страницах perlsub(l) и perlfaq7(l).
И прочее
Perl с каждым днем становится все более мощным и полезным, поэтому оперативное обновление посвященной ему документации — довольно сложная задача. (Кто знает, может быть, к дню появления этой книги на полках магазинов уже будет создан Visual Perl?) В любом случае — спасибо, Ларри!
| Назад |
| Содержание | Предисловие | Введение | Ссылки | Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10 | Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19 | Приложение А | Приложение Б | Приложение В | Приложение Г | |
Управление процессами
Использование функций system u ехес
Когда вы из командной строки shell задаєте выполнение какой-либо команды, он обычно создает новый процесе. Зтот новый процесе становится порожденным процессом shell, выполняется независимо, но в координации с последним.
Аналогичным образом Perl-программа в состоянии запускать новые процессы и может делать зто, как и большинство других операций, несколь-кими способами.
Самий простой способ запуска нового процесса — использовать для зтого функцию system. В простейшей форме зта функция передает в совершенно новый shell /bin/sh одну строку, которая будет выполняться как команда. По выполнении команды функция system возвращает код завершення данной команды (если все было нормально — зто, как правило, 0). Вот пример того, как Perl-программа выполняет команду date c помощью shell*:
system("date");
Здесь мы не проверяем возвращаемое значение, но неудачный исход выполнения команд н date вряд ли возможен.
Куда идет результат зтой команди? Откуда поступают исходные данные, если они нужны команде? Хорошие вопросы, и ответы на них позволят узнать, чем отличаются различные способы создания процессов.
* В данном случае shell фактически не используется, поскольку Рсгі сам выполняет операций shell, если командная строка достаточно проста, как в данном случае.
Три стандартних файла для функции system (стандартний ввод, стандартний внвод и стандартний вивод ошибок) наследуются от Perl-процесса. Таким образом, результат выполнения команди dale в приведенном више примере направляется туда же, куда поступает результат выполнения функции print stdout — скореє всего, на дисплей визвавшего ее пользователя. Поскольку ви запускаєте shell, то можете переадресовать стандартний вивод, пользуясь обычными для /Ып/sh операциями переадресации. Например, чтобы направить результати работи команди date в файл right_now, нужно сделать что-то вроде зтого:
system("date >right_now") && die "cannot create right_now";
На зтот раз ми не только посилаєм результат команди date в файл, внполняя переадресацию в shell, но и проверяем статус возврата. Если статус возврата — значение "истина" (не нуль), зто значит, что с командой shell что-то произошло, и функция die внполнит свою миссию. Данное правило обратно обичним правилам выполнения операций в Perl: ненулевое возвра-щаемое значение операций system, как правило, указивает на какую-то ошибку.
Аргументом функции system может бить все, что пригодно для передачи в /Ып/sh, позтому можно задавать сразу несколько команд, разделяя их точками с запятой или символами новой строки. Процесси, после которих указан символ &, запускаются, но программа не ждет их завершення, т.е. в данном случае все происходит аналогично тому, как если би ви ввели в shell строку, которая заканчивается символом &.
Вот пример задания команд date и who в shell с передачей результатов в файл, заданний Perl-переменной. Все зто вьшолняется в фоновом режиме, чтобн для продолжения выполнения Perl-сценария не нужно било ждать завершення данного процесса.
$where = "who_out.".++$і; t получить новое имя файла system "(date; who) >$where &";
В зтом случае функция system возвращает код выхода shell и показывает таким образом, успешно ли бил запущен фоновий процесе, но не сообщает, били ли успешно выполнени команди date и who. В зтой заключенной в двойные кавички строке производится интерполяция переменних, позтому переменная $ where заменяется своим значением (зто делает Perl, а не shell). Если би ви хотели обратиться к переменной shell с именем $where, вам нужно било би поставить перед знаком доллара обратную косую или использовать строку в одинарних кавнчках.
Помимо стандартних дескрипторов файлов, порожденний процесе на-следует от родительского процесса много других вещей. Зто текущее значение, заданное командой umask, текущий каталог й, конечно, идентификатор пользователя.
Кроме того, порожденний процесе наследует все переменнне среды. Зти переменные обычно изменяются командой csh setenv или же соответствующим присваиванием и команд ой export shell (/bin/sh). Переменные среды исполь-зуются многими утилитами, включая сам shell, для изменения порядка работы зтих утилит и управления йми.
В Perl предусмотрена возможность проверки и изменения текущих пере-менных среды посредством специального хеша, который называется %env. Каждый ключ зтого хеша соответствует имени переменной среды, а соответ-ствующее значение — значенню переменной. Содержимое данного хеша отражает параметры среды, переданные Perl родительским shell; изменение хеша изменяет параметри среды, которую использует Perl и порожденные им процессы, но не среды, используемой родительскими процессами.
Вот простая программа, которая работает, как printenv:
foreach $key (sort keys %ENV) ( print "$key = $ENV($key)\n";
> . ,
Обратите внимание: знак равенства здесь — зто не символ операции присваивания, а просто текстовый символ, с помощью которого функция print выдает сообщения вида TERM=xterm или U3ER=merlyn.
Вот фрагмент программы, с помощью которого значение переменной path изменяется таким образом, чтобы поиск команды grep, запущенной функцией system, производился только в "обычных" местах:
$oldPATH = $ENV{ "PATH"); # сохранить предыдущий путь $ENV("PATH") = "/bin:/usr/bin:/usr/ucb"; # ввести известньй путь systemC'grep fred bedrock >output") ; # запустить команду $ENV{"PATH"} = $oldPATH; # восстановить предьшуший путь
Как много текста придется набирать! Гораздо быстрее будет просто установить локальнеє значение для зтого злемента хеша.
Несмотря на наличие некоторых недостатков, операция local может делать одну вещь, которая не под силу операции ту: она способна присваи-вать временное значение одному злементу массива или хеша.
{
local $ENV{"PATH"> " "/bin:/usr/bin:/usr/ucb"; ! systemC'grep fred bedrock >output");
>
Функция system может принимать не один аргумент, а список аргумен-тов. В зтом случае Perl не передает список аргументов в shell, а рассматривает первый аргумент как подлежащую выполнению команду (при необходимости производится ее поиск согласно переменной path), а остальные аргумен-ты — как аргументи команды без обычной для shell интерпретации. Другими словами, вам не нужно заключать в кавычки пробельные символы и беспо-коиться об аргументах, которые содержат угловые скобки, потому что все зто — просто символы, передаваемые в программу. Таким образом, следую-щие две команды зквивалентны:
system "grep 'fred flintstone' buffaloes"; # с использованием shell system "grep","fred flintstone","buffaloes"; # без использования shell
Применение в функции system списка, а не одной строки, зкономит также один процесе shell, позтому поступайте так при любой возможности. (Если форма функции system c одним аргументом достаточно проста, Perl сам оптимизирует код, полностью убирая вызов shell и обращаясь к соответ-ствующей программе непосредственно, как если бы вы использовали вызов функции с несколькими аргументами.)
Вот еще один пример зквивалентных форм:
Ocfiles = ("fred.c","barney.c"); # что компилировать Soptions = ("-DHARD","-DGRANITE"); # опции system "cc -o slate Soptions Bcfiles"; # c shell system "cc","-o","slate",goptions,Scfiles"; # без shell
Использование обратных кавычек
Еще один способ запуска процесса — заключить командную строку для /Ып/sh в обратные кавычки. Как и в shell, зтот механизм запускает команду и ожидает ее завершення, получая данные со стандартного вывода по мере их поступления:
$now = "the time is now".'date'; # получает текст и дату
Значение переменной $now теперь представляет собой текст the time is now и результат выполнения команди date(l) (включая конечний символ новой строки):
the time is now Fri Aug 13 23:59:59 PDT 1996
Если взятая в обратные кавычки команда используется не в скалярном, а в списочном контексте, то возвращается список строкових значений, каждое из которых представляет собой строку (оканчивающуюся символом новой строки*) из результата выполнения команды. В примере с командой date у нас был бы всего один злемент, потому что она видала всего одну строку текста. Результат работы команды who выглядит так:
merlyn tty42 Dec 7 19:41 fred ttylA Aug 31 07:02 barney ttylF Sep 1 09:22
Вот как можно получить зтот результат в списочном контексте:
foreach $_ ('who') ( # один раз для каждой строки текста из who <$who,$where,$when) = /(\S+)\s+(\S+)\s+(.*)/;
print "$who on $where at $when\n";
}
* Или символом, которьш у вас занесен в переменную $/.
При каждом выполнении зтого цикла используется одна строка виход-ных данных команди who, потому что взятая в обратные кавички команда интерпретируется в списочном контексте.
Стандартний ввод и стандартний вывод ошибок команди, взятой в обратные кавички, наследуются от Perl-процесса*. Зто значит, что обычно стандартний вывод таких команд ви можете получить как значение строки, заключенной в обратные кавички. Одна из распространенных операций — обьединение стандартного внвода ошибок со стандартним виводом, чтобы команда в обратннх кавычках "подбирала" их оба. Для зтого используется конструкция shell 2>&1:
die "rm spoke!" if 'rm fred 2>&1';
Здесь Perl-процесе завершается, если rm посылает какое-нибудь сообще-ние — либо на стандартний вивод, либо на стандартний вивод ошибок, потому что результат больше не будет пустой строкой (пустая строка соот-ветствовала би значенню "ложь").
Использование процессов как дескрипторов файлов
Следующий способ запуска процесса — создание процесса, которий вигля-дит как дескриптор файла (аналогично библиотечной подпрограмме рореп(3), если ви с ней знакомы). Мы можем создать для процесса дескриптор файла, которий либо получает результат работы процесса, либо подает в него входные данные**. Ниже приведен пример создания дескриптора файла для процесса who(l). Поскольку зтот процесе вьщает результат, которий мы хотим прочитать, мы создаем дескриптор файла, открытый для чтения:
open(WHOPROC, "who 1"); # открнть who для чтения
Обратите внимание на вертикальную черту справа от who. Зта черта информирует Perl о том, что данная операция open относится не к имени файла, а к командо, которую необходимо запустить. Поскольку черта стоит справа от команди, данннй дескриптор файла открнвается для чтения. Зто означает, что предполагается прием данных со стандартного внвода команди who. (Стандартний ввод и стандартний вывод ошибок продолжают исполь-зоваться совместно с Perl-процессом.) Для остальной части программы дескриптор whoproc — зто просто дескриптор файла, которий открыт для
* На самом деле все не так просто. См. соответствующий ответ в разделе 8 сборника часто задаваемых вопросов по Perl ("Как перехватить stderr из внешней команды?"). Если у вас Perl версии 5.004, зтот сборник распространяется как обычная man-страница — в данном случас per/faq8( 1).
** Ho не одновременно. Примеры двунаправленной связи приведены в главе 6 книги Programming Perl и на man-странице рег1ірс(1).
чтения, что означает возможность вьшолнения над файлом всех обычных операций ввода-вывода. Вот как можно прочитать данные из команды who в массив:
@whosaid = <WHOPROC>;
Аналогичннм образом для запуска команды, которой необходимы вход-ные данные, мы можем открыть дескриптор файла процесса для записи, поставив вертикальную черту слева от команды, например:
open(LPR,"|Ipr -Psiatewriter") ;
print LPR Srockreport;
close(LPR) ;
В атом случае после открытия lpr мы записываем в него данные и закриваєм данный файл. Открьггие процесса с дескриптором файла позволяет выполнять команду параллельно с Perl-программой. Задание для дескриптора файла команды close заставляет Perl-программу ожидать завершения процесса. Если дескриптор не будет закрыт, процесе может продолжаться даже после завершения Perl-программы.
Открытие процесса для записи предполагает, что стандартний ввод команды будет получен из дескриптора файла. Стандартний вывод и стандартний вивод ошибок используются зтим процессом совместно с Perl. Как и прежде, ви можете использовать переадресацию ввода-вивода в етиле /bin/sh. Вот как в нашем последнем примере можно отбрасивать сообщения об ошибках команди Ipr.
open(LPR,"|Ipr -Psiatewriter >/dev/null 2>&1");
С помощью операций >/dev/null обеспечивается отбраснвание стандартного вивода путем переадресации его на нулевое устройство. Операция 2>&1 обеспечивает передачу стандартного вивода ошибок туда, куда направляется стандартний вывод, позтому сообщения об ошибках также отбрасываются.
Можно даже обьединить все зти фрагменти программи и в результате получить отчет обо всех зарегистрированных пользователях, кроме Фреда:
open (WHO,"who[ ") ;
open (LPR,"|Ipr - Psiatewriter");
while (<WHO>) (
unless (/fred/) { # не показывать имя Фред print LPR $_:
> } close WHO;
close LPR;
Считивая из дескриптора who по одной строке, зтот фрагмент кода выводит в дескриптор lpr все строки, которне не содержат строкового значення fred. В результате на принтер внводятся только те строки, которне не содержат имени fred.
Вовсе не обязательно указывать в команде open только по одной команде за один прием. В ней можно задать сразу весь конвейєр. Например, следую-щая строка запускает процесе ls(l), который передает свои результати по каналу в процесе tail(l), который, в свою очередь, передает свои результати в дескриптор файла who pr:
open(WHOPR, "Is I tail -r I");
Использование функции fork
Еще один способ создания нового процесса — клонирование текущего Perl-процесса с помощью UNIX-функции fork. Функция fork делает то же самое, что и системний вызов fork(2): создает клон текущего процесса. Зтот клон (он называется порожденным процессом, а оригинал — родительским) использует тот же выполняемый код, те же переменные и даже те же открьггые файлы. Различаются зти два процесса по возвращаемому значенню функции fork: для порожденного процесса оно равно нулю, а для родительского — ненулевое (или undef, если зтот системний вызов окажется неудачним). Ненулевое значение, получаемое родительским процессом,— зто не что иное как идентификатор порожденного процесса. Ви можете проверить возвращае-мое значение и действовать соответственно:
if (!defined($child_pid = fork()) {
die "cannot fork: $!";
} elsif ($pid) {
4s я —
родительский процесе } else (
# я — порожденннй процесе >
Чтоби максимально зффективно использовать зтот клон, нам нужно изучить еще несколько функции, которьге восьма похожи на своих UNIX-тезок: зто функции wait, exit и ехес.
Самая простая из них — функция ехес. Зто почти то же самое, что и функция system, за тем исключением, что вместо запуска нового процесса для виполнения shell-команди Perl заменяет текущий процесе на shell. После успешного внполнения ехес Perl-программа исчезает, поскольку вместо нее виполняется затребованная программа. Например,
ехес "date";
заменяет текущую Perl- программу командой date, направляя результат зтой команди на стандартний вивод Perl-программи. После завершення команди date делать больше нечего, потому что Perl-программа давно исчезла.
Все зто можно рассматривать и по-другому: функция system похожа на комбинацию функции fork c функцией ехес, например:
# МЕТОД І... использование system:
system("date");
t МЕТОД 2... использование fork/exec:
unless (fork) (
# fork видала нуль, позтому я — порожденный процесе и я выполняю:
exec ("date") ; t порожденный процесе становится командой date
}
Использовать fork и exec таким способом — не совсем правильно, потому что команда date и родительский процесе "пыхтят" одновременно, их результати могут переметаться и испортить все дело. Как дать родительскому процессу указание подождать, пока не завершится порожденный процесе? Именно зто и делает функция wait; она ждет завершення данного (да и любого, если быть точним) порожденного процесса. Функция waitpid более разбор-чива: она ждет завершення не любого, а определенного порожденного процесса:
if (!defined($kidpid = fork()) (
# fork возвратила undef, т.е. неудача die "cannot fork: $!";
” elsif ($pid =" 0) {
# fork возвратила О, позтому данная ветвь — порожденный процесе exec("date") ;
# если exec терпит неудачу, перейти к следующему оператору die "can't exec date: $!";
} else {
# fork возвратила не 0 и не undef,
# позтому данная ветвь — родительский процесе waitpid($kidpid, 0) ;
}
Если все зто кажется вам слишком сложным, изучите системные вызовы fork(2) и ехес(2), отыскав материалы о них в каком-нибудь руководстве по ОС UNIX, потому что Perl просто передает вызовы зтих функций прямо в системные вызовы UNIX.
Функция exit обеспечивает немедленньш выход из текущего Perl-про-цесса. Она используется для прерывания Perl-программы где-нибудь посе-редине или — вместе с функцией fork — для вьшолнения Perl-кода в процессе с последующим выходом. Вот пример удаления нескольких файлов из каталога///?у? в фоновом режиме с помощью порожденного Perl-процесса:
unless (defined ($pid = fork)) ( die "cannot fork: $!";
}
unless ($pid) (
unlink </tmp/badrock.*>; # удалить зти файлн exit; # порожденный процесе останавливается здесь
)
# родительский процесе продолжается здесь
waitpid($pid, 0); # после уничтожения порожденного процесса нужно все убрать
Без использования функций exit порожденный процесе продолжал бы вьшолнять Perl-код (со строки "# родительский процесе продолжается здесь") — а как раз зтого нам и не нужно.
Функция exit может иметь необязательный параметр, служащий числовим кодом выхода, который воспринимается родительским процессом. По умолчанию выход производится с нулевым кодом, показывающим, что все прошло нормально.
Сводка операций, проводимых над процессами
Операции, служащие для запуска процессов, перечислены в таблице 14.1. Таблица 14.1. Операции запуска процессов
Операция | Стандартний ввод |
Стандартний вывод |
Стандартний вывод ошибок | Нужно ли ожидать завершення процесса |
System() | Наследуется | Наследуется | Наследуется | Да |
от программы | от программы | от программы | ||
Строка в обратных | Наследуется от программы | Принимается как строковое | Наследуется от программы | Да |
кавычках | значение | |||
Запуск | Соединен с | Наследуется | Наследуется | Только во |
процесса как деск | дескриптором файла | от программы | от программы | время вы-полнения |
риптора файла для | close () | |||
вывода при | ||||
помощи | ||||
команди | ||||
open() | ||||
Запуск | Наследуется | Соединен с | Наследуется | Только во |
процесса как деск | от программы | дескриптором файла | от программы | время вы-полнения |
риптора файла для | close () | |||
ввода при | ||||
помощи | ||||
команди | ||||
open() | ||||
fork, | Выбирается | Выбирается | Выбирается | Выбирается |
ехес, | пользователем | пользователем | пользователем | пользователем |
wait, | ||||
waitpid |
Самый простой способ создать процесе — использовать для зтого функ-цию system. На стандартный ввод, вывод и вывод ошибок зто не влияет (они наследуются от Perl-процесса). Строка в обратных кавычках создает процесе и передает данные со стандартного вывода зтого процесса как строковое значение для Perl-программы. Стандартный ввод и стандартный вывод ошибок не изменяются. Оба зти метода требуют завершення процесса до выполнения другого кода.
Простой способ получить асинхронний процесе (процесе, который по-зволяет продолжать выполнение Perl-программы до своего завершення) — открыть команду как дескриптор файла с созданием канала для стандартного ввода или стандартного вывода зтой команди. Команда, открытая как дескриптор файла для чтения, наследует стандартный ввод и стандартный вывод ошибок от Perl-программы; команда, открытая как дескриптор файла для записи, наследует от Perl-программы стандартный вывод и стандартный вывод ошибок.
Самый гибкий способ запустить процесе — заставить программу вызвать функции fork, ехес и wait или waitpid, которые полностью соответст-вуют своим UNIX-тезкам. С помощью зтих функции вы можете запустить какой-либо процесе синхронно или асинхронне, а также конфигурировать по своєму усмотрению стандартный ввод, стандартный вывод и стандартный вывод ошибок*.
Передача и прием сигналов
Один из вариантов организации межпроцессного взаимодействия осно-ван на передаче и приеме сигналов. Сигнал — зто одноразрядное сообщение (означающее, что "произошло данное событие"), которое посылается в процесе из другого процесса или из ядра. Сигналам присваиваются номера, обычно из диапазона от единицы до небольшого числа, например 15 или 31. Одни сигналы (фиксированные) имеют предопределенное значение и посы-лаются в процесе автоматически при возникновении определенных обстоя-тельств (например, при сбоях памяти или в исключительных ситуациях, возникающих при выполнении операций с плавающей запятой). Другие сигналы генерируются исключительно пользователем из других процессов, но не из всех, а только их тех, которые имеют разрешение на передачу сигналов. Передача сигнала разрешается только в том случае, если вы являєтесь привилегированным пользователем или если передающий сигнал процесе имеет тот же идентификатор пользователя, что и принимающий.
Ответ на сигнал называется действием сигнала. Фиксированные сигналы выполняют определенные действия по умолчанию, например, осуществляют прерывание или приостановку процесса. Остальные сигналы по умолчанию
* Полезно таюке знать о формах типа open(STDERR, ">&stdout") , используемых для точной настройки дескрипторов файлов. См. пункт open в главе 3 книги Programming Perl или на man-странице per1func(l).
полностью игнорируются. Почти для любого сигнала действие по умолчанию может быть переопределено, с тем чтобы данный сигнал либо игнорировал-ся, либо перехватывался (с автоматическим вызовом указанной пользовате-лем части кода).
Все зто аналогично тому, что делается и в других языках программиро-вания, но сейчас излагаемый материал приобретет специфический Perl-от-тенок. Когда Perl-процесе перехватывает сигнал, асинхронне и автоматиче-ски вызывается указанная вами подпрограмма, моментально прерывая выполнявшийся до нее код. Когда зта подпрограмма завершается, вьшолне-ние прерванного кода возобновляется, как будто ничего не случилось (за исключением появлення результатов действий, внполненных зтой подпро-граммой,— если она вообще что-нибудь делала).
Обычно подпрограмма-обработчик сигнала делает одно из двух: преры-вает программу, выполнив "очистку", или устанавливает какой-то флаг (например, глобальную переменную), которую данная программа затем проверяет*.
Для того чтобы зарегистрировать подпрограммы-обработчики сигналов в Perl, нужно знать имена сигналов. После регистрации обработчика сигнала Perl при получении зтого сигнала будет вызывать выбранную подпрограмму.
Имена сигналов определяются на man-странице signal(2), а также, как правило, в подключаемом С-файле /usr/include/sys/signal.h. Зти имена обычно начинаются с букв sig, например sigint, sigquit и sigkill. Чтобы обьявить подпрограмму my_sigint_catcher () обработчиком сигнала sigint, мы должны установить соответствующее значение в специальном хеше %sig. В зтом хеше в качестве значення ключа int (зто sigint без sig) следует указать имя подпрограммы, которая будет перехватывать сигнал sigint:
$SIG{'INT'} ” 'my_sigint_catcher';
Но нам понадобится также определение зтой подпрограммы. Вот пример простого определения:
sub my_sigint_catcher (
$saw_sigint = 1; # установить флаг }
Данный перехватчик сигналов устанавливает глобальную переменную и сразу же возвращает управление. Выполнение программы продолжается с той позиции, в которой оно было прервано. Обычно сначала обнуляется флаг $saw_sigint, соответствующая подпрограмма определяется как перехватчик сигнала sigint, а затем следует код основной программы, например:
$saw_sigint = 0; # очистить флаг $SIG{'INT') = 'my_sigint_catcher'; # зарегистрировать перехватчик
foreach (@huge_array) (
# что-нибудь сделать
t
еще что-нибудь сделать
# и еще что-нибудь if ($saw_sigint) ( # прерывание нужно?
t здесь "очистка" last;
} } $SIG('INT'} = 'DEFAULT'; # восстановить действие по умолчанию
* Попытка вьшолнения действий более сложных, чем вышеописанные, вероятнее всего, запутает ситуацию; большинство внутренних механизмов Perl "не любят", когда их вызы-вают одновременно в основной программе и из подпрограммы. Ваши системныс библио-теки тоже зтого "не любят".
Особенность использования сигнала в данном фрагменте программы состоит том, что значение флага проверяется в важнейших точках процесса вычисления и используется для преждевременного выхода из цикла; при зтом выполняется и необходимая "очистка". Обратите внимание на послед-ний оператор в приведенном выше коде: установка действия в значение default восстанавливает действие конкретного сигнала по умолчанию (следующий сигнал sigint немедленно прервет вьшолнение программы). Еще одно полезное специальное значение вроде зтого — ignore, т.е. "игнорировать сигнал" (если действие по умолчанию — не игнорировать сигнал, каку sigint). Для сигнала можно установить действие ignore, если не нужно виполнять никакой "очистки" и вы не хотите преждевременно завершать выполнение основной программы.
Один из способов генерирования сигнала sigint — заставить пользователя нажать на клавиатуре терминала соответствующие прерыванию клавиши (на-пример, [Ctrl+C]). Процесе тоже может генерировать сигнал sigint, используя для зтого функцию kill. Данная функция получает номер или имя сигнала и посылает соответствующий сигнал в процессы (обозначенные идентификато-рами) согласно списку, указанному после сигнала. Следовательно, для передачи сигнала из программы необходимо определить идентификаторы процессов-по-лучателей. (Идентификаторы процессов возвращаются некоторыми функция-ми, например функцией fork, и при открьггии программы как дескриптора файла функцией open). Предположим, вы хотите послать сигнал 2 (известный также как sigint) в процессы 234 и 237. Зто делается очень просто:
kill(2,234,237); # послать SIGINT в 234 и 237 kill ('INT', 234, 237); # то же самое
Более подробно вопросы обработки сигналов описаны в главе 6 книги Programming Perl и на man-странице perlipc(l).
Упражнения
Ответы к упражнениям см. в приложении А.
Напишите программу, которая получает результат команды date и вычис-ляет текущий день недели. Если день недели — рабочий день, виводить get to work, в противном случае виводить go play.
Напишите программу, которая получает все реальнне имена пользователей из файла /etc/passwd, а затем трансформирует результат команды who, заменяя регистрационное имя (первая колонка) реальным именем. (Совет:
создайте хеш, где ключ — регистрационное имя, а значение — реальное имя.) Попробуйте вьшолнить зту задачу с использованием команды who как в обратных кавычках, так и открьггой как канал. Что легче?
Модифицируйте предыдущую программу так, чтобы ее результат автомати-чески поступал на принтер. (Если у вас нетдоступа к принтеру, то, вероятно, вы можете послать самому себе сообщение злектронной почты.)
Предположим, функция mkdir перестала работать. Напишите подпро-грамму, которая не использует mkdir, а вызывает /bin/mkdir c помо-щью функции system. (Убедитесь в том, что она работает с каталогами, в именах которых єсть пробел.)
Расширьте программу из предыдущего упражнения так, чтобы в ней устанавливались права доступа (с помощью функции chmod).
| Назад | Вперед |
| Содержание | Предисловие | Введение | Ссылки | Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10 | Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19 | Приложение А | Приложение Б | Приложение В | Приложение Г | |
Управляющие структуры
Блоки операторов
Блок операторов — это последовательность операторов, заключенная в парные фигурные скобки. Блок операторов выглядит следующим образом:
(
первыи_оператор;
второй_оператор;
третий_оператор;
последний_оператор;
>
Perl выполняет операторы по очереди, начиная с первого и кончая последним. (Позднее вы узнаете о том, как можно изменять порядок выполнения в блоке, но пока достаточно и этого.)
Синтаксически блок операторов принимается вместо любого одиночного оператора, но обратное не верно.
Завершающая точка с запятой, стоящая за последним оператором, не обязательна. Таким образом, вы можете разговаривать на языке Perl с С-акцентом (точка с запятой присутствует) или с паскалевским акцентом (точка с запятой отсутствует). Чтобы облегчить последующее добавление операторов, мы обычно рекомендуем опускать точку с запятой лишь в том случае, если блок занимает целую строку. Сравните примеры этих стилей в следующих двух блоках if:
if ($ready) ( $hungry++ } if ($tired) (
$sleepy = ($hungry + 1) * 2;
}
Оператор if/unless
Следующей по сложности управляющей структурой является оператор if. Эта конструкция состоит из управляющего выражения (проверяемого на истинность) и блока. В ней также может быть часть, начинающаяся оператором else, за которой следует еще один блок операторов. Другими словами, все это выглядит так:
if (выражение) (
оператор_1
оператор_2
оператор_3 }
else {
оператор_1
оператор_2
оператор_3 }
(Если вы любите программировать на С и Java, то для вас должна быть вполне очевидной обязательность фигурных скобок. Они устраняют необходимость в применении правила "путающего зависшего else".)
Во время выполнения Perl-программы вычисляется управляющее выражение. Если оно истинно, то выполняется первый блок операторов в приведенном выше примере. Если выражение ложно, то выполняется второй блок.
Что же такое "истина" и "ложь"? В Perl эти правила несколько странноваты, но, тем не менее, дают ожидаемые результаты. Управляющее выражение вычисляется как строковая величина в скалярном контексте (если это уже строка, ничего не изменяется, а если это число, то оно преобразуется в строку*). Если данная строка либо пуста (т.е. имеет нулевую длину), либо состоит из одного символа "О" (цифры нуль), то значение выражения — "ложь". Все остальное автоматически дает значение "истина". Почему же в Perl столь забавные правила? А потому, что это облегчает условный переход не только по нулю (в противоположность ненулевому числу), но и по пустой (в противоположность непустой) строке, причем без необходимости создания двух версий интерпретируемых значений "истина" и "ложь". Вот несколько примеров интерпретации этих значений.
О # преобразуется в "О", поэтому "ложь"
1-1 # дает в результате 0, затем преобразуется в "О", поэтому "ложь"
1 # преобразуется в "I", поэтому "истина"
"" # пустая строка, поэтому "ложь"
"1" # не "" или "О", поэтому "истина"
"00" # не "" или "О", поэтому "истина" (это странно, поэтому будьте настороже)
"0.000" # "истина" — будьте внимательны, по той же причине
undef # дает в результате "", поэтому "ложь"
* Внутренне все трактуется несколько иначе, но внешне все выполняется так, будто именно это и происходит.
Таким образом, интерпретация значений как истинных или ложных достаточно интуитивна, но пусть это вас не пугает. Вот пример полного оператора if:
print "how old are you? ";
$a = <STDIN>;
chomp($a) ;
if ($a < 18) (
print "So, you're not old enough to vote, eh?\n";
( else (
print "Old enough! Cool! So go vote! \n";
$voter++; # count the voters for later )
Блок else можно опустить, оставив только часть, касающуюся then:
print "how old are you? ";
$a - <STDIN>;
chomp($a) ;
if ($a < 18) (
print "So, you're not old enough to vote, eh?\n";
}
Иногда бывает удобно часть "then" опустить и оставить только else, потому что более естественно сказать "сделайте то, если это ложь", нежели "сделайте то, если это — не истина". В Perl этот вопрос решается с помощью оператора unless:
print "how old are you? ";
$a = <STDIN>;
chomp ($a) ;
unless ($a < 18) (
print "Old enough! Cool! So go vote!\n";
$voter++;
>
Заменить if на unless — это все равно что сказать "Если управляющее выражение ложно, сделать..." (Оператор unless может содержать блок else, как и оператор if.)
Если у вас больше двух возможных вариантов, введите в оператор if ветвь elsif, например:
if (выражение один) {
оператор_1_при_истине_ один;
оператор_2 _при_истине_один;
оператор_ 3_при_истине_ один ;
} elsif (выражение_два) {
оператор_1_при_истине_два
оператор_2_при_истине_два
олератор_3_при_истине_два } elsif (варажение_три) (
оператор_1_при_истине_три
оператор_2_при_истине_три
оператор _ 3_при_истине_ три
} else ( -
оператор__ 1_при_всеи_ложных;
оператор_2_при_в
сех_ложных/ оператор_3_при_всех_ложных;
}
Все управляющие выражения вычисляются по очереди. Если какое-либо выражение истинно, то выполняется соответствующая ветвь, а все остальные управляющие выражения и соответствующие блоки операторов пропускаются. Если все выражения ложны, то выполняется ветвь else (если таковая имеется). Присутствие блока else не обязательно, но он никогда не помешает. В программе может быть столько ветвей elsif, сколько вам необходимо.
Оператор while/until
Ни один язык программирования не был бы полным без какой-нибудь формы организации цикла* (повторяющегося выполнения блока операторов). Perl может организовать цикл с помощью оператора while:
while (выражение) {
оператор_1; '
оператор_2;
оператор_3;
}
Чтобы выполнить оператор while, Perl вычисляет управляющее выражение (в данном примере — выражение). Если полученное значение — "истина" (по принятым в Perl правилам установления истинности), то один раз вычисляется тело оператора while. Это повторяется до тех пор, пока управляющее выражение не станет ложным. Тогда Perl переходит к оператору, следующему после цикла while. Например:
print "how old are you? " ;
$a = <STDIN>;
chomp($a) ;
while ($a > 0) {
print "At one time, you were $a years old.\n";
$a—;
}
Иногда легче сказать "до тех пор, пока что-то не станет истинным", чем "пока не это — истина". Для этого случая у Perl тоже есть ответ. Требуемый эффект дает замена while на until:
until (выражение) { оператор_1;
оператор_2;
оператор_3;
} * Вот почему HTML — не язык программирования.
Обратите внимание на то, что в обеих формах операторы тела цикла полностью пропускаются, если при анализе управляющего выражения выясняется, что цикл должен быть завершен. Например, если в приведенном выше фрагменте программы пользователь введет возраст меньше нуля. Perl пропустит тело цикла.
Вполне может случиться так, что управляющее выражение не даст циклу завершиться. Это абсолютно допустимо, а иногда и желательно, поэтому ошибкой не считается. Например, вы хотите, чтобы цикл повторялся все время, пока у вас нет ошибок, а после цикла ставите какой-то код обработки ошибок. Эту схему можно использовать для программы-демона, которая должна работать до тех пор, пока система не откажет.
Оператор do {} while/until
Оператор while/until, который вы видели в предыдущем разделе, проверяет условие в начале каждого цикла, до входа в него. Если результат проверки условия — "ложь", цикл не будет выполнен вообще.
Иногда возникает необходимость проверять условие не в начале, а в конце цикла. Для этого в Perl есть оператор do {} while, который очень похож на обычный оператор while*, за исключением того, что он проверяет выражение только после однократного выполнения цикла.
do (
оператор_1;
опвратор_2;
оператор_3;
}
while выражение;
Perl выполняет операторы блока do. Дойдя до конца, он вычисляет выражение на предмет истинности. Если выражение ложно, цикл завершается. Если выражение истинно, весь блок выполняется еще раз, а затем выражение проверяется вновь.
Как и в обычном цикле while, условие проверки можно инвертировать, заменив do {} while на do {} until. Выражение все равно проверяется в конце цикла, но на обратное условие. В некоторых случаях, особенно сложных, такой способ записи условия представляется более естественным.
$stops = 0;
do (
$stops++;
print "Next stop? " ;
chomp($location = <STDIN>) ;
} until $stops > 5 I I $location eq 'home';
* Ну, не совсем чтобы очень похож; управляющие директивы цикла, описанные в главе 9, в такой форме не работают.
Оператор for
Еще одна конструкция Perl, предназначенная для организации цикла, — оператор for, который выглядит подозрительно похоже на оператор for языков С и Java и работает примерно так же. Вот он:
for ( начальное_выражение; проверочное_выражение ,• возобновляющее выражение ) (
оператор_1;
оператор_2;
оператор_3;
1
Если преобразовать этот оператор в те формы, которые мы рассмотрели раньше, то он станет таким:
начальное выражение;
while (проверочное выражение) {
оператор_1;
оператор_2;
оператор_3;
возобяовляющее_выраи:ение;
}
В любом случае сначала вычисляется начальное выражение. Это выражение, как правило, присваивает начальное значение переменной цикла, но никаких ограничений относительно того, что оно может содержать, не существует; оно даже может быть пустым (и ничего не делать). Затем вычисляется проверочное выражение. Если полученное значение истинно, выполняется тело цикла, а затем вычисляется возобновляющее выражение (как правило, используемое для инкрементирования переменной цикла — но не только для этого). Затем Perl повторно вычисляет проверочное выражение, повторяя необходимые действия.
Следующий фрагмент программы предназначен для вывода на экран чисел от 1 до 10, после каждого из которых следует пробел:
for ($i = 1; $i <- 10; $i++) ( print "$i ";
)
Сначала переменная $i устанавливается в 1. Затем эта переменная сравнивается с числом 10. Поскольку она меньше или равна 10, то выполняется тело цикла (один оператор print), а затем вычисляется возобновляющее выражение ($i++), в результате чего значение $i изменяется на 2. Поскольку эта переменная все еще меньше или равна 10, процесс повторяется до тех пор, пока в последней итерации значение 10 в $i не изменится на 11. Поскольку переменная $i уже не меньше и не равна 10, цикл завершается (при этом $i становится равным 11).
Оператор foreach
Еще одна циклическая конструкция — оператор foreach. Этот оператор получает список значений и присваивает их по очереди скалярной переменной, выполняя с каждым последующим присваиванием блок кода. Выглядит это так:
foreach
$i (@список) {
оператор_1;
оператор_2;
оператор_3;
}
В отличие от C-shell, в Perl исходное значение этой скалярной переменной при выходе из цикла автоматически восстанавливается; другими словами, эта скалярная переменная локальна для данного цикла.
Вот пример использования оператора foreach:
@а = (1,2,3,4,5);
foreach $b (reverse @a) { print $b;
1
Эта программа выводит на экран 54321. Отметим, что список, используемый в операторе foreach, может быть произвольным списочным литералом, а не просто переменной-массивом. (Это справедливо для всех конструкций Perl, которым требуется список.)
Имя скалярной переменной можно опустить. В этом случае Perl будет действовать так, как будто вы указали имя переменной $_. Вы увидите, что переменная $_ используется по умолчанию во многих операциях языка Perl, поэтому ее можно рассматривать как неявную временную переменную. (Все операции, в которых по умолчанию используется $_, могут использовать и обычную скалярную переменную.) Например, функция print выводит значение переменной $_, если другие значения не указаны, поэтому следующий пример дает такой же результат, как и предыдущий:
@а = (1,2,3,4,5);
foreach (reverse @a)
{
print ;
}
Видите, насколько использование неявной переменной $_ все упрощает? Когда вы познакомитесь с другими функциями и операциями, которые по умолчанию используют $_, то еще выше оцените полезность данной конструкции. Это один из тех случаев, когда короткая конструкция более понятна, чем длинная.
Если список, над которым производятся циклические преобразования, состоит из реальных переменных, а не получен при помощи функции, возвращающей списочное значение, то используемая в цикле переменная представляет собой псевдоним для каждой переменной этого списка, а не просто копию ее значения. Это значит, что, изменяя скалярную переменную, вы изменяете и конкретный элемент в списке, который ей соответствует. Например:
@а = (3,5,7,9) ;
foreach $one (@a) { $one *= 3;
> # @а теперь равно (9,15,21,27)
Обратите внимание на то, что изменение переменной $опе привело к изменению каждого элемента массива @а.
Упражнения
Ответы к упражнениям даны в приложении А.
1. Напишите программу, которая запрашивает температуру окружающего воздуха и выводит на экран слова "too hot", если температура выше 72 градусов (по Фаренгейту), а в противном случае выводит "too cold".
2. Модифицируйте программу из предыдущего упражнения так, чтобы она выводила на экран "too hot", если температура выше 75 градусов, "too cold" при температуре ниже 68 градусов и "just right!" при температуре от 68 до 75 градусов.
3. Напишите программу, которая читает список чисел (каждое из которых записано в отдельной строке), пока не будет прочитано число 999, после чего программа выводит сумму всех этих чисел. (Ни в коем случае не прибавляйте 999!) Например, если вы вводите 1, 2, з и 999, программа должна ответить цифрой 6 (1 + 2 + 3).
4. Напишите программу, которая читает список строковых значений (каждое из которых занимает отдельную строку) и выводит его на экран в обратном порядке, причем без проведения над списком операции reverse. (Вспомните, что <stdin> при использовании в списочном контексте читает список строковых значений, каждое из которых занимает отдельную строку.)
5. Напишите программу, которая выводит на экран таблицу чисел от 0 до 32 и их квадратов. Попробуйте предложить способ, при котором список не обязательно должен содержать все указанные числа, а затем способ, при котором их нужно задавать все. (Чтобы выводимый результат выглядел более привлекательно, используйте функцию
printf "%5g ”8д\п", $а, $Ь
которая выводит $а как пятизначное число, а $ь — как восьмизначное.
| Назад | Вперед |
| Содержание | Предисловие | Введение | Ссылки | Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10 | Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19 | Приложение А | Приложение Б | Приложение В | Приложение Г | |
UUCP
Если у вас или у вашей организации есть доступ к UUNET, это значит, что у вас должна быть система, имеющая прямое UUCP-соединение с этой сетью. Найдите эту систему и введите (в одну строку):
uucp uunet\ !~/published/oreillу/nutshell/learning_perl2/examples.tar.gz ваш_хост\!~/ваше_имя/
Если вместо csh-shell вы пользуетесь Bourne-shell (sh), обратные косые можно опустить. Файл примеров должен появиться через некоторое время (день или более) в каталоге /usr/spool/uucppublic/ваше_имя.
Man-страница | Тема |
perlref | Ссылки |
perldsc | Введение в структуры данных |
perllol | Структуры данных: списки списков |
perltoot | Пособие по объектно-ориентированному программированию |
perlobj | Объекты |
perltie | Объекты, скрытые за простыми переменными |
perlbot | Хитрости и примеры использования объектов |
pertipc | Межпроцессное взаимодействие |
perldebug | Отладка |
perldiag | Диагностические сообщения |
perlsec | Безопасность |
perltrap | Ловушки для неосторожных |
perlstyle | Руководство по стилю |
perlpod | Старая документация в виде простого текста |
perlbook | Информация о книгах |
perlembed | Методы встраивания Perl-кода в приложение, написанное на С или C++ |
perlapio | Внутренний интерфейс абстрагирования ввода-вывода |
perlxs | Интерфейс прикладного программирования XS |
perlxsfut | Пособие по XS |
perlgufs | Внутренние функции для тех, кто разрабатывает расширения |
perlcall | Правила вызова из С |
Слово Perl является аббревиатурой выражения
История создания языка Perl
Слово Perl является аббревиатурой выражения Practical Extraction and Report Language (практический язык извлечений и отчетов), хотя иногда его называют Pathologically Eclectic Rubbish Lister (патологически эклектичный мусорный листер). Не стоит спорить о том, какое из этих названий более правильное, потому что оба они принадлежат Ларри Уоллу, создателю и главному архитектору, распространителю и опекуну языка Perl. Ларри создал этот язык, когда пытался формировать отчеты из иерархии файлов системы оповещения об ошибках, похожей на Usenet-новости, а возможности применявшегося в то время обработчика потоков данных awk оказались исчерпанными. Будучи настоящим (то есть ленивым) программистом, Ларри решил вырвать данную проблему с корнем, применив для этого какой-нибудь универсальный инструмент, который он надеялся использовать и в дальнейшем. В результате появилась первая версия языка Perl.
Позабавившись немного с этой версией, добавив кое-что, Ларри предложил ее сообществу читателей материалов телеконференций Usenet, известному также как "Сеть" (the Net). Пользователи, имеющие доступ к входящим в систему Usenet компьютерам, разбросанным по всему свету (а их в то время было несколько десятков тысяч), обеспечили для создателя Perl эффективную "обратную связь", спрашивая, как делать одно, другое, третье. Многие из этих задач Ларри даже и не собирался ставить перед своим маленьким новым языком программирования.
В результате Perl все рос и рос, причем почти с той же скоростью, что и операционная система UNIX. (Специально для новичков: все ядро UNIX тогда требовало памяти объемом 32 К! Теперь мы счастливы, если нам удается уместить его в несколько мегабайтов.) Выросли и его возможности.
Повысилась переносимость. То, что когда-то было компактным языком, теперь сопровождается сотнями страниц документации, в состав которой входят десятки man-страниц, 600-страничный справочник серии Nutshell, материалы множества телеконференций Usenet с 200000 подписчиков, — а теперь еще и эта скромная книга.
Ларри уже не сопровождает Perl в одиночку, но сохраняет свой эксклюзивный титул главного архитектора. A Perl все растет и растет.
Примеры программ, содержащихся в этой книге, мы проверяли на Perl версии 5.004 (на момент написания книги это был самый последний выпуск). Все программы, которые здесь рассматриваются, должны работать с Perl версии 5.0 и со всеми последующими версиями. По сути дела, даже программы, написанные на языке Perl версии 1.0, довольно неплохо работают с последними версиями, если только не сталкиваются с некоторыми эксцентричными нововведениями, сделанными во имя прогресса.
Назначение языка Perl
Назначение языка Perl — помочь программисту в выполнении рутинных задач, которые для shell слишком трудны или плохо переносимы, а также чересчур заумны, одноразовы или сложны для кодирования на С или ином используемом в UNIX языке.
Научившись пользоваться языком Perl, вы, возможно, обнаружите, что начинаете тратить меньше времени на правильное заключение в кавычки различных параметров shell (или на корректное выполнение С-объявлений), а больше — на чтение Usenet-новостей и катание с гор на лыжах, потому что Perl — замечательное средство для вашего совершенствования как программиста. Мощные конструкции этого языка позволяют создавать (с минимальной затратой сил) некоторые очень эффективные специализированные решения и универсальные инструменты. Эти инструменты можно использовать и в дальнейшем, потому что написанные на Perl программы отличаются высокой переносимостью и готовностью к использованию. В результате у вас появится еще больше времени для чтения Usenet-новостей и посещения с друзьями баров караоке.
Как и любой язык, Perl может быть языком "только_для_написания" программ, которые потом будет невозможно прочитать. Однако при правильном подходе вы можете избежать этого весьма распространенного недостатка. Да, иногда Perl-текст выглядит для непосвященных как случайный набор символов, но умудренный опытом Perl-программист знает, что у этого набора есть контрольная сумма и каждый его символ имеет свое предназначение. Если вы будете следовать указаниям, приведенным в нашей книге, ваши программы будут легкими для чтения и простыми для сопровождения — но, вероятно, не выиграют никаких "состязаний по заумности".
Доступность
Если при попытке вызвать 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 распространяется в виде дистрибутива.
Основные понятия
Сценарий shell — это не что иное, как последовательность команд shell, оформленная в виде текстового файла. Этот файл затем превращается в исполняемый путем включения бита исполнения (посредством команды chmod + х имя_файла), после чего по приглашению shell вводится имя файла. Давайте рассмотрим какой-нибудь сценарий shell. Например, сценарий выполнения команды date, а затем команды who можно записать и выполнить так:
% echo date >somescript % echo who ”somescript % cat somescript date
who '
% chmod +x somescript % somescript
[результат выполнения команды date, затем команды who] %
Аналогичным образом Perl-программа — это набор Perl-операторов и определений, записанных в виде файла. Затем включается бит исполнения*, после чего по приглашению shell вводится имя файла. При этом, однако, файл должен иметь признак, указывающий, что это Perl-программа, а не программа shell.
В большинстве случаев операция введения такого признака заключается в помещении в начало файла строки
#!/usr/bin/perl
Если же ваш Perl инсталлирован в каком-то нестандартном каталоге или если ваша система "не понимает" строку, начинающуюся с символов #!, придется сделать кое-что еще. Узнайте об этом у того, кто инсталлировал вам Perl. В примерах, приведенных в книге, предполагается, что вы пользуетесь именно этим, общепринятым механизмом обозначения Perl-программ.
Perl — это, в основном, язык со свободным форматом записи программ (вроде С) — пробельные символы, включаемые между лексемами (элементами программы, например print или +), не обязательны, если две рядом стоящие лексемы невозможно принять за какую-то третью лексему. В последнем случае какой-нибудь пробельный символ является обязательным. (К пробельным символам относятся пробелы, знаки табуляции, символы новой строки, символы возврата каретки, символы перехода на новую страницу.) Имеется также ряд конструкций, в которых требуется использовать определенный пробельный символ в определенном месте, но об этом мы расскажем, когда дойдем до их описания. Вы можете считать, что тип и
*
Это справедливо для UNIX-систем. Указания о том, как сделать сценарий исполняемым в других системах, вы можете найти в сборнике ответов на часто задаваемые вопросы (FAQ) пп Perl или в документации, приложенной к вашей операционной системе.
количество пробельных символов между лексемами во всех прочих случаях могут быть произвольными.
Хотя почти каждую Perl-программу можно записать в одну строку, эти программы обычно пишут с отступами, как С-программы, причем вложенные операторы записывают с большим отступом, чем охватывающие. В этой книге вы увидите множество примеров, записанных с типичными для языка Perl отступами.
Аналогично сценарию shell, Perl-программа состоит из всех операторов Perl, имеющихся в файле и рассматриваемых в совокупности как одна большая программа, подлежащая выполнению. Понятия "основной" (main) программы, как в С, здесь нет.
Комментарии в Perl похожи на комментарии shell (современные). Комментарием является все, что следует за незаключенным в кавычки знаком # вплоть до конца строки. Многострочных комментариев, как в С, здесь нет.
В отличие от большинства shell (но аналогично awk и sed), интерпретатор языка Perl перед выполнением программы полностью разбирает ее и компилирует в свой внутренний формат. Это значит, что после запуска программы вы никогда не получите сообщение о синтаксической ошибке и что пробельные символы и комментарии не замедляют ход выполнения программы. Такой метод обеспечивает быстрое выполнение операций языка Perl после запуска и является дополнительным стимулом к отказу от использования С в качестве служебного языка систем лишь на том основании, что С — транслируемый язык.
Но процедура компиляции все же требует времени, и применение большой Perl-программы, которая быстро выполняет одну маленькую задачу (из множества тех, которые она способна выполнить), а затем заканчивает свою работу, не будет эффективным, ибо время ее выполнения окажется ничтожно малым по сравнению со временем компиляции.
Perl, таким образом, работает и как компилятор, и как интерпретатор. С одной стороны, это компилятор, потому что перед выполнением первого оператора программы она полностью считывается и разбирается. С другой стороны. Perl — интерпретатор, потому что никакого объектного кода, занимающего место на диске в ожидании исполнения, в данном случае нет. Другими словами, он сочетает в себе лучшее из компилятора и интерпретатора. Конечно же, было бы просто здорово, если бы выполнялось какое-то кэширование компилированного объектного кода между вызовами, а то и его трансляция в "родной" машинный код. Рабочая версия такого компилятора фактически уже существует, и сейчас планируется, что она войдет в выпуск 5.005. О текущем состоянии дел можно узнать в сборнике FAQ, посвященном Perl.
Прогулка по стране Perl
Наше путешествие по стране Perl мы начнем с небольшой прогулки. В ходе этой прогулки мы ознакомимся с некоторыми возможностями языка Perl на примере небольшого приложения. Здесь приведены лишь очень краткие пояснения; каждая тема гораздо подробнее освещается в соответствующей главе. Тем не менее эта короткая прогулка должна дать вам возможность быстро "почувствовать" этот язык, и вы сможете решить, будете ли вы дочитывать книгу до конца или же отправитесь обратно к своим Usenet-новостям и лыжным склонам.
Программа Hello, World
Давайте рассмотрим небольшую программу, которая делает что-то реальное. Вот ваша базовая программа, которая выводит на экран слова "Hello, World":
^! /usr/bin/perl -w print ("Hello, World\n") ;
Первая строка говорит о том, что это программа написана на языке Perl. Кроме того, первая строка является комментарием; ведь комментарием в Perl, как и во многих интерпретирующих языках программирования, являются все лексемы, стоящие после знака # и до конца текущей строки. Но, в отличие от всех остальных комментариев, включенных в эту программу, комментарий в первой строке особенный: Perl ищет здесь необязательные аргументы. В данном случае использовался ключ -w. Этот очень важный ключ дает Perl указание выдавать дополнительные предупреждающие сообщения о потенциально опасных конструкциях. Вам следует всегда использовать в своих программах ключ -w.
Вторая строка — это выполняемая часть данной программы. Здесь мы видим функцию print. В данном случае встроенная функция print имеет всего один аргумент, С-подобную текстовую строку. В этой строке комбинация символов \п обозначает символ новой строки. Оператор print завершается точкой с запятой (;). Как и в С, все простые операторы в Perl завершаются точкой с запятой*.
Когда вы вызываете эту программу, ядро запускает интерпретатор Perl, который разбирает всю программу (обе строки, включая первую, т.е. комментарий), а затем выполняет компилированный вариант. Первая и единственная операция — выполнение функции print, которая посылает значения своих аргументов на устройство вывода. По окончании выполнения программы этот Perl-процесс завершается и возвращает родительскому shell код успешного выполнения.
* Точку с запятой можно опустить, если оператор является последним оператором в блоке или файле либо оператором eval.
Скоро вы увидите Perl-программы, в которых print и другие функции иногда вызываются с круглыми скобками, а иногда — без них. Правило здесь простое: круглые скобки для встроенных функций Perl не являются ни обязательными, ни запрещенными. Их применение может прояснить ситуацию, а может и запутать ее, так что выработайте собственный стиль.
Как задавать вопросы и запоминать результат
Давайте немного усложним пример, ведь приветствие Hello, World — слишком простое и статичное. Сделаем так, чтобы программа называла вас по имени. Для этого нам нужно место для хранения имени, способ задания вопроса об имени и способ получения ответа.
Одно из мест для хранения значений (вроде имени) — скалярная переменная. Для хранения вашего имени в нашей программе мы используем скалярную переменную $name. В главе 2, "Скалярные данные", мы более подробно узнаем о том, что можно хранить в этих переменных и что можно с ними делать. Пока же предположим, что мы можем хранить в скалярной переменной только одно число или строку (последовательность символов).
Программа должна иметь возможность спросить у вас имя. Для этого нам нужен способ выдачи приглашения и способ принятия от вас данных. Предыдущая программа показала нам, как можно приглашать, используя для этого функцию print. Получение же строки с терминала осуществляется с помощью конструкции <stdin>, которая (в нашем случае) получает одну строку введенных данных. Введенные данные мы присваиваем переменной $ name. Это дает нам следующую программу:
print "What is your name? "; # Как ваше имя? $name = <STDIN>;
Значение переменной $name пока содержит завершающий символ новой строки (имя Randai поступает как Randal\n). Чтобы избавиться от этого символа, мы воспользуемся функцией chomp, которая в качестве своего единственного аргумента принимает скалярную переменную и удаляет из ее строкового значения завершающий символ перехода на новую строку (пробельный символ), если он присутствует:
chomp ($name) ;
Теперь нам нужно лишь сказать Hello и указать значение переменной $name, что мы можем сделать так, как в shell, поместив эту переменную в заключенную в кавычки строку:
orint "Hello, $name ! \n";
Как и в shell, если нам нужен именно знак доллара, а не ссылка на скалярную переменную, мы можем предварить этот знак обратной косой чертой.
Сложив все вместе, получаем:
•ft! /usr/bin/perl -w print "What is your name? " ; $name = <STDIN>; chomp ($name) ; print "Hello, Sname ' \n" ;
Добавляем возможность выбора
Допустим теперь, что у нас припасено какое-то особое приветствие для пользователя по имени Рэндал, а для остальных — обычное. Для этого нам нужно сравнить имя, которое было введено, со строкой Randal, и, если оно совпадает, сделать что-то особое. Давайте добавим в программу С-подобную ветвь tf-then-eise и операцию сравнения:
#!/usr/bin/perl
print "What is your name? "; $name = <STDIN>; chomp ($name} ; if ($name eq "Randal") {
print "Hello, Randal! How good of you to be here!\n"; } else { print "Hello, $name! \n"; # обычное приветствие
В операции eq сравниваются две строки. Если они равны (т.е. совпадают все символы и длина строк одинакова), результатом будет "истина". (В С и C++ подобной операции нет*).
Оператор if выбирает, какой блок операторов (заключенных между парными фигурными скобками) выполняется; если выражение дает в результате значение "истина", выполняется первый блок, в противном случае выполняется второй блок.
Секретное слово
Теперь, когда у нас есть имя, давайте сделаем так, чтобы человек, выполняющий эту программу, угадывал секретное слово. У всех, кроме Рэндала, программа будет непрерывно требовать ввести это слово, пока пользователь не наберет слово правильно. Сначала мы приведем текст программы, потом дадим пояснение к ней:
#! /usr/bin/perl -w
$secretword = "llama"; # секретное слово print "What is your name? "; $name = <STDIN>; chomp $name;
if ($name eq "Randal") { print "Hello, Randal! How good of you to be here!\n";
* Для получения аналогичного результата можно использовать стандартную подпрограмму libc. Но это не операция.
} else {
print "Hello, $name ' \n"; # обычное приветствие print "What is the secret word? "; $guess = <STDIN>; chomp ($guess) ; while ($guess ne $secretword) {
print "Wrong, try again. What is the secret worcl.•' '• ; $guess = <STDIN>; chomp ($guess) ;
Сначала мы задаем секретное слово, помещая его в скалярную переменную $secretword. После приветствия программа спрашивает (посредством вызова еще одной функции print) у пользователя (не Рэндала) его вариант секретного слова. Этот вариант сравнивается с заданным секретным словом в операции ne. Данная операция возвращает значение "истина", если сравниваемые строки не равны (т.е. данная операция противоположна операции eq). Результат сравнения управляет циклом while, который выполняет этот блок операторов до тех пор, пока сравнение дает значение "истина".
Конечно, эта программа плохо защищена, потому что любой, кому надоест угадывать секретное слово, может просто прервать ее выполнение и вернуться к приглашению, а то и подсмотреть секретное слово в исходном тексте. Мы, однако, не пытались разработать систему обеспечения безопасности, а лишь хотели привести подходящий для данного раздела пример.
Несколько секретных слов
Давайте посмотрим, как можно модифицировать эту программу так, чтобы она принимала несколько секретных слов. Используя то, что мы уже видели, можно было бы многократно сравнивать вариант-догадку с рядом правильных ответов, хранящихся в отдельных скалярных переменных. Такой список, однако, было бы трудно корректировать или модифицировать в зависимости от дня недели и даты.
Более эффективное решение — хранить все возможные ответы в структуре данных, которая называется список, или (предпочтительнее) массив. Каждый элемент массива — это отдельная скалярная переменная, которой можно присваивать значение и затем использовать ее независимо от других. Можно также одним махом присвоить значение всему массиву. Мы имеем право присвоить значение всему массиву с именем @words так, чтобы он содержал три возможных правильных пароля:
@words = ("camel", "llarna", "alpaca");
Имена переменных-массивов начинаются с символа @, что позволяет отличать их от имен скалярных переменных. Существует еще один способ записи этой конструкции так, чтобы не нужно было ставить все эти кавычки — с помощью операции qw ( ) , например:
@words = qw (camel llama alpaca) ;
Это абсолютно то же самое; операция qw работает так, как будто мы взяли в кавычки каждую из трех строк.
Присвоив значения элементам массива, мы можем обращаться к каждому из них по индексной ссылке. Так, $words [0] — это camel, $words [1] — llama, a $words [2] — alpaca. Индекс может быть и выражением, поэтому если мы присвоим $i значение 2, то элементом $words [$i] будет alpaca. (Индексные ссылки начинаются с символа $, а нес @, потому что они обозначают один элемент массива, а не весь массив.) Вернемся к нашему предыдущему примеру:
#! /usr/bin/perl -w @words == qw (camel llama alpaca); print "What is your name? " ; $name = <STDIN>; chomp ($name) ; if ($name eq "Randal") {
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) ;
$i = 0; # сначала попробуем это слово $correct = "maybe"; # догадка верна или нет? while ($correct eq "maybe") { # продолжаем проверку
if ($words [$i] eq $guess) { # верно?
$correct = "yes"; # да! } elsif ($i < 2) { # смотреть еще слова?
$i==$i+l; #в следующий раз посмотреть следующее слово } else { # больше слов нет, должно быть, неверно print "Wrong, try again. What is the secret word?"; $guess = <STDIN>; chomp ($guess) ;
$i = 0; # вновь начать проверку с первого слова }
} # конец цикла while для неверных слов } # конец цикла "не Рэндал"
Заметьте, что мы используем скалярную переменную $correct для того, чтобы показать, все еще ищем мы правильный пароль или уже нашли его.
В этой программе показан также блок eisif оператора if-then-eise. Такой конструкции нет ни в одном другом языке программирования; это сокращенная запись блока else с новым условием if, но без вложения еще одной пары фигурных скобок. Сравнение набора условий в каскадной цепочке if-eisif-eisif-eisif-eise очень характерно для языка Perl. В нем нет эквивалента оператору switch языка С или оператору case языка Паскаль, но вы можете сами без особых хлопот создать такой оператор. Подробности см. в главе 2 книги Programming Perl и на странице руководства perlsyn(1).
Разные секретные слова для разных пользователей
В предыдущем случае любой пользователь мог угадать одно из трех секретных слов и получить доступ к программе. Если мы хотим, чтобы для каждого пользователя было задано свое секретное слово, нам нужна примерно такая таблица соответствий:
Пользователь | Секретное слово |
Fred Barney Betty Wilma | camel llama alpaca alpaca |
Обратите внимание: у последних двух пользователей одинаковые секретные слова. Такое допускается.
Самый простой способ сохранить такую таблицу — использовать хеш. В каждом элементе хеша содержится отдельное скалярное значение (как и в массиве любого другого типа), но в соответствие каждому элементу хеша ставится ключ. Ключом может быть любое скалярное значение (любая строка или число, в том числе нецелые и отрицательные числа). Чтобы создать хеш под именем Swords (обратите внимание на то, что используется символ ^ вместо @) с ключами и значениями, данными в приведенной выше таблице, мы присвоим Swords значение (почти так же, как мы делали раньше с массивом):
Swords = qw( fred camel barney llama betty alpaca wilma alpaca
);
Каждая пара в этом списке представляет в хеше один ключ и соответствующее ему значение. Обратите внимание на то, что мы разбили эту процедуру присваивания на несколько строк без каких-либо символов продолжения строк, потому что пробельные символы в Perl-программах обычно никакой роли не играют.
Чтобы найти секретное слово для Betty, мы должны использовать имя Betty как ключ в ссылке на хеш Swords с помощью выражения вроде $words { "betty"}. Значение этой ссылки - alpaca, это похоже на то, что мы видели раньше, работая с другим массивом. Как и раньше, ключом может быть любое выражение, поэтому установка $person в значение betty и вычисление $words { $person} также дает в результате alpaca.
Сведя все это воедино, мы получаем такую программу:
#! /usr/bin/perl -w %words = qw ( fred camel barney llama betty alpaca wilma alpaca
) ;
print "What is your name? "; $name = <STDIN>;
chomp ( $name) ; if ($name eq "Randal") {
print "Hello, Randal! How good of you to be here!\n"; } else {
print "Hello, $name !\n"; # обычное приветствие $secretword = $words {$name}; # получить секретное слово print "What is the secret word? "; $guess = <STDIN>; chomp ($guess) ; while ($guess ne $secretword) {
print "Wrong, try again. What is the secret word? "; $guess = <STDIN>; chomp ($guess) ;
} }
Обратите внимание на то, как происходит поиск секретного слова. Если имя не найдено, то значением переменной $secretword будет пустая строка*, и мы можем использовать оператор if, чтобы задать секретное слово по умолчанию для кого-нибудь еще. Вот как это выглядит:
[ . .. остальная часть программы удалена . . . ] $secretword = $words ($name}; # получить секретное слово
if ( $secretword eq "") { # не найдено
$secretword = "groucho"; # конечно,
#можно использовать
}
print "What is the secret word? "; [. .. остальная часть программы удалена .. . ]
Обработка различных вариантов ввода секретного елова
Если вместо Randai вы введете Randai L. Schwartz или randal, то тем самым лишите Рэндала права на особое приветствие, потому что сравнение eq предполагает точное равенство. Давайте рассмотрим один способ решения задачи обработки различных вариантов ввода.
* На самом деле это значение undef, но для операции eq оно выглядит как пустая строка. Если бы в командной строке вы использовали ключ -w, то получили бы предупреждение на этот счет. Именно поэтому мы его здесь опустили.
Допустим, вы хотите найти все строки, которые начинаются со слова Randal, а не просто строку, равную Randal. В sed, awk или grep это можно сделать с помощью регулярного выражения — шаблона, определяющего совокупность соответствующих строк. Как и в sed, awk или grep, в Perl регулярным выражением, которое соответствует любой строке, начинающейся со слова Randal, будет ^Randai. Чтобы сравнить его со строкой, содержащейся в скалярной переменной $ name, мы используем операцию сопоставления:
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 .рад, и у вас будет все, что нужно.
Упражнение
Большинство глав завершаются упражнениями, ответы к которым даются в приложении А. Для этой главы ответы уже были даны выше.
1. Наберите программы-примеры и заставьте их работать. (Вам понадобится создать списки секретных слов.) Если потребуется помощь — обратитесь к местному Perl-гуру.
| Назад | Вперед |
| Содержание | Предисловие | Введение | Ссылки | Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10 | Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19 | Приложение А | Приложение Б | Приложение В | Приложение Г | |
Создание серверных приложений на языке PERL
и везде, первый наш урок
Урок 1. Создание и запуск Perl-скриптов Оператор Print Как и везде, первый наш урок очень важен для всего последующего обучения. Сегодня Вы узнаете много полезной и важной информации о CGI программировании. Начнем с того, что все свои программы Вы будете набирать не в интегрированной среде, как Вы привыкли в Pascal или C, а в текстовом редакторе. Подойдет и notepad. Это связано не с тем, что для Perl нет подобных сред, а с тем, что это благотворно скажется на усвоении материала, это сдисциплинирует Вас. Когда Вы уже будете вполне хорошо знать Perl, можете воспользоваться специальным редактором, который выложен в разделе "Download". Вот основные постулаты по CGI программированию, которые необходимо запомнить: Свою программу Вы будете сохранять в специальную папку на сервере, которая называется CGI-BIN, т.к. только из неё разрешен ЗАПУСК сценариев. Если Вы сохраните файл в другую папку, сервер передаст браузеру исходный код Вашей программы, а не результат выполнения. Расширение у CGI файлов должно быть .pl (к примеру, example.pl). Как и во всех языках программирования, в Perl есть т.н. комментарии. Для справки: комментарии это такие строки, которые не обрабатываются интерпретатором. В Perl строка считается закомментированной, если перед ней стоит знак "#". Действие знака комментария распространяется до конца текущей строки. Любая CGI программа требует ПОЛНЫЙ путь к интерпретатору Perl в её первой строке. Сервер не запустит CGI программу, если в ней не указан тип выводимых данных (пока мы ничего не выводим кроме обычного html). Чтобы вывести данные, в Perl предусмотрен оператор Print. Его синтаксис таков: print (значение); print значение; После каждого оператора должна стоять точка с запятой! Хотя это характерно почти для всех языков программирования, почему-то больше всего ошибок возникает именно из-за отсутствия этого непритязательного знака препинания в конце каждого оператора. * * * * Ну, наверное хватит с Вас теории, займемся практикой. Давайте для начала напишем скрипт, который выведет нам на экран какую-нибудь строчку. Стиль программирования на Perl не отличается от других языков: мы пишем программы сверху вниз, также как их обрабатывает Perl. #!/usr/bin/perl print "Content-Type: text/html\n\n"; print "Мир дому твоему!"; Позвольте я прокомментирую. Первая строка - путь к Perl. Без этого сервер не сможет вызвать Perl-интерпретатор. Вторая строка - печатаем строку "Content-Type: text/html". Это нужно для формирования заголовка HTML, и она гласит, что мы пытаемся передать данные в формате text/html. Форматов данных очень много, поговорим об этом подробнее в следующих уроках, а пока нужно усвоить, что передаем мы браузеру данные именно в таком формате. Третья строка - пустая. Четвертая строка - вывод строки "Мир дому твоему!". Теперь перепечатайте всё это в текстовый редактор и сохраните под каким-нибудь именем на сервере. Готово? Тогда открывайте браузер и вводите URL к скрипту. Это будет выглядеть примерно так: "http://localhost/cgi-bin/test.pl". Разумеется, сервер должен работать, ниаче ничего не откроется. Увидели строчку на экране? Или увидели их аж 10 ;-) ? Если Вы всё правильно набрали, ошибок возникнуть не должно. В разделе "Help" есть небольшое руководство по поиску и устранению ошибок. Теперь нажмите правой кнопкой мыши на рабочем поле браузера и выберите пункт "Просмотр HTML кода". Откроется Notepad, где будет содержаться именно то, что передал Perl серверу, а он в свою очередь браузеру. Запомните эту процедуру, т.к. Вы будете пользоваться ей довольно часто. * * * * А теперь попробуйте самостоятельно написать скрипт, выводящий на экран 3 произвольные строчки, расположенные одна под одной. P.S. Этот урок может показаться Вам немного "сжеванным", это естественно. С первого раза Вы можете не разобраться с материалом, по этому перечитайте его еще несколько раз, обдумывая каждую строчку. Заведите себе тетрадь, в которой будуте конспектировать особенно непонятные места. Это поможет Вам легче разобраться с вопросами. Дополнительная информация: [введение в Perl] [Содержание] [Вступление] [необходимое для начала изучения] [урок 1] [урок 2] [урок 3] [урок 4] [урок 5] [урок 6] [урок 7] [урок 8] [урок 9] [урок 10] [урок 11] [урок 12] [урок 13] [урок 14] [урок 15] [урок 16] [урок 17] [урок 18] [урок 19] [урок 20] [урок 21] [урок 22] [урок 23] [урок 24] [урок 25] [урок 26] [урок 27] [урок 28] [правила хорошего тона] [приложение A] [приложение Б] [приложение В] [заключение] [благодарности] |
Copyright (c) ClericICN, 2002 |
в отличие от Pascal или
Урок 2. Типы данных в Perl, переменные, одинарные и двойные кавычки Perl - не типизированный язык программирования, в отличие от Pascal или C (хотя очень похож на последний). В Perl есть 3 основных типа переменных: скаляры, массивы и хеши (ассоциативные массивы). Сегодня подробно разберем первый из типов. Perl не требует обязательного объявления переменных. Переменная автоматически определяется как только встретилась в контексте. Т.к. Perl не типизированный язык, для него нет таких понятий как строковая переменная, числовая, байтовая и т.д. Это отрадно! В зависимости от контекста, её содержимое автоматически конвертируется в числовое или текстовое. Помимо этого скаляр может иметь и логическое значение True или False (Истина или Ложь). Если скаляр НЕ равен 0 или пустой строке, он имеет логическое True. В противном случае - False. Обычная скалярная переменная начинается со спец. символа $, её имя должно начинаться только с латинской буквы или символа нижнего подчеркивания. В состав имени переменной могут входить также и цифры. Регистр букв тоже важен. К примеру $Var1 и $var1 это разные переменные. Для присваивания переменной какого-то значения, служит знак присваивания. Как и в Basic, это символ равенства: "=". Работает он также как и в остальных языках, по этому не требует описания. Чтобы присвоить переменной текстовую строку, её заключают в кавычки. Perl допускает применение как одинарных ('), так и двойных (") кавычек. Разница лишь в том, что в двойных кавычках переменные интерполируются (подставляются значения переменных), а в одинарных - нет. Для присваивания переменным числовых значений кавычек не требуется. * * * * Теперь давайте вспомним предыдущий наш урок. Попробуем модернизировать нашу программу с учетом полученных сегодня знаний. Изначально она выглядела таким образом: #!/usr/bin/perl print "Content-Type: text/html\n\n"; print "Мир дому твоему!"; Присвоим строку "Мир дому твоему!" какой-нибудь переменной, и будем выводить её значение. Программа получится следующей: #!/usr/bin/perl #programm 1 print "Content-Type: text/html\n\n"; $message = "Мир дому твоему!"; print "$message"; Пока пользы от этого мало. Теперь напишем программу, которая демонстрирует работу одинарных и двойных кавычек. #!/usr/bin/perl #programm 2 print "Content-Type: text/html\n\n"; $var1 = 16; $var2 = "$var1 овец"; print 'Значение переменной $var1: '; print "$var1<br>"; print 'Значение переменной $var2: '; print "$var2"; Небольшой комментарий. Сначала мы присваиваем число 16 переменной $var1. Как было сказано ранее, числа не нужно обрамлять кавычками. Следующая строка требует объяснения. Т.к. кавычки у нас стоят двойные, переменные в них интерполируются. Таким образом переменной $var2 мы присваиваем строку "16 овец". Далее выводим строку "Значение переменной $var1: ". Т.к. кавычки стояли одинарные, переменная не интерполируется. Затем выводим значение переменной $var1 и добавляем HTML тег "<BR>" для переноса текста на следующую строку. Далее ситуация повторяется. * * * * Ответьте на вопросы: Какие символы может содержать название переменной? Когда обычная скалярная переменная имеет логическое "Ложь"? Какие из ниже приведенных переменных написаны неправильно или используются неправильно? Объясните, почему Вы так считаете? $Var1="256"; $Var2="33.2"; $Var3='Var1, '; $_Var=some text here; $_var=some text here; $_var='some text here'; $-var="some text here"; [Содержание] [Вступление] [необходимое для начала изучения] [урок 1] [урок 2] [урок 3] [урок 4] [урок 5] [урок 6] [урок 7] [урок 8] [урок 9] [урок 10] [урок 11] [урок 12] [урок 13] [урок 14] [урок 15] [урок 16] [урок 17] [урок 18] [урок 19] [урок 20] [урок 21] [урок 22] [урок 23] [урок 24] [урок 25] [урок 26] [урок 27] [урок 28] [правила хорошего тона] [приложение A] [приложение Б] [приложение В] [заключение] [благодарности] |
Copyright (c) ClericICN, 2002 |
я расскажу об операциях которые
Урок 3. Операции с числами и строками, логические выражения В этом уроке я расскажу об операциях которые можно проводить с числами и строками. Со многими из них Вы уже встречались в других языках программирования. Начну я, пожалуй, с операциями над числами. |
++ | - инкремент | $a ++; # $a = $a + 1 |
-- | - дикремент | $a --; # $a = $a - 1 |
** | - возведение в степень | $a = 2**10; # $a = 1024 |
* | - умножение | $a = 5 * 6; # $a = 30 |
/ | - деление | $a = 5 / 2; # $a = 2.5 |
% | - получение остатка от деления | $a = 5 % 2; # $a = 1 |
+ | - сложение | $a = 5 + 5; # $a = 10 |
- | - вычитание | $a = 2 - 5; # $a = -3 |
Операции над строками.
. | конкатенация | $a = "abc"."123"; # $a = "abc123" |
Для удобства оперирования с одним операндом, придуман оператор "X=", где X может принимать значение любого из вышеперечисленного операторов. Как это работает: $a .= 5, тоже самое что и $a = $a . 5;
$u += 8, тоже самое что и $u = $u + 8 и т.д.
Логические операторы позволяют проверить на истинность некоторое условие или совокупность условий. Если условие истинно, оператор возвращает True, если ложно - False.
< | меньше | $a = 5 < 2; # $a = False |
> | больше | $a = 5 > 2; # $a = True |
== | равно | $a = 3 == 3; # $a = True |
<= | меньше или равно | $a = 4 <= 8; # $a = True |
>= | больше или равно | $a = 4 >= 8; # $a = False |
!= | не равно | $a = 5 != 5; # $a = False |
lt | строковое меньше | $a = "11" lt "a"; # $a = True |
gt | строковое больше | $a = "11" gt "a"; # $a = False |
eq | строковое равно | $a = "o" eq "o"; # $a = True |
ne | строковое не равно | $a = "o" ne "o"; # $a = False |
le | строковое меньше или равно | $a = "11" le "a"; # $a = True |
ge | строковое больше или равно | $a = "11" ge "a"; # $a = False |
Таблицы истинности операторов AND, OR и XOR. В них показано что возвращает логический оператор в зависимости от значений сравниваемых выражений
AND | OR | XOR | ||||||
True | True | True | True | True | True | False | True | True |
False | False | True | True | True | Flase | True | True | Flase |
False | True | False | True | False | True | True | False | True |
False | False | False | False | False | False | False | False | False |
Программа, демонстрирующая работу условных операторов.
#!/usr/bin/perl #programm 3 print "Content-Type: text/html\n\n";
$var1 = 5; $var2 = 3; $a=(($var1 > $var2) and ($var1 != $var2)); print $a;
Работает она следующем образом: сначала вычисляются логические выражения в скобках, затем сравниваются между собой. Оба выражения истины. Из таблицы истинности видно: если оба выражения истины, логический оператор AND возвращает True, что и выведется нам на экран.
Несколько слов о преобразовании строк в числа и обратно.
Если строковое значение используется как операнд в операции с числами (например, в операции сложения), Perl автоматически преобразует эту строку в эквивалентное числовое значение, как если бы оно было введено в виде десятичного числа с плавающей запятой. Нечисловые окончания и начальные пробельные символы игнорируются, поэтому " 123. 4 5 fed" (с начальным пробелом) без какого-либо предупреждения преобразуется в "123. 45". Самый крайний из подобных случаев — когда нечто, не являющееся числом, вообще без предупреждения преобразуется в нуль (как строка fed, которую мы использовали здесь как число).
Если же, наоборот, там, где должно быть строковое значение, вводится числовое (например, в операции конкатенации строк), это числовое значение конвертируется в строку, которая была бы выведена на экран вместо него. Например, если вы хотите конкатенировать строку х с результатом умножения 4 на 5, это можно записать как
"X" .(4*5); # то же самое, что "X" . 20, или "Х20"
(Помните, что круглые скобки заставляют сначала выполнить 4*5, а затем выполнять операцию конкатенации.)
Другими словами, вам не нужно (в большинстве случаев) беспокоиться о том, что указано в качестве операнда — строка или число, поскольку Perl выполняет все необходимые преобразования самостоятельно.
* * * *
Попробуйте по памяти переписать таблицы истинности для каждого из операторов (AND, OR, XOR).
Напмшите программу, которая выводит значения 3-х переменных в четвертую таким образом, чтобы значение 3-й переменной стояло сразу за начением 2-й, а значение 2-й - за первым.
[Содержание] [Вступление] [необходимое для начала изучения] [урок 1] [урок 2] [урок 3] [урок 4] [урок 5] [урок 6] [урок 7] [урок 8] [урок 9] [урок 10] [урок 11] [урок 12] [урок 13] [урок 14] [урок 15] [урок 16] [урок 17] [урок 18] [урок 19] [урок 20] [урок 21] [урок 22] [урок 23] [урок 24] [урок 25] [урок 26] [урок 27] [урок 28] [правила хорошего тона] [приложение A] [приложение Б] [приложение В] [заключение] [благодарности]
Copyright (c) ClericICN, 2002
в парные фигурные скобки. Блок
Урок 4. Составной и условный операторы Блок операторов — это последовательность операторов, заключенная в парные фигурные скобки. Блок операторов выглядит следующим образом: { оператор 1; оператор 2; оператор 3; ..... оператор n; } Perl выполняет операторы по очереди, начиная с первого и кончая последним. Синтаксически блок операторов принимается вместо любого одиночного оператора, но обратное не верно. Точка с запятой после последнего оператора ставить не обязательно. Условный оператор IF IF (EXPR){operator} IF (EXPR){operator} else {operator1} IF (EXPR){operator} elsif (EXPR2){operator2} ... else{operator3} {operator} IF (EXPR) Условный оператор позволяет проверять некоторое условие и в зависимости от результатов проверки выполнять то или иное действие. Таким образом, условный оператор - это средство позволяющее ветвись вычислительный процесс. Условный оператор работает по следующему алгоритму: Вначале вычисляется условное выражение EXPR. Если оно истинно, выполняется блок "operator" и программа выполняется дальше. Если EXPR ложное, проверяется условие EXPR2. Если оно истинное, выполняется блок "operator2" и программа выполняется дальше, иначе выполняется блок operator3. Примечание: проверяемых условий может быть сколько угодно. С помощью этого свойства оператора IF можно реализовывать многочисленное ветвление программы. Оператор Unless Unless имеет такой же синтаксис как и IF, но отличается от него тем, что блоки выполняются не при истинных значениях условий, а при ложных. * * * * Следующая программа демонстрирует работу оператора IF в сокращенной форме: #!/usr/bin/perl #programm 4 print "Content-Type: text/html\n\n"; $var = 23; if ($var%2){$mess="не четное"}else{$mess="четное"} print "$var -$mess число"; Как работает оператор IF в этой программе: Как Вы помнете, выражение принимает значение True если оно не равно 0 или пустой строке. Наличие остатка от деления на 2 является признаком его нечетности. Значит если есть остаток, выражение истинно, и выполняется первый блок. Если остатка нет, значение ложно и выполняется второй блок. * * * * Напишите программу, которая сообщает о состоянии воды при различных температурах (счисление по Кельвину). Температуру задайте в переменной $temperature. Сделайте проверки на непригодные значения (температура по K никогда не бывает отрицательной.) Ответьте на вопросы: Посчитается ли ошибкой, если после последнего оператора программы не стоит точка с запятой? О чем это говорит? Как продолжет свое выполнение программа, если ни одно из условий следующей конструкции не будет истинно: IF (EXPR){operator} elsif (EXPR2){operator2} ? Перепишите программу 4, чтобы вместо оператора IF использовался оператор Unless. Дополнительная информация: [Управляющие структуры ] [Управляющие структуры 2 ] [Содержание] [Вступление] [необходимое для начала изучения] [урок 1] [урок 2] [урок 3] [урок 4] [урок 5] [урок 6] [урок 7] [урок 8] [урок 9] [урок 10] [урок 11] [урок 12] [урок 13] [урок 14] [урок 15] [урок 16] [урок 17] [урок 18] [урок 19] [урок 20] [урок 21] [урок 22] [урок 23] [урок 24] [урок 25] [урок 26] [урок 27] [урок 28] [правила хорошего тона] [приложение A] [приложение Б] [приложение В] [заключение] [благодарности] |
Copyright (c) ClericICN, 2002 |
формирования циклов) служат для программирования
Урок 5. Операторы повторения с заданным количеством итераций Операторы повторения ( формирования циклов) служат для программирования циклически повторяющегося участка кода. Оператор X operator x (кол-во итераций); x - оператор позволяющий выполнить строго заданное кол-во раз только один оператор. Оператор FOR for (EXPR1; EXPR2; EXPR3) {operator} Работа оператора FOR в Perl аналогична работе его в C и JavaScript: Сначала вычисляется выражение EXPR1. Оно содержит в себе переменную цикла. Затем вычисляется EXPR2. Если его значение True, выполняется блок операторов. Иначе цикл завершается и программа выполняется дальше. После выполнения блока операторов вычисляется выражение EXPR3, затем EXPR2. Если его значение True, выполняется блок операторов. Иначе цикл завершается. И так далее до того как EXPR2 не станет False. * * * * Простейшая программа, демонстрирующая работу оператора FOR. #!/usr/bin/perl #programm 5 print "Content-Type: text/html\n\n"; for($i=0;$i аргумент цикла читается следующем образом: "переменной $i присваиваем значение 0. Выполнять цикл пока $i меньше или равна 1000. Каждую итерацию $i инкрементировать". Эта программа вываливает на экран все числа начиная с 0 и заканчивая 1000. Теперь поставим пере собой задачу вывести таблицу Пифагора в окно браузера. Диапазон чисел по оси X и Y совпадает и равен 2..9. #!/usr/bin/perl #programm 6 print "Content-Type: text/html\n\n"; print '<font face="courier new">'; for($i=1;$i<=9;$i++){ for($u=1;$u<=9;$u++){ if(($u*$i)/10 < 1){print" "} unless($u*$i==1){print $u*$i}else{print" "} print " "; } print"<br>"; } print '</font>'; Разберем как эта программа работает. Выводим строку "<font face="courier new">". Этим мы выбираем тип шрифта, каким будут выводиться данные. Начинаем новый цикл, в котором переменная $i изменяется от 1 до 9. В него "вкладываем" еще один цикл, в котором переменная $u также изменяется от 1 до 9. Проверяем условие: если произведение переменных циклов, деленных на 10 меньше единицы, выводим пустой символ (пробел), иначе ничего не делаем. Такая хитрая операция нужна для того чтобы все числа таблицы Пифагора имели одинаковые знакоместа. Это позволит построить правильно организованную таблицу. Проверяем условие: если произведение переменных массивов не равно единице выводим их произведение, иначе выводим пустой символ. Эта операция нужна чтобы исключить единицу из наших осей таблицы. Затем выводим пустой символ. Это нужно чтобы удалить столбцы таблицы друг от друга на один символ. Когда внутренний цикл закончит выполняться, закончится и вывод первой строки. Выполнится вывод тэга переноса строки и внутренний цикл снова начнет выполняться но уже с другим значением переменной $i. Когда внешний цикл отработает все 9 итераций, а внутренний 81-у итерацию, будет сделан вывод закрывающего тега font и программа успешно завершит свое выполнение, оставив на экране браузера так всеми ненавистную таблицу Пифагора. * * * * Модернизируйте программу таким образом, чтобы таблица Пифагора выводилась в виде HTML таблицы. Реализуйте алгоритм закрашивания ячеек значений в шахматном порядке, а осей координат в один какой-нибудь цвет. Как поведет себя цикл FOR, если в его теле изменять переменную цикла так, чтобы условия цикла всегда было истинно? Дополнительная информация: [Управляющие структуры ] [Управляющие структуры 2 ] [Содержание] [Вступление] [необходимое для начала изучения] [урок 1] [урок 2] [урок 3] [урок 4] [урок 5] [урок 6] [урок 7] [урок 8] [урок 9] [урок 10] [урок 11] [урок 12] [урок 13] [урок 14] [урок 15] [урок 16] [урок 17] [урок 18] [урок 19] [урок 20] [урок 21] [урок 22] [урок 23] [урок 24] [урок 25] [урок 26] [урок 27] [урок 28] [правила хорошего тона] [приложение A] [приложение Б] [приложение В] [заключение] [благодарности] |
Copyright (c) ClericICN, 2002 |
Цикл while выполняет operator1 до
Урок 6. Операторы повторения с неизвестным заранее количеством итераций Оператор WHILE while (EXPR) {operator1} continue {operator2} Цикл while выполняет operator1 до тех пор пока EXPR = true. Блок после continue выполняется всегда перед тем как вычисляется логическое выражение EXPR. Это подобно EXPR3 в предложении for, поэтому в этом блоке удобно изменять счетчики и флаги цикла. Оператор Until Этот оператор по синтаксису ничем не отличается от While, но работает пока EXPR = False. Оператор do do {operator} while (EXPR) do {operator} until (EXPR) Оператор while/until проверяет условие в начале каждого цикла, до входа в него. Если результат проверки условия — "ложь", цикл не будет выполнен вообще. Иногда возникает необходимость проверять условие не в начале, а в конце цикла. Для этого в Perl есть оператор do {} while, который очень похож на обычный оператор while, за исключением того, что он проверяет выражение только после однократного выполнения цикла. * * * * Приведенная ниже программа демонстрирует работу оператора While. #!/usr/bin/perl #programm 7 print "Content-Type: text/html\n\n"; $i=1; while($i<9){ print "$i X 7 = ".$i*7; print "<BR>"; }continue{$i++} * * * * Модернизируйте программу, которая выводит таблицу Пифагора (Всё равно какую) так, чтобы вместо оператора For в ней использовались операторы While и Until. Дополнительная информация: [Управляющие структуры ] [Управляющие структуры 2 ] [Содержание] [Вступление] [необходимое для начала изучения] [урок 1] [урок 2] [урок 3] [урок 4] [урок 5] [урок 6] [урок 7] [урок 8] [урок 9] [урок 10] [урок 11] [урок 12] [урок 13] [урок 14] [урок 15] [урок 16] [урок 17] [урок 18] [урок 19] [урок 20] [урок 21] [урок 22] [урок 23] [урок 24] [урок 25] [урок 26] [урок 27] [урок 28] [правила хорошего тона] [приложение A] [приложение Б] [приложение В] [заключение] [благодарности] |
Copyright (c) ClericICN, 2002 |
Урок 7. Метки, оператор перехода,
Урок 7. Метки, оператор перехода, операторы управления циклами Оператор GOTO goto метка; Можно теоретически показать, что рассмотренных операторов вполне достаточно для написания программ любой сложности. В этом отношении наличие в языке оператора перехода кажется излишнем. Более того технология структурного программирования основана на принципе "программирование без GOTO": считается, что использование оператора перехода затрудняет понимание программы, делает её запутанней и сложной в отладке. Тем не менее, в некоторых случаях использование оператора перехода может упростить программу. В Perl метки обозначаются ЗАГЛАВНЫМИ латинскими буквами и отделяются от следующего за ним оператора двоеточием (:). Оператор Next next - подобен continue в С. Переходит к началу текущего цикла (блок continue вычисляется) т.е. повторяет итерацию. Оператор Last last - подобен оператору break в языке С. Немедленно прерывает цикл. Блок continue пропускается Оператор Redo redo - начать новый цикл не вычисляя EXPR и не выполняя continue блок. * * * * Пример применения оператора next: #!/usr/bin/perl print "Content-Type: text/html\n\n"; M1: while ($i < 6) { ++$i; # Увеличиваем счетчик на 1 next M1 if $i < 3; # Переходим в начало если $i < 3 ++$i; # иначе увеличиваем счетчик еще раз на 1 } continue { print "$i "; # Печатаем $i } # получаем 1 2 4 6 Пример применения оператора last: #!/usr/bin/perl print "Content-Type: text/html\n\n"; M1: while ($i < 6) { ++$i; # Увеличиваем счетчик на 1 last M1 if $i > 3; # Выход из цикла если $i > 3 ++$i; # иначе увеличиваем счетчик еще раз на 1 } continue { print "$i "; # Печатаем $i } # результат: 2 4 Пример применения оператора redo: #!/usr/bin/perl print "Content-Type: text/html\n\n"; M1: while ($i < 6) { ++$i; # Увеличиваем счетчик на 1 redo M1 if $i == 3; # Далее пропустить для $i = 3 ++$i; # иначе увеличиваем счетчик еще раз на 1 } continue { print "$i "; # Печатаем $i } # получим 2 5 7 * * * * Напишите программу, выводящую все простые числа из диапазона (-28..53) включительно. Есть ли вообще простые числа в этом диапазоне? Для справки: простое число, это такое число, которое дилится без остатка только на себя и еденицу. Напишите программу, вычисляющую произведение двух соседних числа ряда из диапазона (-28..53), и если это число делется без остатка на 7, выводит результат на экран и увеличивает переменную цикла на 2. [Содержание] [Вступление] [необходимое для начала изучения] [урок 1] [урок 2] [урок 3] [урок 4] [урок 5] [урок 6] [урок 7] [урок 8] [урок 9] [урок 10] [урок 11] [урок 12] [урок 13] [урок 14] [урок 15] [урок 16] [урок 17] [урок 18] [урок 19] [урок 20] [урок 21] [урок 22] [урок 23] [урок 24] [урок 25] [урок 26] [урок 27] [урок 28] [правила хорошего тона] [приложение A] [приложение Б] [приложение В] [заключение] [благодарности] |
Copyright (c) ClericICN, 2002 |
Урок 8. Список. Массив. Работа
Урок 8. Список. Массив. Работа с массивом Список — это упорядоченные скалярные данные. Массив - переменная, которая содержит список. Каждый элемент массива — это отдельная скалярная переменная с независимым скалярным значением. Массивы могут иметь любое число элементов. Минимально возможный массив не имеет элементов вообще, тогда как максимально возможный может заполнять всю наличную память. Список состоит из значений, отделенных друг от друга запятыми и заключенными в круглые скобки. Эти значения образуют элементы списка. (1,2,perl,3.14159) - список из 4-х значений. Элементы списка не обязательно должны быть константами. Это могут быть выражения, которые вычисляются при каждом использовании списка. Например: ($a,$a**$b,$b+33) -список из 3-х элементов. Список можно присвоить особому типу переменных - массиву. Такая переменная начинается с символа "@", а не "$" как обычная скалярная переменная. Обращение к элементам массива идет через индекс массива. Каждому элементу массива соответствует скалярная переменная с именем массива. Пора Вам рассказать о том, что оператор print способен выводить не только скалярные данные, но и списки. Приведенная ниже программа демонстрирует работу с массивом. #!/usr/bin/perl print "Content-Type: text/html\n\n"; @ar=(1,2,3); $ar[0]++; print @ar,"<BR>"; print $ar[0],$ar[1],$ar[2]; Первый элемент массива имеет индекс 0, значит последний - общее число элементов минус единица. Можно присвоить какой-нибудь скалярной переменной массив. В таком случае в переменную запишется кол-во элементов этого массива. При присваивании скалярной переменной списка, в неё записывается последний эл-т списка. Проверим: #!/usr/bin/perl print "Content-Type: text/html\n\n"; @array=(0,2,4.5,Perl); $ar=@array; $ar1=(0,2,4.5,Perl); print @array,"<br>"; print $ar,"<br>"; print $ar1,"<br>" Списки можно присваивать не только массивам и скалярам, но и, собственно, спискам. Если в левом списке эл-тов больше чем в правом, последние эл-ты этого списка уничтожаются. Если в левом списке эл-тов меньше чем в правом, часть эл-тов правого списка ничему не присвоится. В списки можно вкладывать и массивы: #!/usr/bin/perl print "Content-Type: text/html\n\n"; $d="22"; @ar=(3,2,1); ($a,$b,$c,$d)=@ar; print ($a,$b,$c,$d,"<BR>"); @ar1=(@ar,"It works!"); print @ar1; Если в качестве значения индекса массива указать список элементов, можно получить все их значения в виде списка. Такое использование называется срезом массива. Отрицательное значение индекса обозначает номер позиции элемента с конца: #!/usr/bin/perl print "Content-Type: text/html\n\n"; @array=(1,2,3,4); print @array[0,-3]; Оператор Foreach foreach VAR (LIST) {operator} Переменной VAR присваивается поочередно каждый элемент списка LIST и выполняется блок. Если VAR опущено, то элементы присваиваются встроенной переменной $_. Если в теле блока изменять значение VAR то это вызовет изменение и элементов списка т.к. VAR фактически указывает на текущий элемент списка. Вместо слова foreach можно писать просто for - это слова синонимы. оператор .. Элемент списка может включать операцию конструктора списка. Это два скалярных значения, разделенных двумя точками. Данная операция создает список значений, начиная с левого скалярного значения и кончая правым скалярным значением, с шагом 1. Если правый скаляр меньше левого, то список будет пустым, так как в обратном направлении отсчет вести нельзя. Если последнее значение не соответствует целому числу шагов, то список заканчивается там, где приращение на единицу привело бы к появлению числа, не принадлежащего заданному диапазону. Ниже приведенная программа демонстрирует работу операторов foreach и "..": #!/usr/bin/perl print "Content-Type: text/html\n\n"; @array=(1..10); foreach $a(@array){ print ($a,"<BR>"); } * * * * Как изменится длина 8-и элементного массива, если 2-ум его ячейкам присвоить пустую строку ("") ? Если значения ячеек совпадают с номерами их индексов, то какое значение записано в 9-й ячейке массива? Анологичный 9-и элементный массив. Какое значение будет записано в ячейке с номером "-1" ? Какое число элеменотов вернет такая конструкция: ('3.8'..'9.1') ? Напишите программу, выводящую все простые числа из диапазона (-28..53) включительно, используя конструктор списка и оператор Foreach. Какое минимальное кол-во переменных требуется для написания такой программы? [Содержание] [Вступление] [необходимое для начала изучения] [урок 1] [урок 2] [урок 3] [урок 4] [урок 5] [урок 6] [урок 7] [урок 8] [урок 9] [урок 10] [урок 11] [урок 12] [урок 13] [урок 14] [урок 15] [урок 16] [урок 17] [урок 18] [урок 19] [урок 20] [урок 21] [урок 22] [урок 23] [урок 24] [урок 25] [урок 26] [урок 27] [урок 28] [правила хорошего тона] [приложение A] [приложение Б] [приложение В] [заключение] [благодарности] |
Copyright (c) ClericICN, 2002 |
я дам вам довольно трудный
Урок 9. Метасимволы В этом уроке я дам вам довольно трудный для понимания материал, но который необходимо усвоить. Без знания этого материала вы не сможете проводить дальнейшее изучение Perl. И так, что же такое метасимволы? Метасимвол - это особая последовательность символов, которая выполняет роль непечатаемого символа, либо какую-либо другую специфическую роль. С некоторыми такими символами Вы уже знакомы, это "$", которым мы обозначаем скалярные переменные, и "@", которым обозначаем массив. Другие часто встречающиеся метасимволы приведены в таблице ниже. |
\t | символ табуляции |
\n | символ возврата каретки и перевода строки |
\b | backspace (забой) |
\034 | восьмеричный символ |
\xla | шестнадцатеричный символ |
\l | нижний регистр следующего символа |
\u | верхний регистр следующего символа |
Давайте подробнее поговорим о метасимволе "\n". Это ни что иное как признак конца строки. К примеру, когда Вы набираете текст в текстовом редакторе и нажимаете "Enter" для перехода к новой строке, вы просто добавляете этот символ.
Помните, когда мы проходили двойные и одинарные кавычки я говорил Вам, что в двойных кавычках переменные интерполируются, а в одинарных - нет?! Так вот, это не совсем так. Интерполируются не переменные, а метасимволы. Теперь Вы должны это запомнить.
Давайте рассмотрим такую ситуацию, когда нам необходимо вывести на экран последовательность символов "\n", а не метасимвол. В таком случае выводимую строку можно заключить в одинарные кавычки и интерполяции не произойдет. А если ситуация не позволяет этого делать? Допустим, в этой же строке нам нужно вывести и значение некоторой переменной? Безусловно, мы можем заключить переменную в двойные кавычки, а последовательность "\n" в одинарные и выводить всё поотдельности. Но делать этого не следует, т.к. в Perl предусмотрен еще один метасимвол, говорящий интерпретатору, что следующий символ не подлежит интерполяции. Другими словами этот символ отменяет действие следующего за ним метасимвола. Таким метасимволом является "\" (обратный слеш). Обратите внимание на следующий пример:
print "...и он спросил: "А где ж твоя голова?"..."; - выполнение такой строки приведет к неминуемой ошибке. С точки зрения Perl, строка для вывода началась с открытия первой двойной кавычки и закончилась второй, а не последней. Для Perl кавычки в данном случае - флаги, он рассматривает их не как обычные символы, предназначенные для вывода, а как метасимволы. С помощью символа "\" можно снизить пристрастность Perl к наблюдению за кавычками. Посмотрите как легко можно превратить кавычки в обычные символы:
print "...и он спросил: \"А где ж твоя голова?\"...";. С точки зрения Perl, эта строка безупречна и не содержит ошибок.
Также обстоят дела и с последовательностью "\n". Достаточно поставить перед ней обратные слеш, и она будет восприниматься как группа обычных символов: "\\n".
Обратным слешом удобно пользоваться когда речь идет о выведении HTML кода с подстановкой переменных, к примеру тега <FONT>: print "<font size=\"$size\" color=\"black\" face=\"Times new roman\">"; . В зависимости от значения переменной $size выводимая строка может быть любой. К примеру, если её значение - 4, строка получится следующей:
<font size="4" color="black" face="Times new roman">
Использование \n при выводе страниц
Вспомните урок 5, где мы изучали оператор повторения FOR. Запустите программу, отображающую таблицу Пифагора и посмотрите в браузере исходный текст HTML. Вы увидите, что весь HTML файл написан в одну строку. Согласитесь, что это очень неудобно с точки зрения анализа выводимых данных.
Давайте модернизируем программу таким образом, чтобы она выводила HTML текст в несколько строк. Для этого достаточно поместить метасимвол "\n" после перевода строки тегом <BR>:
#!/usr/bin/perl #programm 8 print "Content-Type: text/html\n\n";
print '<font face="courier new">'; for($i=1;$i<=9;$i++){ for($u=1;$u<=9;$u++){ if(($u*$i)/10 < 1){print" "} unless($u*$i==1){print $u*$i}else{print" "} print " "; } print"<br>\n"; } print '</font>';
Вот теперь полный порядок!
* * * *
Напишите программу, которая выводит несколько произвольных E-mail адресов по одному на каждой строке. Используйте тег <pre> и символ возврата каретки для форматирования.
Напишите программу, которая выводит строку, заданную в переменной. Первый символ строки должен быть в верхнем регистре.
Примечание: это будет работать только со строками, содержащими английские символы.
[Содержание] [Вступление] [необходимое для начала изучения] [урок 1] [урок 2] [урок 3] [урок 4] [урок 5] [урок 6] [урок 7] [урок 8] [урок 9] [урок 10] [урок 11] [урок 12] [урок 13] [урок 14] [урок 15] [урок 16] [урок 17] [урок 18] [урок 19] [урок 20] [урок 21] [урок 22] [урок 23] [урок 24] [урок 25] [урок 26] [урок 27] [урок 28] [правила хорошего тона] [приложение A] [приложение Б] [приложение В] [заключение] [благодарности]
Copyright (c) ClericICN, 2002
Урок 10. Встроенные переменные, получение
Урок 10. Встроенные переменные, получение данных методом GET Когда сервер выполняет скрипт, для него создаются некоторые переменные окружения, среди которых как локальные данные (адрес скрипта на сервере, версия П/О сервера), так и данные полученные с HTTP запросом (версия браузера, IP удаленной машины и т.д.) . Знать эти данные для CGI программиста просто жизненно важно. Вот эти переменные: |
$ENV{'REQUEST_METHOD'} | Это одно из самых главных поле используемое для определения метода запроса HTTP. Протокол HTTP использует методы GET и POST для запроса к серверу. Они отличаются тем что при методе GET запрос является как бы частью URL т.е. http://..../cgi-bin/myscript.cgi?param=value а при методе POST данные передаются в теле HTTP-запроса (при GET тело запроса пусто). Для нас пока интересен только метод GET. |
$ENV{'QUERY_STRING'} | Это строка запроса при методе GET. |
$ENV{'CONTENT_TYPE'} | Тип тела запроса. |
$ENV{'REMOTE_ADDR'} | IP-Адрес удаленного Хоста, делающего данный запрос. |
$ENV{'REMOTE_HOST'} | Если запрашивающий Хост имеет доменное имя, то эта переменная содержит его, в противном случае -тот же самый IP-адресс что и REMOTE_ADDR. |
$ENV{'SCRIPT_NAME'} | Содержит URL адрес файла скрипта. |
$ENV{'SCRIPT_FILENAME'} | Полный путь к скрипту на сервере. |
$ENV{'SERVER_NAME'} | Имя серера ,чаще всего доменное как www.microsoft.com ,но в редких случаях за неимением такового может быть IP-адресом. |
$ENV{'SERVER_PORT'} | TCP-Порт сервера использующийся для соединения .По умолчанию HTTP-порт 80, хотя может быть в некоторых случаях другим. |
$ENV{'SERVER_SOFTWARE'} | Программное обеспечение сервера. |
$ENV{'AUTH_TYPE'} $ENV{'REMOTE_USER'} |
Эти переменные определены в том случае, когда запрошенный ресурс требует аутентификации пользователя. |
$ENV{'HTTP_ACCEPT'} | Давая запрос на сервер браузер обычно рассчитывает получить информацию определенного формата, и для этого он в заголовке запроса указывает поле Accept:, Отсюда скрипту поступает список тех MIME, которые браузер готов принять в качестве ответа от сервера. |
$ENV{'HTTP_USER_AGENT'} | Браузер обычно посылает на сервер и информацию о себе, чтоб базируясь на знании особенностей и недостатков конкретных браузеров CGI-скрипт мог выдать информацию с учетом этого. Например, разные браузеры могут поддерживать или не поддерживать какие-то HTML тэги. |
$ENV{'CONTENT_LENGTH'} | Длина в байтах тела запроса. |
$ENV{'GATEWAY_INTERFACE'} | Версия протокола CGI. |
$ENV{'SERVER_PROTOCOL'} | Версия HTTP протокола сервера. |
Получение данных методом GET
Суть метода GET заключается в передаче некоторого объема информации вместе с URL скрипта. Для отделения URL от передаваемой информации используют знак "?" (вопросительный знак). Неплохо бы чисто формально напомнить, что все пробелы заменяются в URL на знак '+', а все специальные и непечатные символы на последовательность %hh ,где hh-шестнадцатиричный код символа. Методом GET можно одновременно передавать несколько значений. Как это делается я расскажу вам позже, а пока будем довольствоваться одним передаваемым значением.
* * * *
Напишем программу, которая выводит квадрат числа, если передаем ей число, или строку, если передаем текстовую строку. Передавать данные следует примерно так: "http://.../cgi-bin/script.pl?256"
#!/usr/bin/perl
print "Content-Type: text/html\n\n";
if ($ENV{'QUERY_STRING'}**2 == 0){print $ENV{'QUERY_STRING'}}
else{print $ENV{'QUERY_STRING'}**2}
И еще одна программка, выводящая некоторые сведения о Вашем сервере:
#!/usr/bin/perl #programm 9 print "Content-Type: text/html\n\n";
@array=($ENV{'SERVER_SOFTWARE'},$ENV{'SERVER_NAME'}); @array=(@array,$ENV{'SCRIPT_FILENAME'},$ENV{'HTTP_USER_AGENT'}); print "<table>"; foreach (@array){ print "<tr><td>$_</td></tr>\n"; } print "</table>"
* * * *
Попробуйте написать программу, выводящую таблицу Пифагора с максимальным значением по оси X и Y, передаваемым методом GET.
Сделайте так, чтобы ячейки закрашивались в шахматном порядке, а оси имели черный цвет.
Напишите программу, которая выводит символьную строку, переданную скрипту только в том случае, если IP удаленной машины не соответствует 127.0.0.1 .
[Содержание] [Вступление] [необходимое для начала изучения] [урок 1] [урок 2] [урок 3] [урок 4] [урок 5] [урок 6] [урок 7] [урок 8] [урок 9] [урок 10] [урок 11] [урок 12] [урок 13] [урок 14] [урок 15] [урок 16] [урок 17] [урок 18] [урок 19] [урок 20] [урок 21] [урок 22] [урок 23] [урок 24] [урок 25] [урок 26] [урок 27] [урок 28] [правила хорошего тона] [приложение A] [приложение Б] [приложение В] [заключение] [благодарности]
Copyright (c) ClericICN, 2002
Урок 11. HTML формы, функция
Урок 11. HTML формы, функция ParseForm, метод POST Начнем с того, о чем я уже говорил, а именно о том, что методом GET можно передавать несколько параметров, а не только один. Если знак "?" отделяет параметры от URL скрипта, то параметры отделяются друг от друга знаком "&" (коммерческое AND). Чтобы получить эти параметры "на руки" внутри скрипта в виде обычных переменных, придумана функция ParseForm (англ. Разделять Форму). Пусть её вид Вас не пугает, скоро Вы научитесь писать функции подлеще... &parse_form; sub parse_form { if ($ENV{'REQUEST_METHOD'} eq "POST") {$mode = 0} if ($ENV{'REQUEST_METHOD'} eq "GET") {$mode = 1} if ($mode == 0) {read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'})} if ($mode == 1) {$buffer = $ENV{'QUERY_STRING'}} @pairs = split(/&/, $buffer); foreach $pair (@pairs) { ($name, $value) = split(/=/, $pair); $value =~ tr/+/ /; $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg; $FORM{$name} = $value; } } Я не буду пока вдаваться в подробности работы этой функции, скажу лишь чем она для нас интересна. Не зависимо от метода передачи данных, GET или POST (поговорим об этом ниже), эта функция выдает данные в виде переменных вида $FORM{'значение'}. В связи с этим изменились и условия записи данных в строке URL. Теперь их следует писать в таком виде: "http://.../script.pl?параметр1=значение1&параметр2=значение2". Для экономии места, во всех следующих программах я не буду полностью писать функцию, а буду лишь ссылаться на неё. Вы же должны писать всё полностью. Следующая программа демонстрирует работу функции ParseForm. #!/usr/bin/perl #programm 10 print "Content-Type: text/html\n\n"; &parse_form; print "A + B = ",$FORM{'a'}+$FORM{'b'},"<br>"; print "A - B = ",$FORM{'a'}-$FORM{'b'},"<br>"; print "A * B = ",$FORM{'a'}*$FORM{'b'},"<br>"; print "A<sup>B</sup> = ",$FORM{'a'}**$FORM{'b'}; Эта программа выводит сумму, разность, произведение и результат возведения одного числа в другое. Вызывать программу следует таким образом: "http://.../script.pl?a=значение1&b=значение2", к примеру "http://.../script.pl?a=256&b=2" Про такую запись говорят: "У программы 2 параметра вызова - a и b. a=256, b=2". Формы Чтобы было удобнее передавать данные скрипту, в HTML введены формы. С формами Вы уже сталкивались, когда заполняли строку поиска в поисковой системе, или при общении в ЧАТе. И так, что же представляет из себя форма? Форма - это совокупность специальных HTML тэгов, ограниченных тегами <FORM> и </FORM>. Форма может содержать в себе помимо полей формы другие HTML теги (к примеру <table>). Чтобы можно было передать скрипту данные, введенные в форме, необходимо наличие кнопки "submit". При нажатии на эту кнопку отсылаются данные только той формы, в которой эта кнопка находится. Передать данные из разных форм средствами HTML нельзя. Форма может содержать неограниченное кол-во элементов или не содержать их вовсе. Параметры формы Тэг <FORM> имеет нескопараметроветорв: |
action | адрес скрипта, которому следует передать данные. |
method | метод, которым передается форма (GET или POST) |
target | Это поле позволяет указать окно/фрейм из которого будет делаться запрос. По умолчанию запрашивает текущее окно/фрейм |
<form method="get" action="/cgi-bin/script.pl"></form>
Кнопки
Просто кнопки, как таковые, для CGI программиста бесполезны, т.к. для их программирования необходимо знать языки, работающие на локальном браузере (JavaScript, VBScript и др.). Больший же интерес представляют заранее запрограммированные кнопки "Submit" и "Reset". Первая, как Вы уже знаете, отправляет данные скрипту, вторая - обнуляет все эл-ты формы.
Для создания многих элементов формы, в том числе и кнопок, служит тэг <input>. Самым главным параметром этого тега является "type", т.к. в нем задается тип эл-та формы. Чтобы создать кнопку, значением этого параметра должно быть "button". Вот код самой простой кнопки: <input type="button">. Если Вы передадите этот код браузеру, то увидете что этой кнопкой практически нельзя пользоваться, т.к. она очень маленькая и не имеет надписи. Надпись можно задать в параметре "value". Еще у кнопок есть атрибут "disabled", который превращает эту кнопку в декоративную:
<input type="button" value="button" disabled>
Кнопка "Submit" создается такми же образом, только значение параметра "type" должно быть "submit". Анологично с кнопкой "Reset":
<input type="submit"> | |
<input type="reset"> |
Простое поле для ввода создается тегом <input>, но в отличае от кнопок, значение атрибута "type" должно быть "text". У простого поле для ввода есть дополнительные параметры и атрибуты:
name | идентификатор данного поля |
size | длина поля в символах |
title | текст всплывающей подсказки |
value | значение поля по умолчанию |
maxlength | максимально возможное количество вводимых символов |
disabled | этот атрибут не дает изменить значение по умолчанию |
Как это работает: при отправке формы, значение каждого поля будет являться значением параметра, а имя поля - именем параметра. Именно поэтому необходимо каждому полю присваивать какое-нибудь имя.
<input type="text" size="15" title="обычное поле" maxlength="20" value="some text here" >
Поле для ввода пароля
Для этих полей справедливы вышеизложенные утверждения. Создается это поле тегом <input> со значением "pa" пара" параметра "type".
<input type="password" value="default" >
Скрытое поле
Скрытое поле не отображаемое на экране. Но оно имеет имя и значение и следовательно передается в форму. Служит для того (и очень часто программисты его применяют) чтоб передавать скрипту какую нибудь информацию.Например,если ваш скрипт обрабатывает несколько форм разных типов, то в скрытом поле каждой формы можно указать с какой формой конкретно вы имеете дело. Так как это ваша внутренняя кухня то нечего пользователю мозолить глаза этой информацией.
<input type="hidden" value="1" name="okay">
Переключатель
Переключатель должен иметь имя и значение. Имя задается в параметре "Name", значение в "value" (в отличие от кнопок, где value - надпись на кнопке). В том случае, если переключатель выбран, передается его имя и значение. Если не выбран - ничего не передается. Если необходимо чтобы переключатель был по умолчанию отмечен, он должен иметь атрибут "checked".
<input type="checkbox" checked>
Радио-кнопка
В отличие от checkbox, может быть несколько радио кнопок с одинаковым параметром name ,но с разными value, из них передается только та, что выбрана. Одна из них может быть изначально выбрана по умолчанию.
<input type="radio" value="v1" name="r1"><input type="radio" checked value="v2" name="r1">
Список
Задает список, позволяющий выбрать одну (или несколько) опций из списка. Его значение всегда передается, т.к. всегда хотя бы одно выбрано.
Список создается тегом <select>. Его синтаксис таков:
<SELECT name="s1">
<OPTION value="value1">Опция 1
<OPTION value="value2">Опция 2
.....
<OPTION value="valueN">Опция N
</SELECT>
У тега <SELECT> есть дополнительные параматрибутытребу ты:
name | имя списка |
size | кол-во отображаемых пунктов |
multiple | этот атрибут позволяет выделять несколько пунктов списка |
selected | этот атрибут тега <option> указывает выбранный по умолчанию пункт |
<select name="sel"> <option value="1">один <option value="2" selected>два <option value="3">три </select> |
один два три |
Её еще называют областью потенциально большого объема текста. Синтаксис её таков:
<TEXTAREA>
Значение по умолчанию
</TEXTAREA>
У этого тега есть дополнительные параметры и атрибуты:
name | имя области |
rows | кол-во строк области в символах |
cols | ширина области в символах |
<textarea cols="20" rows="5"> А роза упала на лапу Азора </textarea> |
А роза упала на лапу Азора |
8-/ наконец-то закончили с формами, осталось рассказать только о методе POST.
Суть этого метода заключается в передаче данных в теле HTTP запроса, а не в строке URL, как при методе GET. Это снимает ограничения на длину передаваемых данных. Напомню, что длина URL не должна превышать 2048 символов. Поэтому целесообразно использовать этот метод, если вы включили в форму областью потенциально большого объема текста.
* * * *
Модернизируем программу 10 с учетом полученных знаний так, чтобы вводить данные не вручную в поле URL, а в текстовые поля.
#!/usr/bin/perl #programm 11 print "Content-Type: text/html\n\n";
&parse_form;
print "<form method=\"get\" action=\"/cgi-bin/script.pl\"><br>\n"; print "A = <input name=\"a\" size=3 value=\"$FORM{a}\"><br>\n"; print "B = <input name=\"b\" size=3 value=\"$FORM{b}\">\n"; print "<input type=\"hidden\" name=\"match\" value=1>"; print "<input type=\"submit\" value=\"Calc\"></form>\n";
if ($FORM{'match'} == 1){ print "A + B = ",$FORM{'a'}+$FORM{'b'},"<br>"; print "A - B = ",$FORM{'a'}-$FORM{'b'},"<br>"; print "A * B = ",$FORM{'a'}*$FORM{'b'},"<br>"; print "A<sup>B</sup> = ",$FORM{'a'}**$FORM{'b'};}
* * * *
Напишите программу, которая запрашивает кол-во текстовых полей и выводит их одно под другим. Напротив каждого поля должна располагаться "галочка". По нажатию на "submit", форма передается скрпту, который выводит значение только тех полей, напортив которых "галочка" была отмечена. Ограничите максимальное количество полей значением 10, а минимальное - тремя. Используйте метод Post при передаче формы.
[Содержание] [Вступление] [необходимое для начала изучения] [урок 1] [урок 2] [урок 3] [урок 4] [урок 5] [урок 6] [урок 7] [урок 8] [урок 9] [урок 10] [урок 11] [урок 12] [урок 13] [урок 14] [урок 15] [урок 16] [урок 17] [урок 18] [урок 19] [урок 20] [урок 21] [урок 22] [урок 23] [урок 24] [урок 25] [урок 26] [урок 27] [урок 28] [правила хорошего тона] [приложение A] [приложение Б] [приложение В] [заключение] [благодарности]
Copyright (c) ClericICN, 2002
к данным осуществляется при помощи
Урок 12. Оператор Print, хеши. Хэш - ассоциативный массив, доступ к данным осуществляется при помощи ключа, ассоциированного со значением. Описание хеша начинается с символа "%" (процент). Т.к. ключей и значений должно быть одинаковое количество (иначе какой-нибудь ключ ни с чем не проассоциируется или одно из значений останется лишним), массив с четным количеством элементов можно преобразовать в хеш. Верно и обратное. В хеше не должно быть одинаковых ключей. Также ключи не могут иметь значение "пустая строка". Однако это не справедливо для значений ключей. Способы определения хеша Способ 1: %hash = ( 'шляпа' => 'серая', 'водка' => 'горькая', 'вобла' => 'вкусная' ); Способ 2: %hash = (); $hash{'шляпа'} = 'серая'; $hash{'водка'} = 'горькая'; $hash{'вобла'} = 'вкусная'; С одним из системных хешей Вы уже знакомы, это хеш %ENV. Приведенная ниже программа демонстрирует все значения этого хеша. #!/usr/bin/perl #programm 12 print "Content-Type: text/html\n\n"; $flag=1; @array=%ENV; print "<table>\n"; foreach $i(@array){ if($flag==1){$flag=0; print "<tr><td>$i</td>"; }else{$flag=1; print "<td>$i</td></tr>\n" }} print "</table>"; Т.к. этот курс не подразумевает глубокое изучение хешей, на приведенном выше материале мы и остановимся. Материал по более детальному изучению можно найти в приложении. Еще немного об операторе Print Иногда есть необходимость вывести целый блок однотипной информации. В таком случае использование оператора Print в классическом стиле не совсем удобно. Яркий пример тому программа номер 11: чтобы вывести форму, потребовалось написать оператор Print целых 5 раз, да еще нужно было следить, чтобы перед каждой кавычкой стоял бэк-слэш. Чтобы не возникало подобных неудобств прибегают к методу here-doc. Суть этого метода такова: Прямо в текст программы вставляется фрагмент, предназначенный для вывода, за которым следует выражение-огранечитель. Помимо того, что мы избавляемся от необходимости лишний раз писать оператор print, этот метод хорош и тем, что внутри блока сохраняется форматирование и интерполируются все метасимволы. Необходимые требования, предъявляемые ограничителям: 1. ограничитель должен быть изолирован, т.е. стоять на следующей строке ПОСЛЕ выводимого блока, и после ограничителя не должно быть никаких символов кроме перевода строки (не путать с метасимвлолм "\n"); 2. ограничитель не должен являться зарезервированным литералом (это пока Вам не грозит). Вот программа 11 с применением метода here-doc: #!/usr/bin/perl #programm 12 print "Content-Type: text/html\n\n"; &parse_form; print <<OKAY; <form method="get" action="/cgi-bin/script.pl"><br> A = B = OKAY if ($FORM{'match'} == 1){ print "A + B = ",$FORM{'a'}+$FORM{'b'}," "; print "A - B = ",$FORM{'a'}-$FORM{'b'}," "; print "A * B = ",$FORM{'a'}*$FORM{'b'}," "; print "AB = ",$FORM{'a'}**$FORM{'b'};} * * * * Напишите программу, меняющую ключи и значения хеша местами. Используйте промежуточный массив для решения этой задачи. Дополнительная информация: [подробно о хешах] [что такое хеши] [переменные] [Содержание] [Вступление] [необходимое для начала изучения] [урок 1] [урок 2] [урок 3] [урок 4] [урок 5] [урок 6] [урок 7] [урок 8] [урок 9] [урок 10] [урок 11] [урок 12] [урок 13] [урок 14] [урок 15] [урок 16] [урок 17] [урок 18] [урок 19] [урок 20] [урок 21] [урок 22] [урок 23] [урок 24] [урок 25] [урок 26] [урок 27] [урок 28] [правила хорошего тона] [приложение A] [приложение Б] [приложение В] [заключение] [благодарности] |
Copyright (c) ClericICN, 2002 |
с этого урока мы будем
Урок 13. Функции работы с файлами Собственно, начиная с этого урока мы будем изучать встроенные в Perl функции (до этого мы изучали операторы), и начнем с функций, которые позволяют нам работать с файлами. Дескриптор файла Дескриптор файла в Perl-программе — это имя соединения для ввода-вывода между вашим Perl-процессом и внешним миром. Имена дескрипторов файлов похожи на имена помеченных блоков, но они берутся из другого пространства имен (поэтому у вас может быть скаляр $fred, массив $fred, хеш %fred, метка fred, а теперь и дескриптор файла fred). Как и метки блоков, дескрипторы файлов используются без специального префиксного символа, поэтому их можно спутать с существующими или возможными в будущем зарезервированными словами (для команд, подпрограмм и др.). Рекомендую составлять дескрипторы файлов только из прописных букв. Во-первых, они будут хорошо выделяться в тексте программы, и, во-вторых, благодаря этому программа не даст сбой при введении нового зарезервированного слова. Открытие и закрытие дескриптора файла open (FileVar, FileName); close (FileVar); Функция Open открывает указанный файл и ассоциирует с ним файловую переменную (дескриптор файла). В зависимости от спецсимвола, стоящего перед именем файла, файл можно открыть с разным способом доступа к нему. |
open (FV,"FN"); | Файл открыт для чтения. Запись в него запрещена. |
open (FV,">FN"); | Файл открыт для записи. Когда файл открыт таким способом, его содержимое автоматически стирается (а если файл не существует - создается) и в файл можно добавлять данные. |
open (FV,">>FN"); | Файл открыт для записи в конец. При таком способе открытия файла, тело файла не изменяется и разрешено добавлять строки в его конец. |
open (FV,"+<FN"); | Файл открыт для чтения и записи. |
open (FV,"|FN"); | Направить информацию на вход программы. |
open (FV,"FN|"); | Считать информацию с выхода программы. |
open (FV,"|FN|"); | И то и другое. |
Из файла можно считать данные в виде массива и построчно. При считывании файла построчно, какой-то переменной, при первом считывании, присваивается первая строка файла и указатель перемещается на следующую строку. Такое присваивание возвращает True. При втором считывании присваивается вторая строка, при третьем - третья и т.д. до конца файла. Когда указатель дойдет до последней строки, счетчик сбрасывается, указатель перемещается на первую строчку файла, а возвращаемое выражение становится равным False
При считывании файла в массив, каждая строка файла помещается в отдельную ячейку массива. Плюсы и минусы этих методов очевидны: При считывании файла построчно, экономится память сервера, но работать с файлом становится неудобно, да и этот метод не очень производителен. Другое дело - работа с массивом: это быстро, это удобно, но есть очевидный минус - сильная загрузка памяти сервера. Но несмотря на это, данный метод пользуется огромной популярностью при написании небольших скриптов.
Чтобы считать файл/строку в какую-то переменную, этой переменной присваивают дескриптор файла, заключенный в треугольные скобки:
@array = <FileVar>; $Var = <FileVar>;
Чтобы закрыть файл, используют функцию Close. Хотя её использование не обязательно, т.к. файл закроется сам после окончания выполнения скрипта, но желательно и является признаком хорошего стиля программирования.
Запись в тело файла
Нетрудно догадаться, что работать с файлом (считывать и записывать информацию) можно только тогда, когда он открыт.
Выводятся данные в файл всё тем же оператором Print. Оператору необходимо указать, в какой именно файл (может быть несколько одновременно открытых файлов) следует выводить информацию, затем помещают выражение, предназначенное для вывода:
open (file,">>file.txt"); print file "Здесь был Вася"; close file;
Если не указать файловую переменную, строка "Здесь был Вася" выведется на экран браузера.
Путь к файлу
Если опустить путь к файлу, а указать только его имя, будет считаться, что указанный файл лежит в той же директории, что и файл скрипта. Допускается указывать как абсолютный, так и относительный путь. К примеру, имеем такое дерево файлов и каталогов:
D:/USR/LOCAL/APACHE |----> CGI-BIN | |----> MYDIR | | |----> file0.txt | | | |----> script.pl | |----> file1.txt | |----> HTDOCS |----> file2.txt
Тогда абсолютные пути к файлам будем иметь такими:
D:/USR/LOCAL/APACHE/CGI-BIN/MYDIR/file0.txt D:/USR/LOCAL/APACHE/CGI-BIN/file1.txt D:/USR/LOCAL/APACHE/CGI-BIN/script.pl D:/USR/LOCAL/APACHE/HTDOCS/file3.txt
А относительно файла script.pl, пути будут такими:
MYDIR/file0.txt file1.txt ../HTDOCS/file2.txt
При написании CGI программ принято использовать относительные пути, т.к. владельцы WEB-серверов часто скрывают абсолютное расположение своих директорий.
Блокировка файла, функция Flock
flock(FileVar, operation);
Сразу сделаю небольшую оговорочку, эта функция работает ТОЛЬКО под Unix-подобными операционными системами (Unix, Linux, FreeBSD, SunOS и др.) и Windows NT, W2k, XP...
Необходимость блокировки файлов возникает очень часто когда ваша Perl программа использует данные непосредственно из файлов. Это может быть гостевая книга, форум, чат, счетчик и т. д.
Сначала кажется, что никаких проблем не будет если просто написать код, открывающий файл при необходимости и закрывающий его, после завершения операции чтения/записи:
open(file, "file.txt"); while($string=<file>){ print $string; } close(file);
Если использовать такой код при создании, к примеру, гостевой книги, то существует вероятность, при которой один пользователь добавляет запись, а другой просматривает записи в одно и то же время. Таким образом одновременно два процесса в одно и то же время пытаются выполнить операцию ввода/вывода (в/в) с файлом. Это может привести к серьезным ошибкам, вплоть до потери данных. Для того, чтобы избежать таких ситуаций, необходимо блокировать файл непосредственно перед выполнением операции в/в. В Perl'e есть специальная функция для блокировки файлов, которая используют системные вызовы.
В качестве параметра operation допускаются следующие значения:
"LOCK_SH" или "1" | Блокировка для чтения |
"LOCK_EX" или "2" | Блокировка для записи |
"4" | "двухстороннее" блокирование |
"LOCK_UN" или "8" | Разблокирование |
Используя эту функцию, можно обезопасить свою программу от ошибки ввода/вывода. Код чтения данных из файла с использованием блокировки может выглядеть так:
open(file, "file.txt"); flock(file, LOCK_SH); while($string=<file>){ print $string; } close(file);
Примечание: Не обязательно файл разблокировывать, т.к. при закрытии файловой переменной, Perl сделает это автоматически.
Код записи данных в файл, с блокировкой:
open (FH, ">>data.tmp"); flock(FH, 2); $str="new line"; print FH $str; flock(FH, 8); close(FH);
Есть одна особенность при использовании блокировки файлов: если к файлу может обратиться хотя бы один процесс, в котором не ставится блокировка, то вся система блокирования становится бесполезной. Другими словами, надо использовать блокировку не только при создании/обновлении/записи в нужный файл, но и в других скриптах, работающих с этими файлом.
* * * *
Напишем скрипт, который записывает в файл переданную ему строчку, и удаляет строку с переданным номером:
#!/usr/bin/perl # programm 13 print "Content-type: text/html\n\n";
&parse_form;
Добавить запись | ||
Удалить запись номер: |
if ($FORM{'action'} eq "add") { # выполняем этот блок когда хотим записать в файл open(file,">>file.txt"); print file "$FORM{'string'}\n"; close file; open(file,"file.txt"); @array=; close file; foreach $i(@array){ $u++; print "$u. $i
"; } } elsif ($FORM{'action'} eq "del") { # выполняем этот блок когда хотим удалить строку open(file,"file.txt"); @array=; close file; open(file,">file.txt"); $size=@array; for($i=0;$i; close file; foreach $i(@array){ $u++; print "$u. $i
"; } } else { # вып. этот код когда хотим просмотреть все записи open(file,"file.txt"); @array=; close file; foreach $i(@array){ $u++; print "$u. $i
"; } }
Возможно, Вам будет трудно поначалу разобраться с работой этой программы. Попробуйте мысленно "запустить" её, представив что Вы - это Perl. Последовательно интерпретируйте код, возможно записывая часть результатов на бумагу. Мысленно подставляйте значения в форму и снова "запускайте" её, тогда Вы быстро поймете работу программы. Важно обладать таким навыком, это впредь поможет Вам находить ошибки в своих и чужих скриптах.
* * * *
Может ли иметь одна программа дескриптор файла "ONE" и метку "ONE" ? Если оператор Print не содержит указания записи в файл, но стоит сразу после оператора Open, сможет ли он записать в файл какую-либо строку? Посмотрите на дерево каталогов, приведенное выше по тексту. Составьте путь к файлу file0.txt относительно file2.txt и наоборот. Составьте абсолютный путь к файлу file.txt, если относительно файла script.pl его путь выгладет так: "/DOCS/file.txt"
Напишите программу, которая создает в директории с HTML документами, HTML файл, в котором записана таблица Пифагора. Откройте этот файл через его URL. Всё ли правильно отображается?
Дополнительный материал: [работа с файлами] [работа с файлами 2] [работа с файлами 3]
[Содержание] [Вступление] [необходимое для начала изучения] [урок 1] [урок 2] [урок 3] [урок 4] [урок 5] [урок 6] [урок 7] [урок 8] [урок 9] [урок 10] [урок 11] [урок 12] [урок 13] [урок 14] [урок 15] [урок 16] [урок 17] [урок 18] [урок 19] [урок 20] [урок 21] [урок 22] [урок 23] [урок 24] [урок 25] [урок 26] [урок 27] [урок 28] [правила хорошего тона] [приложение A] [приложение Б] [приложение В] [заключение] [благодарности]
Copyright (c) ClericICN, 2002
Урок 14. Функции для работы
Урок 14. Функции для работы с файлами (продолжение) Оператор "-X" Проверка файла, где 'X' - одно из ниже описанных значений. Это унарный оператор с одним аргументом - либо именем файла, либо указателем файла. Проверяет одно из условий. Если аргумент не указан, то берется значение переменной $_. Несмотря на странный вид, это унарный оператор с соответствующим приоритетом. Аргумент можно заключать в круглые скобки. 'X' имеет следующие значения: |
-r | Файл разрешен на чтение эффективным uid/gid |
-w | на запись -//- |
-x | исполнение -//- |
-o | принадлежит эффективному uid (идентификатор пользователя) |
-R | Файл разрешен на чтение реальным uid/gid |
-W | на запись -//- |
-X | исполнение -//- |
-O | принадлежит реальному uid |
-e | файл существует |
-z | пустой |
-s | не пустой |
-f | обычный текст |
-d | директория |
-l | символическая ссылка |
-p | pipes (конвейер) |
-S | socket (гнездо) |
-b | специальное блочное устройство |
-c | -//- символьное -//- |
-t | указатель на уст-во tty |
-u | установлен бит setuid |
-g | -//- setgid |
-k | -//- sticky |
-T | текстовой файл |
-B | двоичный |
-M | "возраст" файла в днях на момент старта скрипта |
-A | дней с последнего чтения |
-C | дней с последней модификации inode |
if (-e("file.txt")){print (-A("file.txt"))} else {print "Файл не создан"}
Функция Read
read (FileVar, скаляр, длина, смещение);
read (FileVar, скаляр, длина);
Прочитать указанное число байт из файла в скалярную переменную. Возвращает количество прочитанных байт или неопределенность при ошибке. Длина скаляра станет равной количеству считанных байт. Смещение указывают, если данные нужно поместить не с самого начала строки. Положение указателя сохраняется.
Код ниже читает и выводит 15 байт из файла "file.txt":
open (file,"file.txt"); read("file",$u,5); print $u,"<BR>"; read("file",$u,10); print $u; close file;
Функция Getc
getc (FileVar);
Читает и возвращает символ из указанного файла. Если файл не указан, то читается STDIN. После конца файла возвращает пустую строку.
Код ниже читает и выводит 15 байт из файла "file.txt":
open (file,"file.txt"); while ($u<=15){ print getc(file); } continue{$u++} close file;
Функция Rename
rename (старое_имя, новое_имя_файла);
Переименовать файл. Возвращает 1 в случае успеха, иначе - 0.
Функция Unlink
unlink (список)
Удаляет файлы, указанные в списке. Возвращает количество удаленных файлов.
* * * *
Напишем программу, которая создает, переименовывает и удаляет файл delete.me:
#!/usr/bin/perl # programm 14 print "Content-type: text/html\n\n";
unless (-e("delete.me") or -e("undelete")){ open (file,">delete.me") or print "Невозможно создать файл<br>"; close file; rename ("delete.me","undelete") and print "Удачно переименован<br>"; unlink ("undelete") and print "Удачно удален"; } else { print" Невозможно произвести операцию!" }
Я чувствую, что привел Вас в замешательство. Действительно, операторы AND и OR использованы здесь не совсем в "традиционном" контексте.
Вы знаете, чтобы оператор OR вернул истину, необходимо чтобы хотя бы ОДИН из операндов имел значение ИСТИНА. Чего не скажешь об операторе AND. Для того чтобы он вернул истину, необходимо чтобы ОБА операнда являлись истинными. От сюда следует, что если первый операнд возвращает ИСТИНУ, то оператору OR совершенно не обязательно выполнять второе выражение чтобы чтобы ему самому стать истинным. Аналогично с оператором AND. Если первое выражение возвращает ложь, то второе выполнять не обязательно, т.к. независимо от его значения, AND всё равно вернет ЛОЖЬ.
Разберем работу AND и OR на примере программы 14.
С IF (unless) всё прозрачно;
Функция Open, как известно, возвращает False в случае неудачного открытия файла и ничего не возвращает в случае удачи. Значит если Open вернет False, оператор OR будет вынужден проверить: "а не является второе выражение True?!" и выполнит вывод строки на экран браузера.
Оператор Rename возвращает кол-во переименованных файлов. Значит, если ему не удастся переименовать 1 файл, он вернет 0, т.е. False.
В нашем случае если Rename вернет 1, т.е. True, оператор AND должен будет проверить на истинность второе выражение и выполнит вывод на экран браузера. Если же Reneme вернет False, второе выражение выполнено не будет.
* * * *
Напишите программу, которая проверяет наличие файла index.htm в директории с HTML файлами и при его наличие выдает его браузеру. Т.е. Вам нужно его полностью считать и распечатать.
Попробуйте использовать разные способы считывания файла (посимвольный и построчный) в зависимости от передаваемого скрипту параметра.
[Содержание] [Вступление] [необходимое для начала изучения] [урок 1] [урок 2] [урок 3] [урок 4] [урок 5] [урок 6] [урок 7] [урок 8] [урок 9] [урок 10] [урок 11] [урок 12] [урок 13] [урок 14] [урок 15] [урок 16] [урок 17] [урок 18] [урок 19] [урок 20] [урок 21] [урок 22] [урок 23] [урок 24] [урок 25] [урок 26] [урок 27] [урок 28] [правила хорошего тона] [приложение A] [приложение Б] [приложение В] [заключение] [благодарности]
Copyright (c) ClericICN, 2002
Урок 15. Функции для работы
Урок 15. Функции для работы с файлами (продолжение) Функция Utime utime (Date1,Date2,список_файлов); Изменяет дату обращения и модификации файлов из списка. Первые два элемента списка должны указывать новое значение даты обращения и модификации. Возвращает количество измененных файлов. Функция Truncate truncate (файл, длина); Усекает длину указанного файла. Запишем в файл file.txt строку и усечем его до 15 байт: #!/usr/bin/perl # programm 15 print "Content-type: text/html\n\n"; $string = "This is a sample file"; print "Write to file: $string "; open (file,">file.txt"); print file $string; close file; truncate ("file.txt",15); open (file,"file.txt"); $string=; close file; print "Read from file: ",$string; Функция Tell tell (файл); Возвращает текущую позицию указателя в открытом файле. Если файл не указан, то последнего читаемого файла. Следующий код читает 15 байт из файла "file.txt". Непременное условие работы этого кода - длина файла должна быть более 15 байт. print ""; open (file, "file.txt"); while (tell(file) < 15){ print getc(file); } print ""; Функция Stat stat (файл); Возвращает 13 элементный массив параметров указанного файла или пустой список при ошибке. Применяется обычно следующим образом: ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat($filename); $dev - имя устройства $ino - номер i-узла $mode - права доступа $nlink - количество связей $uid - идентификатор владельца $gid - идентификатор группы $rdev - тип устройства $size - размер файла в байтах $atime - дата последнего обращения $mtime - дата последней модификации $ctime - дата последнего изменения статуса $blksize - размер блока на диске $blocks - количество блоков в файле. Следующий код выводит размер файла в байтах: $u = (stat("file.txt"))[7]; print $u; } Функция Seek seek (FileVar, позиция, отсчет); Установить указатель на произвольную позицию в файле. Если отсчет равен 0 - позиционировать от начала файла, если 1 - от текущего положения, 2 - от конца файла. Возвращает 1 при успехе и 0 - неудаче. Функция Chmod chmod (список); Изменить права доступа к файлам указанным в списке. Первый элемент в списке - числовое ,обычно восьмеричное значение прав. Возвращает количество файлов которым были изменены права. Пример: chmod 0666 'f1', 'f2', 'f3'; * * * * Напишите программу, которая выводит все сведения об указанном файле или сообщение об ошибке, если файл не найден. Имя файла передается спомощью формы. Напишите программу, которая выводит указанную строку файла с указанным смещением. Т.е. начинает вывод не с начала. Используйте функции Seek и Getc для решения этой задачи. [Содержание] [Вступление] [необходимое для начала изучения] [урок 1] [урок 2] [урок 3] [урок 4] [урок 5] [урок 6] [урок 7] [урок 8] [урок 9] [урок 10] [урок 11] [урок 12] [урок 13] [урок 14] [урок 15] [урок 16] [урок 17] [урок 18] [урок 19] [урок 20] [урок 21] [урок 22] [урок 23] [урок 24] [урок 25] [урок 26] [урок 27] [урок 28] [правила хорошего тона] [приложение A] [приложение Б] [приложение В] [заключение] [благодарности] |
Copyright (c) ClericICN, 2002 |
Общее количество посещений:
Общее количество посещений: <!--#exec cgi="/cgi-bin/counter.pl" -->
Для применения подпрограммы ее необходимо
Урок 17. Подпрограммы, прототипы Для применения подпрограммы ее необходимо определить либо в текущем модуле (файле), либо во внешнем модуле (файле). Подпрограммы определяются и декларируются следующим образом: sub имя; - Только декларация. Определение ниже. sub имя (прототипы); - То же но с декларацией параметров. sub имя блок; - Декларация и определение. sub имя (прототипы) блок; - То же, но с параметрами. Все параметры передаются подпрограмме как массив @_. Соответственно $_[0] - первый параметр, $_[1] - второй и т.д. Массив @_ - локальный, но он содержит адреса параметров, поэтому можно изменять значение параметров. Возвращаемое значение подпрограммы - результат последнего оператора. Это может быть как скаляр так и массив. Можно принудительно возвращать результат используя функцию return(). Подпрограмму можно вызвать, используя префикс '&' перед именем подпрограммы. Если подпрограмма предварительно продекларирована, то префикс и скобки можно опустить. Для применения переменных доступных только внутри блока или подпрограммы необходимо определить их с помощью функции my(список). Если переменная одна, то скобки можно опустить. Для тех кто еще не понял о чем идет речь: Процедуры и функции представляют собой важный инструмент Perl-а, позволяющий писать хорошо структурированные программы. В структурированных программах обычно легко прослеживается основной алгоритм, их нетрудно понять любому читателю, они проще в отладке и менее чувствительны к ошибкам программирования. Все эти свойства являются важной особенностью процедур (функций), каждая из которых представляет собой во многом самостоятельный фрагмент кода, связанный с основной программой лишь несколькими параметрами. Самостоятельность процедур дает возможность без особых изменений в основной программе изменять код процедуры. Процедурой в Perl называют особым образом оформленный участок кода со своим собственным именем. Упоминание этого имени в программе приводит к активации процедуры. Сразу после активации процедуры начинают выполняться входящие в неё операторы. После выполнения последнего оператора, основная программа продолжает свое выполнение дальше. Для обмена информацией между основной программой и процедурой используются параметры вызова. Для передачи информации от процедуры программе, внутри процедуры используется оператор return. Прототипы Для краткого описания типа передаваемых подпрограмме параметров можно применять прототипы. Если формат передаваемых данных не будет удовлетворять шаблону прототипа, это вызовет ошибку программы. В Perl существуют следующие прототипы: |
sub mylink($$) | mylink $old, $new |
sub myvec($$$) | myvec $var, $offset, 1 |
sub myindex($$;$) | myindex &getstring, "substr" |
sub myreverse(@) | myreverse $a, $b, $c |
sub myjoin($@) | myjoin ":",$a,$b,$c |
sub mypop(\@) | mypop @array |
sub mysplice(\@$$@) | mysplice @array, @array, 0, @pushme |
sub mykeys(\%) | mykeys %{$hashref} |
sub myopen(*;$) | myopen HANDLE, $name |
sub mypipe(**) | mypipe READHANDLE, WRITEHANDLE |
sub mygrep(&@) | mygrep { /foo/ } $a, $b, $c |
sub myrand($) | myrand 42 |
sub mytime() | mytime |
\'символ' - параметр с типом 'символ'
'@' или '%' - все оставшиеся параметры как список
'$' - скаляр
'&' - безымянная подпрограмма
'*' - ссылка на таблицу имен
';' - разграничитель обязательных и не обязательных параметров.
Вставить часть CGI кода в программу, подобно SSI, можно с помощью функции "require". Её синтаксис прост до безобразия:
require "файл";
Пример кода, где подпрограмма загружается из внешнего файла:
# Файл sub.pl sub summ($$){ return ($_[0]+$_[1]) }
# Файл со скриптом require "sub.pl"; print summ(5,2);
* * * *
Напишем функцию, которая возвращает фактореал переданного ей числа.
sub fact{ # объявляем функцию fact my($sum,$i); # объявляем локальные переменные $sum=1; for ($i=1;$i<=$_[0];$i++){ $sum*=$i; } return $sum # возвращаем значение переменной $sum }
Примеры вызова такой функции:
print fact(4);
print fact(4,22.8,5);
Результатом выполнения этого кода будет 24 Чтобы избежать вызова функции с ошибочными значениями, используем прототип $, означающий, что функции мы передаем только один парамер:
sub fact($){
my($sum,$i); $sum=1; for ($i=1;$i
Перепишем программу 13 с учетом полученных знаний:
#!/usr/bin/perl # programm 17 print "Content-type: text/html\n\n";
&parse_form;
sub wiev(){ open(file,"file.txt"); @array=; close file; foreach $i(@array){ $u++; print "$u. $i
"; }}
Добавить запись | ||
Удалить запись номер: |
if ($FORM{'action'} eq "add") { open(file,">>file.txt"); print file "$FORM{'string'}\n"; close file;
wiev; } elsif ($FORM{'action'} eq "del") { open(file,"file.txt"); @array=; close file; open(file,">file.txt"); $size=@array; for($i=0;$i
wiev
} else{wiev}
Не правда ли, ощутимая разница?
* * * *
В какую переменную поместится 2-й переданный процедуре параметр? Сколько параметров может принять эта процедура - sub overlay(@); ?
Перепишите 3-4 написанные Вами программы с целью ввести туда процедуры.
[Содержание] [Вступление] [необходимое для начала изучения] [урок 1] [урок 2] [урок 3] [урок 4] [урок 5] [урок 6] [урок 7] [урок 8] [урок 9] [урок 10] [урок 11] [урок 12] [урок 13] [урок 14] [урок 15] [урок 16] [урок 17] [урок 18] [урок 19] [урок 20] [урок 21] [урок 22] [урок 23] [урок 24] [урок 25] [урок 26] [урок 27] [урок 28] [правила хорошего тона] [приложение A] [приложение Б] [приложение В] [заключение] [благодарности]
Воспринимает выражение как строку шестнадцатеричных
Урок 18. Работа с числами Функция ABS Возвращает абсолютное значение выражения Функция COS Возвращает косинус выражения в радианах Функция EXP Возвращает e (основание натурального логарифма) в степени выражения Функция HEX Воспринимает выражение как строку шестнадцатеричных цифр и возвращает десятичное значение Функция INT Возвращает целую часть выражения Функция LOG Возвращает натуральный логарифм выражения Функция OCT Возвращает десятичное число, представленное восьмеричными цифрами Функция SCALAR Вычисляет выражение в скалярном контексте и возвращает результат. В Perl нет специальной функции для принудительного вычисления в списковом контексте, т.к. для этого достаточно заключить его в круглые скобки Функция SIN Возвращает в радианах синус выражения Функция SQRT Возвращает корень квадратный выражения Функция Rand Возвращает псевдослучайное число в диапазоне от 0 до значения, указанного в аргументе. Если аргумент отсутствует, то от 0 до 1. Функция Srand Задает начальное значение для генератора случайных чисел. Если аргумент отсутствует, то используется текущее машинное время. * * * * Напишем программу, находящую корни квадратного уравнения: #!/usr/bin/perl # programm 18 print "Content-type: text/html\n\n"; &parse_form; $a=$FORM{'a'}; $b=$FORM{'b'}; $c=$FORM{'c'}; X2 + X + = 0 EOF ; sub info(){ $Ds=sqrt($D); print "D = $D, Sqtr(D) = $Ds \n"; } print "A=$a, B=$b, C=$c \n"; $D=(($b**2)-4*$a*$c); if ($D0){ info; print "Первый корень: ",((-$b+$Ds)/(2*$a)); print " \n"; print "Второй корень: ",((-$b-$Ds)/(2*$a)) } На мой взгляд, это единственная программа, реализованная абсолютно на всех языках программирования. * * * * Введите проверку деления на 0 в программе 18. Напишите программу, которая создает 10 случайных квадратных уравнений и предлагает пользователю выбрать какое из них она будет решать. Диапазон колебания значений a,b,c - (-15..15) включительно, с шагом 1. [Содержание] [Вступление] [необходимое для начала изучения] [урок 1] [урок 2] [урок 3] [урок 4] [урок 5] [урок 6] [урок 7] [урок 8] [урок 9] [урок 10] [урок 11] [урок 12] [урок 13] [урок 14] [урок 15] [урок 16] [урок 17] [урок 18] [урок 19] [урок 20] [урок 21] [урок 22] [урок 23] [урок 24] [урок 25] [урок 26] [урок 27] [урок 28] [правила хорошего тона] [приложение A] [приложение Б] [приложение В] [заключение] [благодарности] |
Copyright (c) ClericICN, 2002 |
Урок 19. Функции для работы
Урок 19. Функции для работы со строками Функция Chomp chomp переменная; chomp список; chomp; Удаляет в конце строки символ указанный переменной $/. Обычно это '\n'. Возвращает количество удаленных символов. Если переменная не указана то используется переменная $_. Если указан список то обрабатывается каждая строка списка и возвращается общее количество удаленных символов. Функция Chop chop переменная; chop список; chop; Полностью аналогично функции chomp но всегда удаляет последний символ строки. Функция Chr Возвращает символ с заданным ASCII кодом. Функция Lc Преобразовать строку в буквы нижнего регистра. Функция Lcfirst Заменить первую букву строки на малую. Функция Length Возвращает длину строки выражения. Функция Ord Возвращает ascii код первого символа выражения. Функция Uc Возвращает строку выражения, в которой малые буквы заменены на заглавные. Функция Ucfirst Возвращает строку выражения, в которой первая буква заменена на заглавную. Функция Substr substr (выражение, смещение, длина); substr (выражение, смещение); Возвращает подстроку выражения, начиная со смещения и заданной длины. Если смещение отрицательное, то отсчет ведется от конца строки. Если длина не указана, то берется все до конца строки. Функция Pack pack (шаблон, список); Упаковывает список в двоичную структуру по шаблону. Шаблон - это строка символов, описывающая порядок и тип упакованных значений. A - текстовая строка, добавляется пробелами. a - текстовая строка, добавляется 0 символом. b - битовая строка (возрастающая нумерация бит) B - битовая строка (убывающая нумерация бит) h - шестнадцатеричная строка ( младший байт - первый) H - шестнадцатеричная строка (старший байт первый) c - символ со знаком C - символ без знака. s - короткое целое (один байт) со знаком. S - короткое целое без знака. i - целое (два байта) со знаком. I - целое без знака. l - длинное целое (4 байта) со знаком. L - длинное целое без знака. n - короткое в "сетевом" формате. N - длинное в "сетевом" формате. v - короткое в "VAX" формате. V - длинное в "VAX" формате. f - single float. F - double float. p - указатель на строку ограниченную 0. P - указатель на структуру с фиксированной длиной. u - упаковка uuencode. x - нуль-байт X - резервный байт. @ - заполнить нулями позицию. За каждой буквой может стоять число означающее количество повторов. Для всех типов за исключением 'a', 'A', 'b', 'B', 'h' и 'H', упаковывается максимально возможное количество значений из списка. Символ '*' после типа означает использовать оставшиеся параметры. Тип 'a' и 'A' использует только одно значение из списка и добавляет остаток поля либо нуль-символами, либо пробелами (при распаковке по типу 'A' пробелы и нули отбрасываются, а по 'a' - нет). Типы 'b' и 'B' упаковывают строку в указанное число бит. Так же как и 'h' и 'H' в число ниблов (байт). 'P' - упаковывает указатель на структуру с указанной длиной. Числа с плавающей запятой (floats и double) записываются в стандартном для каждой машины формате и могут быть разными для разных типов машин. Заметьте, что Perl всегда использует двойную точность (double), поэтому упаковка и распаковка по типу 'f' приведет к потере точности. Шаблоны для распаковки и упаковки совершенно одинаковы. print(pack("ccc",65,66,67)); # Результат ABC print(pack("A4A3","ab","cdefg")); # "ab cde" print(pack("a4a3","ab","cdefg")); # "ab\0\0cde" Функция Unpack unpack (шаблон, выражение); Функция, обратная pack(). Распаковывает строку выражения по указанному шаблону в массив значений. В скалярном контексте возвращает значение только первого элемента. Шаблоны такие же как и в pack(). Символ '%' в шаблоне означает, что вы хотите получить вместо значения его контрольную сумму. По умолчанию - 16-битную. * * * * Напишите программу, выводящую латинский алфавит. ASCII код первой буквы алфавита - 65. В латинском алфавите 25 букв. Попробуйте записать в файл IP адрес, затем считать его и сравнить (программно с помощью оператора IF) его с текущем IP адресом. Почему, как вы думаете, они не совпали? запись IP адреса осуществляйте такой строкой: print "$ENV{'REMOTE_ADDR'}\n"; [Содержание] [Вступление] [необходимое для начала изучения] [урок 1] [урок 2] [урок 3] [урок 4] [урок 5] [урок 6] [урок 7] [урок 8] [урок 9] [урок 10] [урок 11] [урок 12] [урок 13] [урок 14] [урок 15] [урок 16] [урок 17] [урок 18] [урок 19] [урок 20] [урок 21] [урок 22] [урок 23] [урок 24] [урок 25] [урок 26] [урок 27] [урок 28] [правила хорошего тона] [приложение A] [приложение Б] [приложение В] [заключение] [благодарности] |
Copyright (c) ClericICN, 2002 |
Урок 20. Операторы для работы
Урок 20. Операторы для работы со строками (продолжение) Оператор q// q/строка/; 'строка'; Не интерполируемая строка литералов. Если в качестве разделителя используется одинарная кавычка, ключевое слово "q" можно опускать. Сами того не зная, Вы используете этот оператор почти с самого первого урока. print q^Windows 3.11^; # результат: Windows 3.11 print q?Windows 98?; # результат: Windows 98 print 'Windows 2000'; # результат: Windows 2000 Оператор qq// qq/строка/; "строка"; Интерполируемая строка. Если в качестве разделителя используется двойная кавычка, ключевое слово "qq" можно опускать. $var311 = "3.11"; # определяем переменные $var98 = "98"; print qq#Windows $var311#; # результат: Windows 3.11 print qq+Windows $var98+; # релультат: Windows 98 print "Windows 2000"; # результат: Windows 2000 Оператор qx// qx/строка/; `строка`; Сначала строка интерполируется, а потом выполняется как системная команда. print ""; print `dir`; print ""; Этот код выведет очень много ценной информации ;-) . Не забудьте переключить кодировку браузера в "DOS". Оператор qw// qw/строка/; Возвращает список, элементы которого содержат слова из строки. Т.е. пробелы рассматриваются как символы-разделители. Следующий код подчеркнет все слова фразы, длиной 7 букв: @array = qw(Пока толстый сохнет, худой - сдохнет); foreach $i(@array){ if (length($i) == 7){print "$i "} else {print "$i "} } Оператор Split split (/шаблон/, выражение, предел); split (/шаблон/, выражение); split (/шаблон/); split; Разделяет строку, указанную выражением, на массив элементов и возвращает его. В скалярном контексте возвращает количество полученных элементов массива, а сам массив помещает в @_ (в списковом контексте поместить результат в @_ можно если применить ?шаблон?) Если выражение отсутствует, то обрабатывается содержимое переменной $_. Если шаблон отсутствует, то разделителем является пробел. Все, что подходит по шаблону, считается разделителем. Если указан предел, то это максимальное число разделений. Отрицательное значение предела воспринимается как неограниченно большой предел. Если пустая строка походит под шаблон разделителя, то исходное значение разделяется посимвольно. Предел удобно использовать для частичного разделения строки. ($a, $b, $остаток) = split(/ /, "Один Два Три Четыре",3); # $a = 'Один', $b = 'Два' и $остаток = 'Три Четыре' Если шаблон содержит круглые скобки, то символы-разделители, указанные в них, вставляются в результирующий список как обычные элементы, причем вставляется символ, который совпал с разделителем @a = split(/([,.])/, "Один,Два.Три"); # @a = ("Один", "," ,"Два", ".", "Три") И еще один примерчик: @a = split(//,"червь"); # @a = ('ч','е','р','в','ь') * * * * Попробуйте создать так называемую "плоскую базу": напишите скрипт, который записывает в файл строчки такого вида: "переданная_строка1 & переданная_строка2 & переданная_строка3", при каждом обращении к скрипту. И выводящий на экран самую длинную строку из первых значений и саму короткую из вторых. Эти строки должны сопровождаться значениями из 3-их строк. [Содержание] [Вступление] [необходимое для начала изучения] [урок 1] [урок 2] [урок 3] [урок 4] [урок 5] [урок 6] [урок 7] [урок 8] [урок 9] [урок 10] [урок 11] [урок 12] [урок 13] [урок 14] [урок 15] [урок 16] [урок 17] [урок 18] [урок 19] [урок 20] [урок 21] [урок 22] [урок 23] [урок 24] [урок 25] [урок 26] [урок 27] [урок 28] [правила хорошего тона] [приложение A] [приложение Б] [приложение В] [заключение] [благодарности] |
Copyright (c) ClericICN, 2002 |
Наш сегодняшний урок ЧЕРЕЗВЫЧАЙНО труден
Урок 21. Регулярные выражения Наш сегодняшний урок ЧЕРЕЗВЫЧАЙНО труден - будем изучать весьма специфическую и тяжелую для усвоения информацию. Но понять эту тему важно, ведь она - вся сила (Power) языка Perl. И так, соберем все оставшиеся силы в кулак и приступим к поеданию гранита науки, немного уже осталось... Совет: Прочитайте этот урок не спеша, пытаясь вдуматься буквально в каждое слово. С первого раза Вы, скорее всего, не поймете тему. По этому читайте её не менее 4-х раз с интервалом не менее 1 часа. Это поможет запомнить до 60% всей информации на уровне восприятия и до 30% на интуитивном уровне. Лучше всего держать всю эту страницу где-нибудь "под рукой" в распечатанном виде, чтобы в случае необходимости Вы смогли быстро найти нужную Вам информацию. Не зацикливайтесь на том, что Вам не понятны примеры. Это естественно. Просто примите к сведению, что эти скрипты работают и не старайтесь разобраться в них. Сегодня Вы должны ознакомиться с темой только теоретически, практикой займемся немного позже. Чаще всего в Perl регулярные выражения используются в операторах поиска и замены таких как s//, m/, операторах связки =~ или != и т.д. Как правило все эти операторы имеют схожие опции такие как: |
i | - не различать строчные и заглавные буквы. |
m | - считать строку многострочной. |
s | - однострочная строка. |
x | - расширенный синтаксис ( использование пробелов и комментариев) |
Регулярные выражения или шаблоны (pattern) то же самое, что и regexp процедуры в Unix. Выражения и синтаксис заимствованы из свободно распространяемых процедур V8 Генри Спенсера (Henry Spencer), там же они подробно и описаны.
В шаблонах используются следующие метасимволы (символы обозначающие группы других символов) часто называемые egrep - стандартом:
\ | - считать следующий метасимвол как обычный символ. |
^ | - начало строки |
. | - один произвольный символ. Кроме '\n' - конец строки. |
$ | - конец строки |
| | - альтернатива (или) |
() | - группировка |
[] | - класс символов |
Метасимволы имеют модификаторы (пишутся после метасимвола):
* | - повторяется 0 или большее число раз |
+ | - -//- 1 или большее число раз |
? | - 1 или 0 раз |
{n} | - точно n раз |
{n,} | - по меньшей мере раз |
{n,m} | - не меньше n, но и не больше m |
По умолчанию действие метасимволов "жадно" (greedy). Совпадение распространяется столько раз, сколько возможно, не учитывая результат действия следующих метасимволов. Если вы хотите "уменьшить их аппетит", то используйте символ '?'. Это не изменяет значение метасимволов, просто уменьшает распространение. Таким образом:
*? | - станет 0 и более |
+? | - 1 и более |
?? | - 0 или 1 раз |
{n}? | - точно n раз |
{n,}? | - не меньше n раз |
{n,m}? | - больше или равно n и меньше m раз |
\t | - символ табуляции |
\n | - новая строка |
\r | - перевод каретки |
\а | - перевод формата |
\v | - вертикальная табуляция |
\a | - звонок |
\e | - escape |
\033 | - восьмеричная запись символа |
\x1A | - шестнадцатеричная |
\c[ | - control символ |
\l | - нижний регистр следующего символа |
\u | - верхний регистр -//- |
\L | - все символы в нижнем регистре до \E |
\U | - в верхнем -//- |
\E | - ограничитель смены регистра |
\Q | - отмена действия как метасимвола |
\w | - алфавитно-цифровой или '_' символ |
\W | - не -//- |
\s | - один пробел |
\S | - один не пробел |
\d | - одна цифра |
\D | - одна не цифра |
\w+ | - слово |
\d+ | - целое число |
[+-]?\d+ | - целое со знаком |
[+-]?\d+\.?\d* | - число с точкой |
\b | - граница слова |
\B | - не граница слова |
\A | - начало строки |
\Z | - конец строки |
\G | - конец действия m//g |
Граница слова (\b) - это мнимая точка между символами \w и \W. Внутри класса символов '\b' обозначает символ backspace (стирания). Метасимволы \A и \Z - аналогичны '^' и '$', но если начало строки '^' и конец строки '$' действуют для каждой строки в многострочной строке, то \A и \Z
обозначают начало и конец всей многострочной строки.
Если внутри шаблона применяется группировка (круглые скобки), то номер подстроки группы обозначается как '\цифра'. Заметьте, что за шаблоном в пределах выражения или блока эти группы обозначаются как '$цифра'. Кроме этого существуют дополнительные переменные:
$+ | - обозначает последнее совпадение |
$& | - все совпадение |
$` | - все до совпадения |
$' | - все после совпадения |
$s = "Один 1 два 2 и три 3"; if ($s =~ /(\d+)\D+(\d+)/) { print "$1\n";# Результат '1' print "$2\n";# '2' print "$+\n";# '2' print "$&\n";# '1 два 2' print "$`\n";# 'Один ' print "$'\n";# ' и три 3' }
Perl версии 5 содержит дополнительные конструкции шаблонов:
(?#комментарий) | - комментарий в теле шаблона. |
(?:шаблон) | - группировка как и '( )', но без обратной ссылки |
(?=шаблон) | - "заглядывание" вперед. Например /\w+(?=\t)/ соответствует слову, за которым идет табуляция, но символ '\t' не включается в результат. |
$s = "1+2-3*4"; if ($s =~ /(\d)(?=-)/) # Найти цифру за которой стоит '-' { print "$1\n"; # Результат '2' } else { print "ошибка поиска\n";}
(?!шаблон) - "заглядывание" вперед по отрицанию.
Пример:
$s = "1+2-3*4"; if ($s =~ /(\d)(?!\+)/) # Найти цифру за которой не стоит '+' { print "$1\n"; # Результат '2' } else { print "ошибка поиска\n";}
Правила регулярных выражений:
Любой символ обозначает себя самого, если это не метасимвол. Если вам нужно отменить действие метасимвола, то поставьте перед ним '\'.
Строка символов обозначает строку этих символов.
Множество возможных символов (класс) заключается в квадратные скобки '[]', это значит, что в данном месте может стоять один из указанных в скобках символов. Если первый символ в скобках это '^' - значит ни один из указанных символов не может стоять в данном месте выражения. Внутри класса можно употреблять символ '-', обозначающий диапазон символов. Например, a-z - один из малых букв латинского алфавита, 0-9 - цифра и т.д.
Все символы, включая специальные, можно обозначать с помощью '\' как в языке С.
Альтернативные последовательности разделяются символом '|' Заметьте что внутри квадратных скобок это обычный символ.
Внутри регулярного выражения можно указывать "подшаблоны" заключая их в круглые скобки и ссылаться на них как '\номер' Первая скобка обозначается как '\1'.
* * * *
[Содержание] [Вступление] [необходимое для начала изучения] [урок 1] [урок 2] [урок 3] [урок 4] [урок 5] [урок 6] [урок 7] [урок 8] [урок 9] [урок 10] [урок 11] [урок 12] [урок 13] [урок 14] [урок 15] [урок 16] [урок 17] [урок 18] [урок 19] [урок 20] [урок 21] [урок 22] [урок 23] [урок 24] [урок 25] [урок 26] [урок 27] [урок 28] [правила хорошего тона] [приложение A] [приложение Б] [приложение В] [заключение] [благодарности]
Copyright (c) ClericICN, 2002
В скалярном контексте возвращает логическое
Урок 22. Операторы поиска и замены Оператор m// m/PATERN/gimosx; /PATERN/gimosx; Поиск в строке по паттерну (шаблону). В скалярном контексте возвращает логическое значение true (1) или false (''). Если строка не указана с помощью операторов '=~' или '!~', поиск ведется в строке $_ . Опции: |
g | - Глобальный поиск. Поиск всех вхождений. |
i | - Сравнение не зависит от регистра (верхний или нижний) |
m | - Строка многострочная. |
o | - однопроходная компиляция |
s | - однострочная строка |
x | - используются расширенные регулярные выражения. |
PATTERN может содержать переменные, которые будут интерполироваться (перекомпилироваться) каждый раз в момент вычисления. Переменные $) и $| не интерполируются. Если вы хотите, что бы такой шаблон интерполировался один раз - добавьте /o. Это необходимо делать в циклах поиска для увеличения быстродействия, однако, если вы измените значение переменной, Perl этого даже не заметит.
Если PATERN - нулевая строка, то используется последнее регулярное выражение.
В скалярном контексте возвращается список элементы которого - результаты выполнения выражений в скобках паттерна ($1, $2, $3...). Обратите внимание что первый элемент $1.
Оператор S///
s/шаблон/подстрока/egimosx;
Поиск по шаблону и в случае успеха замена подстрокой. Возвращает количество произведенных подстановок, иначе false (0). Если строка в которой ведется поиск не указана (операторы =~ или != ), то используется переменная $_ . Если в качестве разделителя '/' использовать одинарную кавычку ('), то интерполяции не будет, иначе можно применять переменные в шаблоне или подстроке. Опции:
e | - Рассматривать правую часть как выражение. |
g | - Глобальный поиск. |
i | - Без различия регистра букв |
m | - многострочная переменная |
o | - компилировать шаблон один раз |
s | - однострочная переменная |
x | - расширенное регулярное выражение |
Разделитель '/' можно заменить на любой алфавитно-цифровой символ кроме пробела.
$var = "12345"; # исходная строка $var =~ s/1/0/; # Заменить '1' на '0'. Результат 02345 $var =~ s(5)(.); # Заменить '5' на '.' Результат 0234.
Здесь в качестве разделителя применены скобки, поэтому подстрока взята в две скобки.
$var =~ s/\d*/каламбур/; #Заменить все цифры. Результат 'каламбур.'
$var =~ s/а/о/g; # Заменить все 'а' на 'о'. Результат 'коломбур.'
$var = "12 34"; # Новое значение
$var =~ s/(\d\d) (\d\d)/$2 $1/; # Поменять местами числа. Результат '34 12'.
Операторы tr/// и y///
tr/таблица1/таблица2/cds;
y/таблица1/таблица2/cds;
Замена всех символов из "таблица1" на соответствующий символ из "таблица2". Результат - количество замен или стирании. Без оператора =~ или != операция выполняется со строкой $_. Для совместимости с программой sed вместо tr можно писать 'y'.
Опции:
c | - дополнение "таблица1" |
d | - стереть найденные, но не замененные символы. |
s | - "сжать" повторяющиеся замененные символы. |
$s = "hello"; # Исходная строка
$s =~ tr/a-z/A-Z/; # Заменить малые буквы на большие. Результат # 'HELLO'
$s = 'Hel....lo'; $s =~ tr/a-zA-z/_/c; # Заменить все не буквы на '_' # Результат 'Hel____lo' $s =~ tr/_/ /s; # Заменить '_' на ' ' и сжать. # Результат 'Hel lo' $s =~ tr/a-zA-Z /a-zA-Z/d; # Удалить все не буквы. Результат 'Hel
* * * *
Попробуйте написать функцию, которая запрашивает выражение и число. Функция должна возвращать номера слов в выражении, длина которых превышает переданное число.
Дополнительная информация: [регулярные выражения]
[Содержание] [Вступление] [необходимое для начала изучения] [урок 1] [урок 2] [урок 3] [урок 4] [урок 5] [урок 6] [урок 7] [урок 8] [урок 9] [урок 10] [урок 11] [урок 12] [урок 13] [урок 14] [урок 15] [урок 16] [урок 17] [урок 18] [урок 19] [урок 20] [урок 21] [урок 22] [урок 23] [урок 24] [урок 25] [урок 26] [урок 27] [урок 28] [правила хорошего тона] [приложение A] [приложение Б] [приложение В] [заключение] [благодарности]
Copyright (c) ClericICN, 2002
Выполнят блок или выражение для
Урок 23. Еще немного о циклах Оператор Grep grep {блок} список; grep (выражение, список); Выполнят блок или выражение для каждого элемента списка. Переменная $_ содержит текущий элемент. Возвращает список элементов, с которыми выражение дало результат true. В скалярном контексте возвращает количество результатов true. Как видите, grep очень похож на foreach: @tmp=(1,2,3); grep{print $_} @tmp; Оператор Map map {блок} список; map выражение, список; Выполняет блок или выражение для каждого элемента списка. Возвращает список результатов. @tmp=(1,2,3); $v = map $i.=$_, @tmp; print $i," "; print $v; * * * * [Содержание] [Вступление] [необходимое для начала изучения] [урок 1] [урок 2] [урок 3] [урок 4] [урок 5] [урок 6] [урок 7] [урок 8] [урок 9] [урок 10] [урок 11] [урок 12] [урок 13] [урок 14] [урок 15] [урок 16] [урок 17] [урок 18] [урок 19] [урок 20] [урок 21] [урок 22] [урок 23] [урок 24] [урок 25] [урок 26] [урок 27] [урок 28] [правила хорошего тона] [приложение A] [приложение Б] [приложение В] [заключение] [благодарности] |
Copyright (c) ClericICN, 2002 |
Урок 24. Функции для работы
Урок 24. Функции для работы с массивами Функция Pop pop массив; Удаляет и возвращает последний элемент массива. Длина массива уменьшается на 1. Результат не определен, если массив пустой. Если имя массива отсутствует, то извлекает из @ARGV для головной программы и @_ для подпрограммы. Функция Shift shift массив; shift; Удаляет первый элемент массива и возвращает его значение. Если массив пустой, то результат - неопределенность. Если аргумент опущен - обрабатывается массив @ARGV в головной программе или @_ в подпрограмме. Функция Unshift unshift массив, список; Противоположное shift. Помещает элементы списка в начало массива. Функция Push push массив, список; Добавить элементы массива значениями из списка. Длина массива увеличивается на количество элементов списка. Функция Sort sort подпрограмма список; sort блок список; sort список; Сортирует элементы списка и возвращает полученный список. Несуществующие элементы списка отбрасываются. Если не указано имя подпрограммы или блок, то сортирует в стандартном строковом порядке. Указанная подпрограмма возвращает значения больше, меньше или равное нулю в зависимости от двух соседних элементов списка. Имя подпрограммы может быть указано переменной. Она не должна быть рекурсивной, и два сравниваемых элемента списка передаются как глобальные переменные $main::a и $main::b. Это ссылки, поэтому их изменение приводит к изменению самих элементов списка. Функция Splice splice массив, смещение, длина, список; splice массив, смещение, длина; splice массив, смещение; Удаляет элементы массива, начиная со смещения и указанной длины. Заменяет их указанным списком. Возвращает удаленный список. Если длина не указана, удаляет все элементы списка, начиная со смещения Функция Reverse reverse список; В списковом контексте возвращает список с элементами в обратном порядке указанному списку. В скалярном контексте возвращает строку с переставленными символами первого элемента списка. * * * * Напишите программу, создающую массив со случайными значениями ячеек, и сортирующий их по убыванию. Напишите алгоритм, удаляющий все ячейки, значения которых ниже среднего значения всего массива. Выведите массив на экран. Сколько ячеек массива было изначально (возьмите для начала 200), и сколько вывелось на экран? Дополнительная информация: [массивы] [Содержание] [Вступление] [необходимое для начала изучения] [урок 1] [урок 2] [урок 3] [урок 4] [урок 5] [урок 6] [урок 7] [урок 8] [урок 9] [урок 10] [урок 11] [урок 12] [урок 13] [урок 14] [урок 15] [урок 16] [урок 17] [урок 18] [урок 19] [урок 20] [урок 21] [урок 22] [урок 23] [урок 24] [урок 25] [урок 26] [урок 27] [урок 28] [правила хорошего тона] [приложение A] [приложение Б] [приложение В] [заключение] [благодарности] |
Copyright (c) ClericICN, 2002 |
Урок 25. Функции для работы
Урок 25. Функции для работы с хешами Функция Delete delete выражение; Удалить значение из хеша. Возвращает удаляемое значение или неопределенность, если ничего не удаляется. Удаление из массива окружения $ENV{} - изменяет окружение. Удаление из хеша связанного с базой данных - удаляет запись в базе данных. #!/usr/bin/perl #programm 19 print "Content-Type: text/html\n\n"; %c=( 'Windows 98' => '1998', 'Windows 2000' => '1999', 'Windows XP' => '2001' ); $flag=1; delete $c{'Windows XP'}; @array=%c; print " |
$i | "; }else{$flag=1; print "$i |
Функция Each
each хеш;
Возвращает 2-элементный массив. Первый элемент - ключ второй - значение следующего элемента хеша. Позволяет "просматривать" все значения хеша в определенном порядке. Сброс переменной цикла происходит только после прохождения всего массива. Код ниже выводит все пары ключ-значение из хэша:
%c=( 'Windows 98' => '1998', 'Windows 2000' => '1999', 'Windows XP' => '2001' );
while(($key,$value) = each %c){ print "$key => $value
\n"; };
Функция Exists
exists выражение;
Возвращает true, если существует указанный ключ хеша, даже если не определено его значение.
%hash=( 'Windows 98' => '1998', 'Windows 2000' => '1999', 'Windows XP' => '2001' );
if(exists($hash{"Windows 98"})){ print "Элемент найден"; }else{ print "Элемент не найден"; }
Функция Keys
keys хеш;
Возвращает массив всех ключей хеша. В скалярном контексте - количество ключей. Порядок следования ключей аналогичен порядку в each функции. Следующий код выводит все ключи хэша:
%hash=( 'Windows 98' => '1998', 'Windows 2000' => '1999', 'Windows XP' => '2001' );
foreach $k (keys %hash){ print "$k
";}
Функция Values
values хеш;
Возвращает обычный массив со всеми значениями хеша. В скалярном контексте возвращает количество значений хеша.
%hash=( 'Windows 98' => '1998', 'Windows 2000' => '1999', 'Windows XP' => '2001' );
foreach $k (values %hash){ print "$k
"; }
* * * *
[Содержание] [Вступление] [ необходимое для начала изучения] [урок 1] [урок 2] [урок 3] [урок 4] [урок 5] [урок 6] [урок 7] [урок 8] [урок 9] [урок 10] [урок 11] [урок 12] [урок 13] [урок 14] [урок 15] [урок 16] [урок 17] [урок 18] [урок 19] [урок 20] [урок 21] [урок 22] [урок 23] [урок 24] [урок 25] [урок 26] [урок 27] [урок 28] [правила хорошего тона] [приложение A] [приложение Б] [приложение В] [заключение] [благодарности]
Copyright (c) ClericICN, 2002
Скорее всего эти функции Вам
Урок 26. Дата и время Скорее всего эти функции Вам будут недоступны, если вы используете Windows95/98. Функция Time time; Возвращает количество секунд, начиная с 1 января 1970 г 00:00:00 UTC. Годы не разделяются на високосные. Функция Localtime localtime выражение; В списковом контексте преобразовывает в 9-и элементарный массив значение, возвращаемое функцией time. Если аргумент опущен, используется сама функция time. В скалярном контексте возвращает строку, где все даты представлены на естественном языке. Способ употребления: ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time); Код ниже выводит значения функций time и localtime в списковом и скалярном контекстах: print time," "; print localtime," "; $time = localtime(time); print $time; Функция Times times; Возвращает 4-х элементный массив значений: ($user, $system, $cuser, $csystem) = times; $user - пользовательское время процесса в секундах. $system - системное время текущего процесса. $cuser - время процесса-потомка текущего процесса. $csystem - его системное время. Часто используется для определения времени работы скрипта: $start = (times)[0]; # помещяем в самое начало скрипта foreach $i(0..200000){ $u+=$i # что-то делаем } $gen_time = (times)[0]-$start; # а это - в конец скрипта. print "Время работы скрипта:"; print "$gen_time секунд."; Функция Gmtime gmtime выражение; Переводит местное время, полученное функцией time(), во время по Гринвичу. Обычно используется в виде: ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=gmtime(time); * * * * [Содержание] [Вступление] [необходимое для начала изучения] [урок 1] [урок 2] [урок 3] [урок 4] [урок 5] [урок 6] [урок 7] [урок 8] [урок 9] [урок 10] [урок 11] [урок 12] [урок 13] [урок 14] [урок 15] [урок 16] [урок 17] [урок 18] [урок 19] [урок 20] [урок 21] [урок 22] [урок 23] [урок 24] [урок 25] [урок 26] [урок 27] [урок 28] [правила хорошего тона] [приложение A] [приложение Б] [приложение В] [заключение] [благодарности] |
Copyright (c) ClericICN, 2002 |
Урок 27. Функции для работы
Урок 27. Функции для работы с директориями Функция Chdir chdir выражение; Перейти в директорию указанную выражением. Если выражение отсутствует то перейти в "домашнюю" директорию. Возвращает true в случае успеха и false - неудачи. Функция Chroot chroot директория; Сделать "корневой" указанную директорию. "Корневая" - значит внутри программы на нее можно ссылаться как '/'. Функция Mkdir mkdir директория, права; Создать директорию с указанными правами доступа. Возвращает 1 при успехе, и 0 при неудаче. Функция Opendir opendir указатель, выражение; Открыть директорию с именем выражения для обработки функциями readdir(), telldir(), seekdir(), rewinddir() и closedir(). Возвращает true при успехе. Функция Readdir readdir директория; Возвращает имя следующего файла в директории открытой командой opendir(). В скалярном контексте возвращает все оставшиеся имена файлов. Если файлов больше нет, то возвращает неопределенность в скалярном контексте и пустую строку в списковом. Функция Rmdir rmdir директория; Удалить указанную директорию, если в ней нет никаких файлов. Возвращает 1 при успехе и 0 - при неудаче. При этом переменная $! содержит код (errno). Функция Sseekdir seekdir директория, позиция; Установить указатель файла в открытой директории для последующего чтения функцией readdir(). Текущая позиция может быть получена функцией telldir(). Функция Telldir telldir директория; Возвращает текущую позицию указателя в открытой директории. Функция Rewindir rewindir директория; Устанавливает указатель файлов открытой директории в начало для последующего чтения функцией readdir(). *Ускоренный курс изучения Perl не подразумевает глубокого изучения функций для работы с директориями. Дополнительная информация по этой теме находится в разделе помощи. * * * * [Содержание] [Вступление] [необходимое для начала изучения] [урок 1] [урок 2] [урок 3] [урок 4] [урок 5] [урок 6] [урок 7] [урок 8] [урок 9] [урок 10] [урок 11] [урок 12] [урок 13] [урок 14] [урок 15] [урок 16] [урок 17] [урок 18] [урок 19] [урок 20] [урок 21] [урок 22] [урок 23] [урок 24] [урок 25] [урок 26] [урок 27] [урок 28] [правила хорошего тона] [приложение A] [приложение Б] [приложение В] [заключение] [благодарности] |
Copyright (c) ClericICN, 2002 |
Строго говоря, Вы уже овладели
Урок 28 заключительный. Прочие функции Строго говоря, Вы уже овладели необходимыми навыками для написания собственных CGI-приложений. В этом уроке я расскажу Вам о тех немногочисленных функциях, которые, в редких случаях, позволяют упростить процесс программирования или просто существуют, но остались не затронутыми в наших уроках. Функция Eval eval выражение; eval блок; Выражение сканируется и выполняется как обычная Perl программа. Это делается в контексте текущей программы, поэтому можно использовать уже определенные переменные и подпрограммы. Возвращается результат последнего оператора в блоке или значение оператора return. Если обнаружится синтаксическая ошибка или выполнится оператор die, то возвращается неопределенное значение, а переменная $@ содержит сообщение об ошибке. Если ошибки не было, то $@ содержит нулевую строку. При отсутствии аргумента берется значение переменной $_: $op = "print(1..10); print\"\n \""; eval $op; if ($@ eq ""){print "Ошибок не было"} else {print "Переменная \$op содержит ошибки"} Функция Exit exit выражение; Выполнение программы завершается с кодом выражения. Функция Goto goto метка; goto выражение; goto &подпрограмма Безусловный переход на метку. Нельзя делать переход на метку в структуру, которая должна быть инициирована, например, подпрограмму или цикл foreach и т.д. Вторая форма применяется для динамического определения перехода. Например: goto ('метка1', 'метка2', 'метка3')[$i]; Здесь при $i = 0 будет переход на 'метка1', $i = 1 на 'метка2' и т.д. Третья форма (goto &подпрограмма) довольно "хитрый" метод подмены имени вызываемой подпрограммы именем текущей. Это используется в методе автозагрузки, когда нужно запустить другую процедуру, но под именем текущей, как будто та была вызвана раньше. Функция quotemeta quotemeta выражение; Вычисляет выражение, в котором метасимволы рассматриваются как обычные символы. Функция Sleep sleep выражение; sleep; Приостанавливает работу скрипта на указанное число секунд или зацикливается, если аргумент отсутствует. Работа продолжается, если получен сигнал SIGALARM. Возвращает время фактической паузы. * * * * [Содержание] [Вступление] [необходимое для начала изучения] [урок 1] [урок 2] [урок 3] [урок 4] [урок 5] [урок 6] [урок 7] [урок 8] [урок 9] [урок 10] [урок 11] [урок 12] [урок 13] [урок 14] [урок 15] [урок 16] [урок 17] [урок 18] [урок 19] [урок 20] [урок 21] [урок 22] [урок 23] [урок 24] [урок 25] [урок 26] [урок 27] [урок 28] [правила хорошего тона] [приложение A] [приложение Б] [приложение В] [заключение] [благодарности] |
Copyright (c) ClericICN, 2002 |