Изучаем Perl

         

Открытие и закрытие дескриптора файла


В Perl есть три дескриптора файлов, stdin, stdout и stderr, которые автоматически открываются для файлов или устройств, установленных родительским процессом программы (вероятно, shell). Для открытия дополнительных дескрипторов используется функция open. Она имеет следующий синтаксис:

open(ДЕСКРИПТОР,"имя") ;

где дескриптор — новый дескриптор файла, а имя — имя файла (или устройства), которое будет связано с новым дескриптором. Этот вызов открывает файл для чтения. Чтобы открыть файл для записи, используйте ту же функцию open, но поставьте перед именем файла знак "больше" (как в shell):

open(OUT, ">выходной_файл");

Мы увидим, как использовать этот дескриптор, в разделе "Использование дескрипторов файлов". Как и в shell, файл можно открыть для добавления, поставив перед именем два знака "больше чем", т.е.

open (LOGFILE, "”мой_фаил_регчстрации") ;

Все формы функции open в случае успешного выполнения возвращают значение "истина", а в случае неудачи — "ложь". (Например, при попытке открытия файла для чтения выдается значение "ложь", если файла нет или доступ к нему запрещен; при открытии файла для вывода возвращается значение "ложь", если файл защищен от записи или если невозможна запись в каталог либо доступ к этому каталогу.)

Закончив работать с дескриптором файла, вы можете закрыть его, воспользовавшись операцией close:

close(LOGFILE) ;

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



Открытие и закрытие дескриптора каталога


Функция opendir работает аналогично библиотечному вызову с тем же именем в С и C++. Ей дается имя нового дескриптора каталога и строковое значение, задающее имя открываемого каталога. Если каталог может быть открыт, opendir возвращает значение "истина"; в противном случае она возвращает "ложь". Вот пример:

opendir(ETC,"/etc") || die "Cannot opendir /etc: $!";

После этого обычно следуют разного рода манипуляции с дескриптором каталога etc, но сначала, наверное, нужно разобраться, как закрывать дескриптор каталога. Это делается с помощью функции closedir, которая весьма похожа на close:

closedir(ETC);

Как и close, closedir часто оказывается ненужной, потому что все дескрипторы каталогов автоматически закрываются перед повторным открытием либо в конце программы.



Ответы к упражнениям




В этом приложении даны ответы к упражнениям, помещенным в конце каждой главы.



Передача и прием сигналов


Один из вариантов организации межпроцессного взаимодействия осно-ван на передаче и приеме сигналов. Сигнал — зто одноразрядное сообщение (означающее, что "произошло данное событие"), которое посылается в процесе из другого процесса или из ядра. Сигналам присваиваются номера, обычно из диапазона от единицы до небольшого числа, например 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).


Передача параметров через 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


Переименование файла


В 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.



Переменные


Переменная-массив содержит одно значение в виде списка (нуль или более скалярных значений). Имена переменных-массивов похожи на имена скалярных переменных. Единственное отличие состоит в первом символе —-это не знак доллара ($), а знак @. Например:

@fred # массив-переменная @fred @A_Very_Long_Array_Variable_Name @A_Very_Long_Array_Variable_Name_that_is_different

Отметим здесь, что переменная-массив @fred не имеет никакого отношения к скалярной переменной $fred. Для разных типов переменных Perl предусматривает отдельные пространства имен.

Переменная-массив, которой еще не присвоено значение, имеет значение (), т.е. пустой список.

Выражение может ссылаться на переменные-массивы в целом, а также проверять и изменять отдельные элементы таких массивов.



Перемещение по дереву каталогов


Вы уже, вероятно, знакомы с понятием "текущий каталог" и с тем, как использовать команду cd shell. В системном программировании для изменения текущего каталога процесса вы производили бы системный вызов chdir. В Perl тоже используется это имя.

Функция chdir в Perl принимает один аргумент — выражение; при его вычислении определяется имя каталога, который становится текущим. Как и в большинстве других системных вызовов, при успешном изменении текущего каталога на затребованный chdir возвращает значение "истина", а при неудачном исходе — "ложь". Вот пример:

chdir("/etc") || die "cannot cd to /etc ($!)";

Круглые скобки не обязательны, поэтому можно обойтись и такой записью:

print "where do you want to go? ";

chomp($where = <STDIN>) ;

if (chdir $where) f

# получилось ) else {

# не получилось >

Вы не сможете определить, где вы находитесь, не запустив команду pwcf. О запуске команд мы расскажем в главе 14.

* Или не использовав функцию getcwd () из модуля Cwd.

Для каждого процесса* назначается свой текущий каталог. Когда запускается новый процесс, он наследует текущий каталог своего родительского процесса, но на этом вся связь заканчивается. Если Perl-программа меняет свой каталог, это не влияет на родительский shell (или иной объект), который запустил этот Perl-процесс. Точно так же процессы, создаваемые Perl-программой, не влияют на текущий каталог самой этой программы. Текущие каталоги для новых процессов наследуются от текущего каталога Perl-программы.

По умолчанию функция chdir без параметра делает текущим начальный каталог, почти так же, как команда cd

shell.



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 можно найти обширную документацию и примеры.


Поддержка


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.



Поиск и устранение ошибок в 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,



Поиск подстроки


Успех поиска подстроки зависит от того, где вы ее потеряли. Если вы потеряли ее в большей строке — вам повезло, потому что в такам случае может помочь операция index. Вот как ею можно воспользоваться:

$х = index($сгрока,$подстрока)

Perl находит первый зкземпляр указанной подстроки в заданной строке и возвращает целочисленный индекс первого символа. Возвращаемый ин-декс отсчитывается от нуля, т.е. если подстрока

найдена в начале указанной строки,

вы получаете 0. Если она найдена на символ дальше, вы получаете 1 и т.д. Если в указанной строке нет подстроки, вы получаете -1.

Вот несколько примеров:

$where = index("hello","e") ;

$person = "barney";

$where = index("fred barney",$person);

$rockers = ("fred","barney") ;

$where = index(join(" ",Orockers),$person); # то же самое

Отметим, что и строка, в которой производится поиск, и строка, которая ищется, может быть литеральной строкой, скалярной переменной, содержа-щей строку, и даже выражением, которое имеет строковое значение. Вот еще несколько примеров:

$which ° index("a very long string","long"); # $which получает 7 $which as index("a very long string","lame"); # $which получает -1

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

$х = index($большая_строка,$маленькая_строка,$пропуск}

Вот несколько примеров того, как работает зтот третий параметр:

$where = index("hello world","!"); # возвращает 2 (первая буква 1) $where = index("hello world","I",0); # то же самое $where =• index("hello world","I",1); # опять то же самое $where = indexC'hello world", "I", 3) ; # теперь возвращает З


# (3 - первая позиция, которая больше или равна 3) $where = indexC'hello world", "о", 5) ; # возвращает 7 (вторая о) $where ° indexC'hello world", "о", 8) ; # возвращает -1 (ни одной после 8)

Вы можете пойти другим путем и просматривать строку справа налево с помощью функции rindex, чтобы получить правый крайний зкземпляр. Зта функция тоже возвращает количество символов между левым концом строки и началом подстроки, как и раньше, но вы получите крайний правый зкземпляр, а не первый слева, если их несколько. Функция rindex тоже принимает третий параметр, как и функция index, чтобы вы могли получить целочисленный индекс первого символа зкземпляра подстроки, который меньше или равен адресу выбранной позиции. Вот несколько примеров того, что можно таким образом получить:

$w = rindex("hello world","he"); # $w принимает значение О

$w = rindex("hello world","!"); # $w принимает значение 9 (крайняя правая 1)

$w = rindex("hello world","о"); # $w принимает значение 7

$w = rindex("hello world","o "); # теперь $w принимает значение 4

$w = rindex("hello world","xx"); t $w принимает значение -1 (не найдена)

$w = rindex("hello world","о",6); # $w принимает значение 4 (первая до 6)

$w = rindex ("hello world", "o",3) ; # $w принимает значение -1 (не найдена до 3)


Получение информации о паролях и группах


Информация о вашем пользовательском имени и идентификаторе, кото-рая имеется в системе UNIX, практически открыта. По сути дела, любая программа, которая не сочтет за труд заглянуть в файл /etc/passwd,

поможет вам увидеть почти все, кроме незашифрованного пароля. Зтот файл имеет особый формат, определяемый в passwd(5),

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

name:pa3swd:uid:gid:gcos:dir:shell

Поля определены следующим образом:

name

Регистрационное имя пользователя

passwd

Зашифрованими пароль или что-нибудь простое, если используется теневой файл паролей

uid

Идентификатор пользователя (для пользователя root — 0, для обычных пользователей — ненулевое число)

gid

Регистрационная группа по умолчанию (группа 0 может быть привиле-гированной, но не обязательно)

gcos

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

dir

Начальний каталог (каталог, в который вы переходите, когда даєте команду cd без аргументов, и в котором хранится большинство ваших файлов, имена которых начинаются с точки)

shell

Ваш регистрационный shell, как правило, /bin/sh

или /Ып/csh (а, может быть, даже /usr/bin/perl,

если вы большой оригинал)

Типичные злементы файла паролей выглядят так:

fred:*:123:15:Fred Flintstone,,,:/home/fred:/bin/csh barney:*:125:15:Barney Rubble,,,:/home/barney:/bin/csh

Сейчас в Perl достаточно инструментов для того, чтобы можно было легко выполнить разбор такой строки (например, с помощью функции split), не прибегая к специальным программам. Тем не менее в библиотеке UNIX все же єсть набор специальных программ: getpwent(3), getpwuid(3), gelpwnam(3) и т.д. Зти программы доступны в Perl под теми же именами, с похожими аргументами и возвращаемыми значеннями.

Например, программа getpwnam в Perl становится функцией getpwnam. Ее единственный аргумент — пользовательское имя (например, fred или barney), а возвращаемое значение — строка файла /etc/passwd, преобра-зованная в массив со следующими значеннями:

($name, $passwd, $uid, $gid, $quota, $cominent, $gcos, $dir, $shell)


Обратите внимание: здесь несколько больше значений, чем в файле паролей. Обычно в UNIX-системах, по крайней мере в тех, которые мы видели, поле $quota всегда пусто, а поля $comment и $gcos часто оба содержат персональную информацию о пользователе (поле GCOS). Так, для старины Фреда мы получаем

("fred", "*", 123, 15, "", "Fred Flintstone,,,", "Fred Flintstone,,,", "/home/gred"," /bin/csh")

посредством любого из следующих вызовов:

getpwuid(123) getpwnam("fred")

Отметим, что в качестве аргумента функция getpwuid принимает иден-тификатор пользователя, a getpwnam — регистрационное имя.

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

$idnum = getpwuid("daemon");

$login °= getpwnam (25);

Возможно, вам захочется получить зти результаты по отдельности, ис-пользуя некоторые из уже знакомых вам операций, проводимых над списками. Один способ — получить часть списка, используя для зтого срез списка, например получить для Фреда только начальний каталог:

($fred home) = (getpwnam ("fred"))[7]; # начальний каталог Фреда

Как просмотреть весь файл паролей? Для зтого можно бьгло бн поступить, к примеру, так:

for($id = 0; $id <= 10_000; $id++) f @stuff = getpwuid $id;

} ### не рекомендуется!

Зто, однако, неверный путь. Наличие нескольких способов само по себе еще не означает, что все они в равной степени зффективны.

Функции getpwuid и getpwnam можно считать функциями произволь-ного доступа; они извлекают конкретний злемент по его ключу, позтому для начала у вас должен быть ключ. Другой метод доступа к файлу паролей — последовательный, т.е. поочередное получение его записей.

Программами последовательного доступа к файлу паролей являются функции setpwent, getpwent и endpwent. В совокупности зти три функции выполняют последовательный проход по всем записям файла паролей. Функция setpwent инициализирует просмотр. После инициализации каж-дый вызов getpwent возвращает следующую запись файла паролей. Если данных для обработки больше нет, getpwent возвращает пустой список. Наконец, вызов endpwent освобождает ресурсы, используемне программой просмотра; зто делается автоматически и при выходе из программы.



Приведенное описание может сказаться не совсем понятным без приме-ра, позтому дадим его:

setpwent (); # инициализировать просмотр while (@list " getpwent ()) ( # выбрать следующий злемент

($login,$home) = @list[0,7]; # получить регистрационное имя # и начальний каталог

print "Home directory for $login is $home\n"; # сообщить зто } endpwent(); # все сделано

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

setpwentf); # инициализировать просмотр while (@list = getpwentO) ( # вибрать следующий злемент

($login,$home) = @list[0,7]; # долучить регистрационное имя # и начальний каталог

$home($login} = $home; # сохранить их )

endpwent(); # все сделано Skeys = sort ( $home($a( cmp $home($b) } keys %home;

foreach $login (Skeys) ( # пройти по рассортированньпл именам

print "home of $login is $home($login)\n";

}

Зтот несколько более длинный фрагмент иллюстрирует важную особен-ность последовательного просмотра файла паролей: вы можете сохранять соответствующие фрагменты данных в структурах данных, выбираемых по своєму усмотрению. Первая часть примера — зто код просмотра всего файла паролей с созданием хеша, в котором ключ — регистрационное имя, а значение — начальний каталог, соответствующий зтому регистрационному имени. Строка sort получает ключи хеша и сортирует их в соответствии со строковим значением. Завершающий цикл — зто проход по рассортирован-ным ключам и поочередный вывод всех значений.

В общем случае для просмотра небольшого количества значений реко-мендуется использовать программы произвольного доступа (getpwuid и getpwnam). Если значений много или необходим просмотр всех значений, проще выполнить проход с последовательним доступом (с помощью функ-ций setpwent, getpwent и endpwent) и помостить конкретные значення, которые вы будете искать, в хеш*.

Доступ к файлу /etc/group

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

<$name, $passwd, $gid, $members)

Зти четыре значення примерно соответствуют четырем полям файла /etc/group,

позтому за подробной информацией обращайтесь к описанням, приведенным на man-страницах, относящихся к формату зтого файла. Соответствующие функций произвольного доступа — getgrgid (по иден-тификатору группы) и getgrnam (по имени группы).

* Если у вас узел с большой NIS-картой, то по соображениям производительности такой способ предобработки файла паролей лучше не использовать.






Получение информации о сети


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

Один из параметров, который вам приходится часто определять,— зто IP-адрес, соответствующий сетевому имени (или наоборот). В С вы преоб-разуете сетевое имя в сетевой адрес с помощью программы gethostbyname(3). Затем, используя полученный адрес, вн устанавливаете связь между своей программой и другой программой, которая работает где-то в другом месте.

В Perl функция, преобразующая хост-имя в адрес, имеет то же имя, что и С-программа, и похожие параметры. Выглядит она так:

($name, $aliases, $addrtype, $length, @addrs) = gethostbyname($name);

# основная форма функций gethostbyname

Параметр зтой функций — имя хоста, например, slate.bedrock.com, а возвращаемое значение — список из четырех и более параметров (в зави-симости от того, сколько адресов связано с данным именем). Если имя хоста недействительно, функция возвращает пустой список.

Когда gethostbyname вызывается в скалярном контексте, возвращается только первый адрес.

Если gethostbyname завершается успешно, то переменной $name в качестве значення присваивается каноническое имя, которое, если входное имя — псевдоним, отличается от входного имени. Значение переменной $aliases — зто список разделенных пробелами имен, под которыми данный хост известен в сети. Переменная $addrtype содержит кодовое обозначение формата представлення адреса. Для имени slate. bedrock. corn мы можем предположить, что зто значение указывает на IP-адрес, обычно представляемый как четыре числа из диапазона от 1 до 256, разделенных точками. Переменная $length содержит количество адресов. Зто лишняя информация, так как в любом случае можно посмотреть на размер массива @addrs.


Наиболее полезная часть возвращаемого значення — массив @addrs. Каждый злемент данного массива — зто отдельный IP-адрес, представлен-ный во внутреннем формате и обрабатываемый в Perl как четырехсимвольная строка*. Зта четырехсимвольная строка представлена в формо, понятной для других сетевых Perl-функций. Однако предположим, что нам требуется вывести результат в виде, удобном для пользователя. В данном случае нам нужно с помощью функции unpack и еще нескольких операций преобра-зовать возвращаемое значение в удобочитаемый формат. Вот код, который обеспечивает вывод одного из ІР-адресов хоста slate.bedrock.com:

($addr) = (gethostbyname("slate.bedrock.com"))[4];

print "Slate's address is ",

join(".",unpack ("C4", $addr)), "\n";

Функция unpack получает четырехбайтовую строку и возвращает четыре числа. Оказывается, они стоят именно в том порядке, который нужен функции join для того, чтобы она вставила между каждой парой чисел точку и представила таким образом все зто в удобочитаемой форме. Информация о простих программах-клиентах приведена в приложении В.


Полулокальные переменные, созданные при помощи функции local


В Perl существует еще один способ создания "частных" локальных переменных — с помощью функции local. Важно понять различия между my и local. Например:

$values = "original";

tellmeO ;

spoof() ;

tellmeO ;

sub spoof (

local ($value) = "temporary";

tellmeO ;

> sub telime {

print "Current value is $value\n";

}

На выходе получаем следующее:

Current value is original Current value is temporary Current value is original

Если бы вместо local мы использовали my, то локальный вариант переменной $value был бы доступен только в пределах подпрограммы spoof (). При использовании функции local, как видно по результатам программы, это локальное значение не совсем локальное; оно доступно и во всех остальных подпрограммах, вызываемых из spoof (). Общее правило таково: локальные переменные доступны для функций, вызываемых из того блока, в котором эти функции объявлены.

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

#1 и @argv, с помощью my объявлять нельзя, а вот с local они работают прекрасно. Поскольку $_ используется в большинстве Perl-программ, то будет, вероятно, разумным помещать строку

local $_;

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

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

В большинстве случаев рекомендуем использовать не local, a my, потому что эта операция действует быстрее и надежнее.



Пожалуйста, пишите нам


Комментарии и вопросы по этой книге направляйте, пожалуйста, в издательство по адресу:

    О ' Reilly & Associates 101 Morris Street Sebastopol, CA 95472 тел. 1-800-998-9938 (в США и Канаде) тел. 1-707-829-0515 (международный или местный) факс 1-707-829-0104

    Сообщения можно посылать и в электронной форме. Чтобы попасть в список рассылки или запросить каталог, пошлите сообщение по адресу nuts@ora.com.

    Сообщения с техническими вопросами и комментариями по книге направляйте по адресу Ошибка! Закладка не определена..

|     Назад    

|     Вперед    

|

| Содержание | Предисловие | Введение | Ссылки

| Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10

| Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19

| Приложение А | Приложение Б | Приложение В | Приложение Г |



Приветствую вас, начинающие волшебники. Надеюсь,


Внимание, класс! Внимание! Спасибо.
     Приветствую вас, начинающие волшебники. Надеюсь, летние каникулы принесли вам море удовольствия и, наверное, показались слишком корот-кими. Позвольте мне быть первым, кто приветствует вас в Колледже кол-довства, а точнее — на этом вводном курсе по Волшебству 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

| Приложение А | Приложение Б | Приложение В | Приложение Г |

Преобразование 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-код читается достаточно легко (учитывая, что перевод выполняется автоматически, сле-дует отметить зто как большое достижение).



Преобразование 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. В большинстве случаев, однако, вы увидите, что зто несколько напоминает конвертирование программы, написанной на Коболе, в С (почти с тем же уменьшением количества символов й, как следствие, повышением степени неразборчивости текста программы).



В этой главе:


В этой главе:
Глава 2

Глава 3

Глава 4

Глава 5

Глава 6

Глава 7

Глава 8

Глава 9

Глава 10

Глава 11

Глава 12

Глава 13

Глава 14

Глава 15

Глава 16

Глава 17

Глава 18

Глава 19

Прогулка по стране 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, то получили бы предупреждение на этот счет. Именно поэтому мы его здесь опустили.


Простейшая 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, о котором мы упоминали выше.


Развертывание


Если в качестве аргумента в командной строке стоит звездочка (*), то shell (или тот интерпретатор командной строки, которым вы пользуетесь) интерпретирует ее как список имен всех файлов, находящихся в текущем каталоге. Так, если вы дали команду rm *, то она удалит все файлы из текущего каталога. (Не делайте этого, если не хотите потом долго приставать к системному администратору с просьбами о восстановлении файлов.) Еще примеры: аргумент [а-т] *. с превращается в список имен файлов, находящихся в текущем каталоге, которые начинаются с одной из букв первой половины алфавита и заканчиваются на .с, а аргумент /etc/host* — в список имен файлов каталога /etc,

которые начинаются со слова host. (Если для вас это ново, то перед дальнейшей работой рекомендуем почитать дополнительную литературу о сценариях shell.)

Преобразование аргументов вроде * или /etc/host* в список соответствующих имен файлов называется развертыванием (globbing). Perl обеспечивает развертывание с помощью очень простого механизма: подлежащий развертыванию образец заключается в угловые скобки или же используется функция glob.

@а = </etc/host*> @а = glob("/de/host*");

В списочном контексте, как показано выше, результатом развертывания является список всех имен, которые совпадают с образцом (как если бы shell раскрыл указанные аргументы), или пустой список, если совпадения не обнаружено. В скалярном контексте возвращается следующее совпадающее имя, а если совпадений нет — возвращается undef; это очень похоже на чтение из дескриптора файла. Например, просмотр имен по одному реализуется с помощью следующего кода:

while (defined($nextname = </etc/host*>)) ( print "one of the files is $nextname\n";

>

* Это справедливо для UNIX и большинства других современных операционных систем.

Здесь возвращенные имена файлов начинаются с префикса, соответствующего пути доступа к ним (/etc/host),

поэтому, если вам нужна только последняя часть имени, придется выделять ее самостоятельно, например:


while ($nextname = </etc/host*>) (

$nextname =~ s#.*/##; # удалить часть до последней косой черты

print "one of the files is $nextname\n";

}

Внутри аргумента допускается указывать несколько образцов; эти списки развертываются отдельно, а затем конкатенируются так, как будто это один большой список:

@fred_barney_files = <fred* barney*>;

Другими словами, эта операция развертывания выдает те же значения, которые возвратила бы эквивалентная ей команда echo с такими же параметрами.*

Хотя развертывание списка файлов и сопоставление с регулярным выражением осуществляются почти одинаково, специальные символы имеют совершенно разный смысл. Не путайте эти механизмы, иначе будете удивляться, почему вдруг <\. с$> не находит все файлы, имена которых заканчиваются на .с!

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

if (-d. "/usr/etc") (

$where = "/usr/etc";

} else (

$where = "/etc";

) Sfiles = <$where/*>;

Здесь переменной $ where присваивается одно из двух имен каталогов в зависимости от того, существует каталог /usr/etc или нет. Затем мы получаем список файлов, находящихся в выбранном каталоге. Обратите внимание:

переменная $where является развертываемой, т.е. подлежащими развертыванию символами являются /etc/*

или /usr/etc/*.

Есть одно исключение из этого правила: образец <$var> (который означает использование в качестве развертываемого выражения переменной $var) должен записываться как <${var}>. В причины появления этого исключения сейчас лучше не вдаваться.**

* Для вас не будет сюрпризом узнать о том, что для выполнения развертывания Perl просто запускает C-shell, который раскрывает указанный список аргументов, и производит синтаксический анализ того, что получает обратно.

** Конструкция <$fred> читает строку из дескриптора файла, заданного содержимым скалярной переменной $ fred. Наряду с некоторыми другими особенностями, не упомянутыми в нашей книге, эта конструкция позволяет использовать косвенные дескрипторы файлов, где имя дескриптора передается и обрабатывается так, как будто это данные.






Сборник часто задаваемых вопросов


Часто задаваемые вопросы (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.



Скалярные операции


Оператор обозначает проведение определенной операции, благодаря которой создается новое значение (результат) из одного или нескольких других значений (операндов).

Например, символ + обозначает операцию, потому что при его использовании берутся два числа (операнда, например, 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
Вы, возможно, спросите, почему предусмотрены отдельные операции для чисел и строк, если числа можно автоматически преобразовывать в строки и наоборот. Давайте рассмотрим два значения, 7 и 30. Если их сравнить как числа, то 7, очевидно, меньше 30. Если же сравнить их как строки, то строка "30" идет перед строкой "7" (ведь ASCII-значение цифры 3 меньше ASCII-значения цифры 7), поэтому 30 меньше 7. Perl всегда требует указывать тип сравнения, то есть конкретизировать, какое именно сравнение, числовое или строковое, будет проводиться.

Если вы уже имеете опыт программирования на 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. В целях обеспечения безопасности всегда задавайте эту опцию.






Скалярные операции и функции


Самая распространенная операция, выполняемая над скалярной переменной — присваивание, которое представляет собой способ задания значения этой переменной. Операция присваивания в 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.)


Скалярные переменные


Имя переменной — это имя контейнера, который содержит одно или более значений. Имя переменной остается постоянным на протяжении всей программы, но значение (или значения), содержащееся в этой переменной, как правило, в ходе выполнения программы непрерывно изменяется.

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

переменная $А и переменная $а — разные. Все буквы, цифры и знаки подчеркивания в имени переменной имеют значение. Так, переменная

$a_very_long_variable_that_ends_in_l

отличается от переменной

$a_very_long_variable_that_ends_in_2

Имена переменных следует, как правило, подбирать так, чтобы они имели какой-то смысл в связи со значением переменной. Например, имя $xyzl23, вероятно, не очень описательно, в отличие от $line_length.



Скалярный и списочный контексты


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

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

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

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



Содержание:


Предисловие

Введение

Ссылки

Где найти упражнения

FTP

FTPMAIL

BITFTP

UUCP

Телеконференции Usenet

Домашняя страница Perl

Сборник часто задаваемых вопросов

Сообщения о дефектах

Как распространяется Perl

Другие книги

Как получить Perl

Использование анонимного FTP

Как выбирать модули

Обозначения, принятые в книге

Поддержка

Благодарности: первое издание

Благодарности: второе издание

Пожалуйста, пишите нам

Глава 1

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

Назначение языка Perl

Доступность

Основные полятия

Прогулка по стране Perl

Упражнение

Глава 2

Что такое скалярные данные

Скалярные операции

Скалярные переменные

Скалярные операции и функции

<STDIN> как скалярное значение

Упражнения

Глава 3

Список и массив

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

Переменные

Операции над массивами и функции обработки массивов

Скалярный и списочный контексты

<STDIN> как массив

Интерполяция массивов

Упражнения

Глава 4

Блоки операторов

Оператор if/unless

Оператор while/until

Оператор for

Оператор foreach

Упражнения

Глава 5

Что такое хеш

Хеш-переменные

Литеральное представление хеша

Хеш-функции

Срезы хешей

Упражнения

Глава 6

Ввод из STDIN

Ввод из операции "ромб"

Вывод в STDOUT

Упражнения

Глава 7

Основные понятия

Основные направления использования регулярных выражений

Образцы

Еще об операции сопоставления

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

Функции split и join

Упражнения

Глава 8

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

Вызов пользовательской функции

Возвращаемые значения

Аргументы

Локальные переменные в функциях

Полулокальные переменные, созданные при помощи функции local

Создаваемые операцией my() переменные файлового уровня

Упражнения

Глава 9

Оператор last

Оператор next

Оператор redo

Метки

Модификаторы выражений

Операции && и || как управляющие структуры

Упражнения

Глава 10

Что такое дескриптор файла

Открытие и закрытие дескриптора файла

Небольшое отступление: функция die

Использование дескрипторов файлов


Операции для проверки файлов

Функции stat и Istat

Упражнения

Глава 11

Что такое формат

Определение формата

Вызов формата

Еще о поледержателях

Формат начала страницы

Изменение в форматах установок по умолчанию

Упражнения

Глава 12

Перемещение по дереву каталогов

Развертывание

Дескрипторы каталогов

Открытие и закрытие дескриптора каталога

Чтение дескриптора каталога

Упражнения

Глава 13

Удаление файла

Переименование файла

Создание для файла альтернативных имен: связывание ссылками

Создание и удаление каталогов

Изменение прав доступа

Изменение принадлежности

Изменение меток времени

Упражнения

Глава 14

Использование функций system и ехес

Использование обратных кавычек

Использование процессов как дескрипторов файлов

Использование функции fork

Сводка операций, проводимых над процессами

Передача и прием сигналов

Упражнения

Глава 15

Поиск подстроки

Извлечение и замена подстроки

Форматирование данных с помощью функции sprintf()

Сортировка по заданным критериям

Транслитерация

Упражнения

Глава 16

Получение информации о паролях и группах

Упаковка и распаковка двоичных данных

Получение информации о сети

Упражнение

Глава 17

DBM-базы данных и DBM-хеши

Открытие и закрытие DBM-хешей

Использование DBM-хеша

Базы данных произвольного доступа с записями фиксированной длины

Базы данных с записями переменной длины (текстовые)

Упражнения

Глава 18

Преобразование awk-программ в Perl-программы

Преобразование sed-программ в Perl-программы

Преобразование shell-сценариев в Perl-программы

Упражнение

Глава 19

Модуль CGI.pm

Ваша CGI-программа в контексте

Простейшая CGI-программа

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

Как сократить объем вводимого текста

Генерирование формы

Другие компоненты формы

Создание CGI-программы гостевой книги

Поиск и устранение ошибок в CGI-программах

Perl и Web: не только CGI-программирование

Дополнительная литература

Упражнения

Приложение А

Приложение Б

Приложение В

Приложение Г

|     Вперед    

|


Сообщения о дефектах


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



Сортировка по заданным критериям


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

Чтобы задать порядок сортировки, следует определить программу срав-нения, которая задает способ сравнения двух злементов. Для чего она нужна? Нетрудно понять, что сортировка — зто размещение множества злементов в определенном порядке путем их сравнения между собой. Поскольку сравнить сразу все злементы нельзя, нужно сравнивать их по два й, используя результати зтих попарных сравнений, расставить все их по местам.

Программа сравнения определяется как обычная подпрограмма. Она будет вызываться многократно, и каждый раз ей будут передаваться два аргумента сортируемого списка. Данная подпрограмма должна определить, как первое значение соотносится со вторым (меньше, равно или больше), и возвратить закодированное значение (которое мы опишем чуть ниже). Зтот процесе повторяется до тех пор, пока не будет рассортирован весь список.

Чтобы повысить скорость вьшолнения, зти два значення передаются в подпрограмму не в массиве, а как значення глобальных переменных $а и $Ь. (Не волнуйтесь: исходные значення $ а и $Ь належно защищены.) Зта подпрограмма должна возвратить любое отрицательное число, если $а меньше $Ь, нуль, если $а равно $Ь, и любое положительное число, если $а больше $Ь. Теперь учтите, что "меньше чем" соответствует вашому пониманию зтого результата в данном конкретном случае; зто может бьггь сравнение чисел, сравнение по третьому символу строки, наконец, сравнение по значенням какого-то хеша с использованием передаваемьгх значений как ключей — в общем, зто очень гибкий механизм.

Вот пример подпрограммы сортировки в числовом порядке:

sub by_number (


if ($a < $b) ( return -1;

} elsif ($a == $b) ( return 0;

} elsif ($a > $b) ( return 1;

) >

Обратите внимание на имя by_number. На первый взгляд, в имени зтой подпрограммы нет ничего особенного, но скоро вы поймете, почему нам нравятся имена, которые начинаются с префикса Ьу_.

Давайте разберем зту подпрограмму. Если значение $а меньше (в данном случае в числовом смысле), чем значение $Ь, мы возвращаем значение -1.

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

Как использоватьданную программу? Давайте попробуєм рассортировать такой список:

Bsomelist = (1,2,4,8,16,32,64,128,256);

Если использовать с зтим списком обычную функцию sort без всяких "украшений", числа будут рассортированы так, как будто зто строки, причем сортировка будет выполнена с учетом кодов ASCII, т.е.:

Swronglist = sort Osomelist; # Owronglist теперь содержит (1,128,16,2,256,32,4,64,8)

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

@rightlist = sort by_number Swronglist;

* @rightlist теперь содержит (1,2,4,8,16,32,64,128,256)

Задача решена. Обратите внимание: функцию sort можно прочитать вместе с ее спутницей, программой сортировки, на человеческом языке, т.е. "рассортировать по числовым значенням". Бот почому мы использовали в имени подпрограммы префикс Ьу_ ("по").

Такое тройное значение (-1, 0, +1), отражающее результаты сравнения числовьк значений, встречается в программах сортировки достаточно часто, позтому в Perl єсть специальная операция, которая позволяет сделать все зто за один раз. Зту операцию часто называют "челноком" (или "космическим кораблем", как следует из дословного перевода английского spaceship), потому что ее знак — <=>.



Используя "космический корабль", можно заменить предыдущую под-программу сортировки следующим кодом:

sub by_number ( $а <=> $b;

}

Обратите внимание на знак операции между двумя переменными. Да, он действительно состоит из трех символов. Зта операция возвращает те же значення, что и цепочка if/elsif из предыдущего определения зтой программы. Теперь все записано очень кратко, но зтот вызов можно сокра-тить и дальше, заменив имя подпрограммы сортировки самой подпрограм-мой, записанной в той же строке:

@rightlist " sort ( $а <=> $b } @wronglist;

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

Для операции сравнения числових значений "челнок" єсть соответст-вующая строковая операция — стр*. Зта операция возвращает одно из трех значений в зависимости от результата сравнения двух аргументов по строковим значенням. Вот как можно по-другому записать порядок сортировки, который установлен по умолчанию:

@result = sort ( $а cmp $b } Osomelist;

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



sub by mostly_numeric (

($a <=> $b) I I ($a cmp $b) ;

)

Зтот код работает следующим образом. Если результат работы "челнока" равен -1 или 1, то остальная часть вираження пропускается и возвращается -1 или 1. Если "челнок" дает нуль, то вьшолняется операция cmp, которая возвращает соответствующее значение, сравнивая сортируемие значення как строки.

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

Сделать зто довольно легко. Давайте Предположим, что значення находятся в массиве % names. Регистрационнне имена, таким образом, представляют собой список keys (%names). Нам нужно получить список регистрационных имен, рассортированных по соответствующим значенням, позтому для любого конкретного ключа $а мы должны проверить значение $ names ($а} и провести

* Не вполне соответствующая. Встроенная функция sort отбрасывает злементы undef, а зта функция — нет.

сортировку относительно данного значення. Если следовать зтой логике, то программа практически напишется сама:

@sortedkeys = sort_by_name keys (%names);

sub by_names (

return $names{$a} cmp $names($b};

) foreach (@sortedkeys) {

print "$_ has a real name of $names($_)\n";

}

K зтому нужно еще добавить "аварийное" сравнение. Предположим, что реальные имена двух пользователей совпадают. Из-за капризной натуры программы sort мы в перами раз можем получить зти значення в одном порядке, а во второй раз — в другом. Зто плохо, если данный результат придется, например, вводить в программу сравнения для формирования отчета, позтому следует избегать таких вещей. Задача легко решается с помощью операции cmp:

sub by_names {

($names($a} cmp $names($b}) || ($a cmp $b);

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


Создание 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-программу;

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


Создание для файла альтернативных имен: связывание ссылками


Иногда пользователю нужно, чтобы у файла было два, три, а то и дюжина имен (как будто одного имени файлу не хватает!). Операция присвоєння файлу альтернативних имен называется создание ссылок. Две основних формы создания ссылок — ато создание жестких ссылок и создание симво-лических (или мягких) ссылок. Не все виды файлових систем подцерживают оба типа сеилок или хотя би один из них. В зтом разделе описани файловие системи, соответствующие стандарту 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 (или как там они называются у вас в системо) автоматически.



Создаваемые операцией my() переменные файлового уровня


Операцию my () можно также использовать на внешнем уровне программы, вне всех подпрограмм и блоков. Хотя в результате не будет получена "локальная" переменная в описанном выше смысле, это может оказаться достаточно полезным, особенно при использовании в сочетании с Рет\-прагмой*

use strict;

* Прагма — это директива компилятора. Среди этих директив — директивы задания целочисленных арифметических операций, перегрузки числовых операций, запрашивания дополнительных текстовых предупреждений и сообщений об ошибках. Эти директивы описаны в главе 7 книги Programming Perl и на man-странице perlmodlib(l).

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

use strict;

my $а; # сначала значение undef my @b = qw(fred barney betty); # присвоить начальное значение

push @b, qw(wilma); # разве можно забыть про Вильму? @с = sort @b; # HE КОМПИЛИРУЕТСЯ

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

Преимущества принудительного объявления переменных таковы:

1. Ваши программы будут работать несколько быстрее (обращение к переменным, созданным с помощью my, производится несколько быстрее, чем к обычным переменным).*

2. Гораздо быстрее будут выявляться ошибки набора, потому что вы больше

не сможете случайно обратиться к несуществующей переменной $ freed,

когда вам будет нужна переменная $fred.

По этой причине многие программисты, пишущие на Perl, автоматически начинают каждую новую программу прагмой use strict.



Список и массив


Список — это упорядоченные скалярные данные. Массив -- переменная, которая содержит список. Каждый элемент

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

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



Срезы хешей


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

$score("fred"} - 205;

$score("barney"} = 195;

$scoref"dino"} = 30;

Все это можно записать одной строкой:

($score("fred"},$score("barney"),$score("dino")) ° (205,195,30);

Но даже она слишком длинна, поэтому давайте используем срез хеша:

$score{"fred","barney","dino"} = (205,195,30);

Вот так гораздо короче. Можно сочетать использование среза хеша и интерполяции переменных:

@players = qwffred barney dino);

print "scores are: @score(@players}\n";

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

@league{keys %score( = values %score;

Здесь значения хеша %score сливаются с хешем %league. Это эквивалентно выполнению гораздо более медленной операции:

%league = (%league, %score); = # слить %score с %league



<STDIN> как массив


Одна из ранее изученных нами операций, которая в списочном контексте возвращает иное значение, чем в скалярном, — <stdin>. Как упоминалось выше, <stdin> в скалярном контексте возвращает следующую введенную строку. В списочном же контексте эта операция возвращает все строки, оставшиеся до конца файла. Каждая строка при этом возвращается как отдельный элемент списка, например:

$а = <STDIN>; # читать стандартный ввод в списочном контексте

Если пользователь, выполняющий программу, введет три строки и нажмет [Ctrl+D]* (чтобы обозначить конец файла), массив будет состоять из трех элементов. Каждый из них является строкой, заканчивающейся символом новой строки, и соответствует введенной пользователем строке.

* В некоторых системах конец файла обозначается нажатием клавиш [Ctrl+Z], а в других эта комбинация служит для приостановки выполняемого процесса.



<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 мы посмотрим, как осуществить проверку в данной ситуации и выполнить специальные действия, если данных для чтения больше нет.


Сводка операций, проводимых над процессами


Операции, служащие для запуска процессов, перечислены в таблице 14.1. Таблица 14.1. Операции запуска процессов

Операция Стандартний ввод Стандартний

вывод

Стандартний вывод ошибок Нужно ли ожидать завершення процесса
System() Наследуется Наследуется Наследуется Да
от программы от программы от программы
Строка в обратных Наследуется от программы Принимается как строковое Наследуется от программы Да
кавычках значение
Запуск Соединен с Наследуется Наследуется Только во
процесса как деск дескриптором файла от программы от программы время вы-полнения
риптора файла для close ()
вывода при
помощи
команди
open()
Запуск Наследуется Соединен с Наследуется Только во
процесса как деск от программы дескриптором файла от программы время вы-полнения
риптора файла для close ()
ввода при
помощи
команди
open()
fork, Выбирается Выбирается Выбирается Выбирается
ехес, пользователем пользователем пользователем пользователем
wait,
waitpid

Самый простой способ создать процесе — использовать для зтого функ-цию system. На стандартный ввод, вывод и вывод ошибок зто не влияет (они наследуются от Perl-процесса). Строка в обратных кавычках создает процесе и передает данные со стандартного вывода зтого процесса как строковое значение для Perl-программы. Стандартный ввод и стандартный вывод ошибок не изменяются. Оба зти метода требуют завершення процесса до выполнения другого кода.


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

Самый гибкий способ запустить процесе — заставить программу вызвать функции fork, ехес и wait или waitpid, которые полностью соответст-вуют своим UNIX-тезкам. С помощью зтих функции вы можете запустить какой-либо процесе синхронно или асинхронне, а также конфигурировать по своєму усмотрению стандартный ввод, стандартный вывод и стандартный вывод ошибок*.


Телеконференции 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

| Приложение А | Приложение Б | Приложение В | Приложение Г |


Транслитерация


Если вам необходимо взять строку и заменить все зкземпляры какого-нибудь символа другим символом или удалить их, зто можно сделать, как вы уже знаєте, с помощью тщательно подобранных команд s///. Предположим, однако, вам нужно превратить все буквы а в буквы Ь, а все буквы b — в буквы а. Зто нельзя сделать посредством двух команд s///, потому что вторая команда отменит все изменения, сделанные первой.

Такое преобразование данных очень просто выполняется в shell с помощью стандартной команды tr(l):

tr ab ba <indata >outdata

(Если вы ничего не знаєте о командо tr, загляните на man-страницу tr(l);

зто полезный инструмент.) В Perl тоже применяется операция tr, которая работает в основном так же:

tr/ab/ba;

Операция tr принимает два аргумента: старая_строка и новая_строка. Они используются так же, как аргументы команды s///; другими словами, имеется некий разделитель, который стоит сразу же за ключевым словом tr и разделяет и завершает аргументы (в данном случае зто косая черта, но в зтой роли могут виступать почти все символы).

Аргументи операции tr похожи на аргументи команды tr(l). Операция tr изменяет содержимое переменной $_ (совсем как si/I}, отыскивая в ней символы старой строки и заменяя найденные символы соответствующи-ми символами новой строки. Вот несколько примеров:

$_ = "fred and barney";

tr/fb/bf; # $ теперь содержит "bred and farney" tr/abcde/ABCDE/; t $_ теперь содержит "BrED AnD fArnEy" tr/a-z/A-Z/; # $_ теперь содержит "BRED AND FARNEY"

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

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

$_ = "fred and barney";

tr/a-z/x/; # $_ теперь содержит "хххх xxx xxxxxx"

Чтобы такое не происходило, поставьте в конце операции tr/// букву d, которая означает delete ("удалить"). В данном случае последний символ не повторяется. Все символы старой строки, для которых нет соответствую-щих символов в новой строке, просто удаляются:


$ = "fred and barney";

tr/a-z/ABCDE/d; # $_ теперь содержит "ED AD BAE"

Обратите внимание на то, что все буквы, стоящие после буквы е, исчезают, потому что в новом списке соответствующей буквы нет, и на то, что на пробелы зто не влияет, потому что их нет в старом списке. По принципу работы зто зквивалентно команде tr c опцией -d.

Если новый список пуст и опция d не используется, то новый список будет совпадать со старым. Зто может показаться глупым — зачем заменять І на І и 2 на 2? — но на самом деле в зтом єсть довольно глубокий смысл. Операция tr/// возвращает количество символов, совпавших со старой строкой, и путем замены символов на самих себя вы можете получить число таких символов, содержащихся в новой строке*. Например:

* Зто справедливо только для одиночних символов. Для подсчета строк в операции сопоставления с образцом используйте флаг /д:

while (/образец/д) { $count++;

>

$_ = "fred and barney";

$count = tr/a-z//; # $_ не изменилась, но $count = 13 $count2 = tr/a-z/A-Z/; # $_ переведена в верхний регистр, и $count2 = 13

Если в конце операции добавить букву с (как мы добавляли букву d), символы старой строки будут рассматриваться как исключение из набора всех 256 символов. Каждый символ, указаними в старой строке, удаляется из совокупности всех возможных символов; оставшиеся символы, взятые по порядку от младшего к старшему, образуют новую строку-результат. Напри-мер, подсчитать или изменить в нашей строке все символы, не являющиеся буквами, можно так:

$_ = "fred and barney";

$count == tr/a-z//c; # $_ не изменилась, но $count = 2

tr/a-z/_/c; # $_ теперь содержит "fred_and_barney" (символы-небуквы => _)

tr/a-z//cd; t $_ теперь содержит "fredandbarney" (символы-небуквы

удалены)

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



Последняя опция операции tr/// — s, которая заменяет множество зкземпляров одной преобразованной буквы одним. Например:

$_ = "aaabbbcccdefghi";

tr/defghi/abcddd/s; # $_ теперь содержит "aaabbbcccabcd"

Обратите внимание: буквы def заменены на abc, a ghi (которые без опции s превратились би в ddd) стали одной буквой d. Отметим также, что стоящие друг за другом буквы в первой части строки "не сжимаются", потому что для них не задано преобразование. Вот еще несколько примеров:

$_ = "fred and barney, wilma and betty";

tr/a-z/X/s; ” $_ теперь содержит "X X X, X X X" $_ = "fred and barney, wilma and betty";

tr/a-z/_/cs; # $_ теперь содержит "fred_and_barney_wilma and betty"

В первом из зтих примеров каждое слово (стоящие друг за другом буквы) было заменено одной буквой х. Во втором примере все группы стоящихдруг за другом символов-небукв стали одиночними знаками подчеркивания.

Как и команда s///, операция tr может бьггь проведена над другой строкой, а не только над строкой, хранящейся в переменной $_. Зто достигается с помощью операции =~:

$names = "fred and barney";

$names =~ tr/aeiou/X/; # $names теперь содержит "frXd Xnd bXrnXy"


Удаление файла


Вы уже научились создавать в 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 $_: $!";



Упаковка и распаковка двоичных данных


Данные о паролях и группах удобно использовать в текстовом виде. Информацию в других системных базах данных более естественно представлять иначе. Например, IP-адрес интерфейса обрабатывается внутренними механизмами как четырехбайтовое число. Хотя его часто представляют в текстовом виде (как четыре небольших целых числа, разделенных точками), такое преобразование — пустая трата времени, если зти данные в промежутке между преобразованиями не выводятся на зкран пользователя.

По зтой причино написанные на Perl сетевые программы, ожидающие или возвращающие IP-адрес, используют четырехбайтовую строку, одному символу которой соответстввует один байт в памяти. Хотя конструирование и интерпретация такой байтовой строки — довольно простая задача, решае-мая с помощью функций chr и ord (здесь не представленных), в Perl используется более зффективное решение, которое в равной степени при-менимо и к более сложньш структурам.

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

5buf = packf'CCCC", 140, 186, 65, 25);

Здесь строка формата pack — четыре буквы С. Каждая С соответствует отдельному значенню, взятому из приведенного следом списка (подобно тому, что делает спецификация % в функций sprintf). Формат С (согласно man-страницам Perl, краткому справочнику, книге Programming Perl,

HTML-файлам и даже видеоролику Perl: The Motion Picture) обозначает один байт, вычисляемый из символьного значення без знака (короткого целого). Отрока-результат в переменной $buf представляет собой четырехсимвольную строку, в которой каждый символ задан одним байтом. Зти байты имеют значення 140, 186, 65 и 25 соответственно.

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


$buf = packC'l",0х41424344) ;

генерирует четырехсимвольную строку, состоящую из символов abcd или dcba -- в зависимости от того, какой порядок хранения байтов используется на данной машине: "младший в младшем" или "старший в младшем" (либо что-то совершенно иное, если зта машина "не говорит" на ASCII). Зто обіясняется тем, что мы упаковываем одно значение в четыре символа (для представлення длинного целого отводится четыре байта), а зто одно значение как раз состоит из байтов, представляющих коды ASCII первых четырех букв алфавита. Аналогичньш образом,

$buf = pack("ll", 0х41424344, 0х45464748);

создает восьмибайтовую строку, состоящую из букв abcdefgh или dcbahgfe, опять-таки в зависимости от того, какой порядок хранения байтов используется в данной машине — "младший в младшем" или "старший в младшем".

Полный перечень различных форматов, используемых для упаковки, приведен в справочной документации (perlfunc(l)

или Programming Perl). Мы приведем некоторые из них как примеры, но все, конечно, давать не будем.

Допустим, вам дали восьмибайтовую строку abcdefgh и сказали, что она является представлением хранящихся в памяти (один символ — один байт) двух длинных (четырехбайтовых) значений со знаком. Как ее интерпретиро-вать? Нужно воспользоваться функцией, обратной функции pack,— функ-цией unpack. Она берет строку управления форматом (как правило, иден-тичную той, которую вы указывали в функции pack) и строку данных и возвращает список значений, которые хранятся в соответствующих ячейках памяти. Давайте, например, распакуем такую строку:

($vall,$val2) = unpack("ll","ABCDEFGH");

Зто даст нам в переменной $vall нечто вроде 0х41424344, а может быть, и 0х44434241 (в зависимости от порядка хранения байтов). По сути дела, по возвращаемым значенням мы можем определить, на какой машине работаем — с порядком "младший в младшем" или "старший в младшем".

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



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

$bu£ ” pack("C*", 140, 186, 65, 25);

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

$buf = pack("s2 C*", 3141, 5926, 5, 3, 5, 8, 9, 7, 9, 3, 2);

Здесь мы получаем первые два значення как короткие (й генерируем, вероятно, четыре или восемь символов), а остальные девять — как символы без знака (й генерируем, почти наверняка, девять символов).

Функция unpack co звездочкой в качестве спецификации может форми-ровать список злементов, длина которых заранее не определена. Например, при распаковке с использованием формата с* создается один злемент списка (число) для каждого символа строки. Так, оператор

@values = unpack("С*", "hello, world!\n");

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


Большинство глав завершаются упражнениями, ответы


Большинство глав завершаются упражнениями, ответы к которым даются в приложении А. Для этой главы ответы уже были даны выше.
1. Наберите программы-примеры и заставьте их работать. (Вам понадобится создать списки секретных слов.) Если потребуется помощь — обратитесь к местному Perl-гуру.
|     Назад    
|     Вперед    
|


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


| Содержание | Предисловие | Введение | Ссылки

| Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10

| Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19

| Приложение А | Приложение Б | Приложение В | Приложение Г |


Ответ см. в приложении А.

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

| Приложение А | Приложение Б | Приложение В | Приложение Г |


Ответ см. в приложении А.

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

| Приложение А | Приложение Б | Приложение В | Приложение Г |

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


Ответы к упражнениям приведены в приложении А.
1. Напишите программу, которая вычисляет длину окружности с радиусом 12, 5. Длина окружности равна 2я (около 2 * 3,141592654) радиусам.
2. Модифицируйте программу из предыдущего упражнения так, чтобы она запрашивала и принимала значение радиуса окружности у пользователя.
3. Напишите программу, которая запрашивает и считывает два числа, после чего выводит на экран результат перемножения этих чисел.
4. Напишите программу, которая считывает строку и число и выводит на экран строку столько раз, сколько задано числом, причем каждый раз с новой строки. (Совет: используйте оператор х.)
|     Назад    
|     Вперед    
|


Ответы к упражнениям приведены в приложении А.
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. Напишите программу, которая запрашивает температуру окружающего воздуха и выводит на экран слова "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

| Приложение А | Приложение Б | Приложение В | Приложение Г |


Ответы к упражнениям даны в приложении А.
1. Напишите программу, которая читает строку, а затем выводит эту строку и соответствующее ей значение согласно приведенной ниже таблице:

Ввод Вывод
red green blue apple
leaves ocean

2. Напишите программу, которая читает ряд слов (по одному в строке) до конца файла, а затем выводит на экран сводку о том, сколько раз встретилось каждое слово. (Дополнительная задача: отсортируйте появляющиеся на экране слова по их ASCII-значениям в порядке возрастания последних.)
|     Назад    
|     Вперед    
|


| Содержание | Предисловие | Введение | Ссылки

| Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10

| Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19

| Приложение А | Приложение Б | Приложение В | Приложение Г |


Ответы к упражнениям даны в приложении А.
1. Напишите программу, которая работает аналогично команде cat, но изменяет на обратный порядок следования всех строк всех файлов, указанных в командной строке, или всех строк, поступающих со стандартного ввода, если файлы не указаны. (В некоторых системах есть подобная утилита; она называется tac.)
2. Измените программу из предыдущего упражнения таким образом, чтобы в каждом файле, указанном в командной строке, порядок строк изменялся на обратный индивидуально. (Да-да, вы можете сделать это, пользуясь тем материалом, который до сих пор изучили, даже не возвращаясь к обзору возможностей языка Perl, приведенному в главе 1.)
3. Напишите программу, которая читает список строковых значений (каждое из которых занимает отдельную строку) и выводит эти строки в 20-символьном столбце с выравниванием справа. Например, при вводе строк hello и good-bye они выводятся в 20-символьном столбце с выравниванием справа. (Добейтесь, чтобы ваша программа действительно использовала 20-символьный столбец, а не 21-символьный. Это весьма распространенная ошибка.)
4. Измените программу из предыдущего упражнения таким образом, чтобы пользователь мог выбирать ширину столбца. Например, при вводе строк 20, hello и good-bye получатся те же результаты, что и в предыдущем упражнении, а ввод 30, hello, good-bye обеспечит размещение hello и good-bye в 30-символьном столбце.
|     Назад    
|     Вперед    
|


| Содержание | Предисловие | Введение | Ссылки

| Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10

| Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19

| Приложение А | Приложение Б | Приложение В | Приложение Г |


'Ответы к упражнениям даны в приложении А.
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. Повторите последнее упражнение, но с выдачей имен всех пользователей, зарегистрировавшихся под одинаковыми именами. (Совет: в хеше вместо числа экземпляров сохраните список регистрационных имен, записанных через пробелы. Затем ищите значения, содержащие пробел.)


Ответы к упражнениям даны в приложении А.
1. Напишите подпрограмму, которая будет принимать в качестве аргумента числовое значение от 1 до 9 и возвращать английское название этого числа (т.е. one, two и т.д.). Если значение не принадлежит указанному диапазону, подпрограмма должна возвратить вместо имени исходное число. Проверьте работу подпрограммы, введя какие-нибудь данные. Для вызова этой подпрограммы вам, наверное, придется написать какой-то код. (Совет: эта подпрограмма не должна выполнять никакого ввода-вывода.)
2. Взяв подпрограмму из предыдущего упражнения, напишите программу, которая будет брать два числа и складывать их, выдавая результат в формате Two plus two equals four. (He забудьте начать первое слово с заглавной буквы!)
3. Модернизируйте эту подпрограмму так, чтобы она возвращала названия от negative nine до negative one и zero (т.е. принимала числа из диапазона от -9 до -1 и нуль). Проверьте, как она будет работать в программе из упражнения 2.
* "Обычная переменная" в этом случае — переменная пакета (поэтому $х — это, по сути дела, $main:: х). Переменные, созданные с помощью my (), ни в один пакет не входят.
|     Назад    
|     Вперед    
|


| Содержание | Предисловие | Введение | Ссылки

| Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10

| Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19

| Приложение А | Приложение Б | Приложение В | Приложение Г |


Ответы к упражнениям приведены в приложении А.
1. Модифицируйте упражнение из предыдущей главы так, чтобы операция повторялась до тех пор, пока в качестве одного из значений не будет введено слово end. (Совет: используйте бесконечный цикл, а затем выполните оператор last, если любое из значений равно end.)
2. Перепишите упражнение из главы 4 (суммирование чисел до появления числа 999), используя цикл с выходом из середины. (Совет: используйте "голый" блок с оператором redo в конце, чтобы получить бесконечный цикл, и оператор last в середине, выполняющийся при выполнении определенных условий.)
|     Назад    
|     Вперед    
|


| Содержание | Предисловие | Введение | Ссылки

| Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10

| Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19

| Приложение А | Приложение Б | Приложение В | Приложение Г |


Ответы приведены в приложении А.
1. Напишите программу чтения имени файла из stdin, открытия этого файла и выдачи его содержимого с предварением каждой строки именем файла и двоеточием. Например, если считано имя fred, а файл fred состоял из трех строк, a a a, bbb и с с с, вы должны увидеть fred: а а а, fred: bbb И fred: ссс.
2. Напишите программу, которая приглашает ввести имя входного файла, имя выходного файла, образец поиска и заменяющую строку, после чего копирует входной файл в выходной, заменяя все экземпляры образца этой строкой. Попробуйте выполнить эту программу с несколькими файлами. Можете ли вы заменить существующий файл (не проводите эту операцию над чем-то важным!)? Можете ли вы использовать символы регулярных выражений в искомой строке? Можете ли вы использовать $1 в заменяющей строке?
3. Напишите программу чтения списка имен файлов и выдачи информации о том, какие файлы доступны для чтения, для записи и (или) для выполнения, а какие файлы не существуют. (Каждую проверку можно выполнять для каждого имени файла по мере их чтения или для всей совокупности имен после прочтения их всех. Не забудьте удалять символ новой строки в конце каждого прочитанного имени файла.)
4. Напишите программу чтения списка имен файлов и поиска среди них самого старого. Выведите на экран имя файла и его возраст в днях.
|     Назад    
|     Вперед    
|


| Содержание | Предисловие | Введение | Ссылки

| Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10

| Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19

| Приложение А | Приложение Б | Приложение В | Приложение Г |


Ответы см. в приложении А.
1. Напишите программу, которая открывает файл /etc/passwd по имени и выводит на экран имя пользователя, идентификатор (номер) пользователя и его реальное имя в форматированных столбцах. Используйте функции format И write.
2. Добавьте в предыдущую программу формат начала страницы. (Если ваш файл паролей относительно короткий, то, возможно, придется установить длину страницы где-то в 10 строк, чтобы можно было использовать несколько экземпляров начала страницы.)
3. Добавьте в начало страницы номер страницы, чтобы при их выводе указывалось page I, page 2 и т.д.
|     Назад    
|     Вперед    
|


| Содержание | Предисловие | Введение | Ссылки

| Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10

| Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19

| Приложение А | Приложение Б | Приложение В | Приложение Г |


Ответы см. в приложении А.
1. Напишите программу, которая открывает каталог, заданный ее параметром, а затем выдает список имен файлов этого каталога в алфавитном порядке. (В случае, если переход в каталог не может быть выполнен, программа должна предупредить об этом пользователя.) "
2. Модифицируйте программу так, чтобы она выдавала имена всех файлов, а не только те, имена которых не начинаются с точки. Попробуйте сделать это как с помощью операции развертывания, так и посредством использования дескриптора каталога.
|     Назад    
|     Вперед    
|


| Содержание | Предисловие | Введение | Ссылки

| Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10

| Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19

| Приложение А | Приложение Б | Приложение В | Приложение Г |


Ответы к упражнениям см. в приложении А.
Напишите программу, которая работает как утилита гт, удаляя файлы, имена которых были заданы как аргументи командной строки при вызове программы. (Никакие опции команды ті
вам для зтого не понадобятся.)
Обязательно проверьте зту программу с почти пустым каталогом, чтобы случайно не удалить нужные файлы! Помните, что аргументы командной строки при запуске программы извлекаются из массива @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

| Приложение А | Приложение Б | Приложение В | Приложение Г |


Ответы к упражнениям см. в приложении А.
Напишите программу, которая получает результат команды 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

| Приложение А | Приложение Б | Приложение В | Приложение Г |


Ответы к упражнениям см. в приложении А.
1. Напишите программу, которая читает список имен файлов и разбивает каждое имя на начальний и конечний компоненти. (Все, что стоит в имени файла до последней косой черты — начальный компонент, а все, что за ней — конечний компонент. Если косой нет, то все имя является конечним компонентом.) Попробуйте вьшолнить зту программу с име-нами вроде /fred, bamey, fred/barney. Имеют ли результати смисл?
2. Напишите программу, которая читает список чисел, стоящих в отдельних строках, и сортирует их по числовим значенням, виводя список-результат в столбец с выравниванием справа. (Совет: для вивода столбца с выравниванием справа нужно использовать формат наподобие %20д.)
3. Напишите программу вивода реальних и регистрационннх имен поль-зователей из файла /etc/passwd с сортировкой по фамилиям пользовате-лей. Работоспособно ли ваше решение в случае, если удвух пользователей одинаковне фамилии?
4. Создайте файл, состоящий из предложений, каждое из которих стоит в отдельной строке. Напишите программу, которая переводит первий символ каждого предложения в верхний регистр, а остальную часть предложения — в нижний. (Работает ли зта программа в случае, если первый символ — небуква? Как решить зту задачу, если предложения не стоят в отдельних строках?)
|     Назад    
|     Вперед    
|


| Содержание | Предисловие | Введение | Ссылки

| Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10

| Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19

| Приложение А | Приложение Б | Приложение В | Приложение Г |


Ответы см. в приложении А.
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

| Приложение А | Приложение Б | Приложение В | Приложение Г |

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 Правила вызова из С



Ваша 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-кодами.


Возвращаемые значения


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

Давайте, например, определим такую подпрограмму:

sub sum_of_a and_b ( return $a + $b;

}

Последнее выражение, вычисляемое в теле этой подпрограммы (фактически единственное вычисляемое выражение), — сумма переменных $а и $ь, поэтому эта сумма и будет возвращаемым значением. Вот как все это работает:

$а = 3; $b = 4;

$с = sum_of_a_and_b(); # $с присваивается значение 7 $d = 3 * sum_of_a_and_b(); # $d содержит значение 21

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

sub Ii st_o f_a_and_b {

return($a,$b) ;

}

$a = 5; $b = 6;

@c = list_of_a_and_b(); # @c присваивается значение (5,6)

Последнее вычисленное выражение действительно означает последнее вычисленное выражение, а не последнее выражение, определенное в теле подпрограммы. Например, следующая подпрограмма возвращает $а, если $а > 0; в противном случае она возвращает $Ь:

sub gimme a_or_b ( if ($a > 0) (

print "choosing a ($a)\n";

returns $a;

) else (

print "choosing b ($b)\n";

returns $b;

} }

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



Прежде всего следует сказать, что


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

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

    Тем не менее в качестве прелюдии к более глубокому изучению языка мы включили в книгу серьезную главу о CGI-программировании. В ней также затрагиваются такие темы, как библиотечные модули, ссылки и объектно-ориентированное программирование на Perl. Надеемся, что она пробудит у вас интерес к этим более сложным темам.

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

    Мы считаем, что эту книгу нужно использовать совместно с классическим полным справочником по Perl - Programming Perl, Second Edition, by Larry Wall, Randal L. Schwartz, and Tom Christiansen (издательство O'Reilly & Associates).

    Задуманный первоначально как язык для операционной системы UNIX, Perl сейчас работает практически везде, включая MS-DOS, VMS, OS/2, Plan 9, Macintosh и все известные разновидности Windows. Это один из наиболее переносимых языков программирования, известных на сегодняшний день. За исключением тех нескольких разделов, которые относятся к управлению UNIX-системами, информация из большей части этой книги применима к любой платформе, на которой работает Perl.
|     Назад    
|     Вперед    
|


| Содержание | Предисловие | Введение | Ссылки

| Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10

| Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19

| Приложение А | Приложение Б | Приложение В | Приложение Г |

Ввод из операции "ромб"


Другой способ чтения входной информации состоит в использовании операции "ромб" (О). По принципу работы она похожа на <stdin> — тем, что возвращает в скалярном контексте одну строку (или undef, если все строки прочитаны), а в списочном контексте — все оставшиеся строки. В отличие, однако, от операции <stdin> операция "ромб" получает данные из файла или файлов, заданных в командной строке при вызове Perl-программы. Пусть, например, у вас есть программа kitty:

#!/usr/bin/peri while (<>) ( print $_;

i

и вы вызываете ее так:

kitty filel file2 file3

Операция "ромб" читает сначала все строки первого файла, затем все строки второго и, наконец, все строки третьего файла. Значение undef возвращается только тогда, когда будут прочитаны все строки всех файлов. Как видите, программа kitty немного похожа на UNIX-команду cat — тем, что посылает все строки указанных файлов по очереди на стандартный вывод. Если, как в cat,

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

В ходе своего выполнения операция "ромб" не рассматривает аргументы командной строки непосредственно, а работает с массивом @argv. Это специальный массив, инициализируемый интерпретатором Perl в соответствии с аргументами командной строки. Каждый аргумент заносится в отдельный элемент этого массива. Интерпретировать этот массив можно как угодно*. Можно даже определить этот массив в своей программе и заставить операцию "ромб" работать с этим новым списком, а не с аргументами командной строки, например:

@ARGV = ("aaa","bbb","ccc");

while (о) { # обработать файлы aaa, bbb и ссс print "this line is: $_";

t

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



Ввод из STDIN


Чтение со стандартного ввода (через Perl-дескриптор файла stdin) — несложная задача. Мы уже делали это в операции <stdin>. Выполняя эту операцию в скалярном контексте, мы получаем следующую строку ввода* (а если строк больше нет — то значение undef):

$а = <STDIN>; # прочитать следующую строку

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

@а = <STDIN>;

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

while (defined($line_ = <STDIN>)) (

# здесь обработать $line )

Пока есть непрочитанные строки, в результате выполнения операции <stdin> получается определенное значение, и выполнение цикла продолжается. Если строк для чтения у <stdin> больше нет, эта операция возвращает значение undef и завершает цикл.

* До символа новой строки или того, что вы присвоили переменной $/.

Операция чтения скалярного значения из <stdin> в $_ и использование этого значения в качестве переменной цикла (как в предыдущем примере) выполняется довольно часто, поэтому в Perl предусмотрена для этого случая специальная сокращенная запись. Если в выражении для проверки цикла указан только оператор чтения со стандартного ввода (нечто вроде <...>), то Perl автоматически копирует строку, которая считывается, в переменную $_.

while (<STDIN>) { # как "while(defined($_ = <STDIN>_) "

chomp; # как "chomp($_)" # здесь выполняются другие операции с $_ )

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



Вывод в STDOUT


Для посылки каких-либо данных на стандартный вывод в Perl служат функции print и printf. Давайте посмотрим, как они работают.

Функция print — обычный вывод

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

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

$а = print("hello ", "world", "\n");

— это еще один вариант вывода приветствия hello, world. Возвращаемое значение функции print — "истина" или "ложь", говорящее соответственно об успешном и неудачном выполнении. Она почти всегда выполняется успешно, если только не произошла ошибка ввода-вывода, поэтому в рассматриваемом случае $а обычно бывает равно 1.

Иногда в print нужно вводить круглые скобки, как показано в данном примере, особенно когда первый элемент, который вы хотите вывести, сам начинается с левой скобки:

print (2+3),"hello"; # неверно! Выводит 5, игнорируя "hello" print ((2+3),"hello"); # верно, выводит Shello print 2+3,"hello"; # тоже верно, выводит Shello

* Стандартный дистрибутив Perl содержит модули getopt-подобного синтаксического анализа аргументов командной строки Perl-программы. Информация об этой библиотеке есть в книге Programming Perl и на man-странице perlmodlib(l).

Функция printf — форматированный вывод

Вероятно, вы захотите в большей степени контролировать выводимые данные, чем это позволяет функция print. Возможно, вы привыкли к форматированному выводу С-функции printf. Спешим обрадовать: в Perl есть почти сравнимая операция с таким же именем.

Функция printf принимает список аргументов (заключенный в необязательные круглые скобки, как и в случае использования функции print). Первый аргумент — строка управления форматом, указывающая, как выводить остальные аргументы. Если вы не знакомы со стандартной функцией printf, обратитесь к man-странице printf(3) или perlfunc{\),

если она у вас есть, или к главе 3 книги Programming Perl.

Пример:

printf "%15s %5d %10.2f\n", $s, $n, $r;

Эта функция выводит $s в 15-символьном поле, затем пробел, затем $п как десятичное целое в 5-символьном поле, затем еще один пробел, затем $г как значение с плавающей запятой с двумя десятичными знаками в 10-символьном поле и, наконец, символ новой строки.



Вызов формата


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

Давайте еще раз обратимся к формату адресной этикетки и создадим файл, в котором содержится несколько таких этикеток. Вот фрагмент программы:

format ADDRESSLABEL =

$name

$address

$city, $state, $zip

open(ADDRESSLABEL,">labels-to-print") || die "can't create";

open (ADDRESSES,"addresses") || die "cannot open addresses";

while (<ADDRESSES>) f

chomp; # удалить символ новой строки

($name,$address,$city,$state,$zip) = split (/:/);

# загрузить глобальные переменные

write (ADDRESSLABEL); # send the output }

Здесь мы видим уже знакомое определение формата, но теперь у нас есть и выполняемый код. Сначала мы открываем дескриптор для выходного файла, который называется labels-to-print. Отметим, что имя дескриптора файла (addresslabel) совпадает с именем формата. Это важно. Затем мы открываем дескриптор для файла, содержащего список адресов. Предполагается, что этот список имеет следующий формат:

Stonehenge:4470 SW Hall Suite 107 :Beaverton:OR:97005 Fred Flintstone:3737 Hard Rock Lane:Bedrock:OZ:999bc

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

Цикл while в программе считывает строку из файла адресов, избавляется от символа новой строки, а затем разбивает то, что осталось, на пять переменных. Обратите внимание: имена переменных — те же самые, которые мы использовали, определяя формат. Это тоже важно.

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

Каждое поле в формате заменяется соответствующим значением из следующей строки формата. После обработки двух приведенных в примере записей файл labels-to-print будет содержать следующее:

Stonehenge

4470 SW Hall Suite 107

Beaverton , OR 97005

Fred Flintstone 3737 Hard Rock Lane Bedrock , OZ 999bc



Вызов пользовательской функции


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

say_hello(); # простое выражение

$а = 3 + say_hello() # часть более сложного выражения

for ($х = start_value(); $х < end_value(); $х += increment О) (

} t

вызов трех подпрограмм для определения значений

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

** Если только вы не выполняете программу с ключом -w.

Одна подпрограмма может вызывать другую подпрограмму, которая, в свою очередь, может вызывать третью подпрограмму и т.д., пока вся наличная память не будет заполнена адресами возврата и не полностью вычисленными выражениями. (Ведь настоящего программиста вряд ли удовлетворят какие-то 8 или 32 уровня вложенности подпрограмм.)