Теперь интересы пользователей не ограничиваются электронным почтовым ящиком и поиском какой-либо информации в WWW, они стремятся создавать собственные Web-страницы или даже Web-узлы. Новички организуют простые HTML-страницы, не обращая особого внимания на стиль и дизайн. Но со временем у них появляется желание сделать нечто эффектное и интересное для определенной части Internet-сообщества. И тогда новоявленному Web-мастеру приходится более полно изучить язык разметки гипертекста (HTML) и языки создания клиентских сценариев (VBScript1 и JavaScript)*.

Впоследствии у него могут появиться и новые запросы, — например пообщаться с посетителями своего узла. И в этом случае его уже не удовлетворит, если в тело страницы просто добавится Webmaster. Так, он решит, что неплохо было бы получить достаточно полную информацию о посетителях узла (имена, e-mail, телефоны, факсы и адреса), подсчитать количество посещений, собрать различные мнения и, наконец, создать базу данных, чтобы пользователям сеансов связи предоставить какие-либо определенные услуги. Но для всего этого знания HTML, VBScript1 и Java Script становится явно недостаточно, так как они описывают только технологию взаимодействия сервера и клиента.

Следовательно, нужно изучить языки для создания программ, работающих на сервере. Обычно их разрабатывают на Perl (Practical Extraction and Report Language — практический язык извлечений и отчетов), применяемом также для обработки потоков информации. Изначально предполагалось, что он будет использоваться в ОС Unix, но в дальнейшем Perl стали переносить на другие платформы, и сейчас он существует в самых разных версиях — для Unix, Windows, MS-DOS, OS/2, MacOS, Amiga, Atari ST, VMS, Plan 9 и др.

Для чего нужен Perl?

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

Perl распространяется бесплатно, поэтому исходные тексты языка и многие двоичные файлы для использования вне Unix-архитектуры можно получить на одном из серверов сети CPAN (Comprehensive Perl Archive Network) по адресу http://www.perl.com/CPAN или на узле поддержки разработчиков по адресу http://www.basicnet.sonnet.ru/dounload.

Для создания и тестирования Perl-программ необходимы:

  • любой текстовый редактор, позволяющий сохранять файлы в ASCII-коде (например, встроенный редактор из оболочки FAR Commander);
  • программа конвертации ACSII-файлов в формат Unix-систем (в частности, редактор Castillo TextEditor, который можно свободно загрузить с сервера www.castillobueno.com);
  • интерпретатор Perl для отладки (у автора — Win32-версия Perl, доступная по адресу ftp.perl.com/pub/perl);
  • Web-сервер, поддерживающий работу Perl-программ (для проверки интерфейсных программ был применен Web-сервер OmniHTTPd 2.0 Professional, который можно загрузить с узла компании-разработчика по адресу www.omnicron.ab.ca/httpd);
  • FTP-клиент для загрузки файлов на сервер (больше всего для этого подходит CuteFTP 2.0, позволяющий устанавливать права доступа к файлам; его можно найти по адресу www.cuteftp.com);
  • любой Web-браузер (был использован MS IE 4.0).

Программы на языке Perl с расширениями .cgi или .pl должны находиться в специальном каталоге на Web-сервере, обычно называемом CGI-BIN. Размещая в нем свои программы, пользователи могут создавать там собственные папки для обеспечения иерархичности. Если сервер работает в среде Unix, то необходимо придать ему определенные права для доступа. Для исполняемых Perl-программ следует задать атрибут 755. Если в них существует какой-либо каталог, в котором будут производиться запись и удаление файлов, то предпочтительнее поставить атрибут 777 (полный доступ). Когда сервер работает под управлением операционной системы Windows 9.x/NT, то не следует преобразовывать созданные файлы в формат систем Unix. А операционные системы семейства Windows не позволяют выставлять атрибуты доступа подобно Unix-системам — в них используется другая методика.

Структура Perl-программ

Perl-программы очень похожи на написанные на Cи/Cи++, возможно, потому, что сам язык был написан на Cи. Все Perl-программы состоят из операторов, имеющихся в файле и рассматриваемых в совокупности как одна большая программа, подлежащая выполнению. Но понятия «основной» (main) программы, как в языке Си, здесь нет. Комментарием в Perl является все, что следует за «решеткой» (#), вплоть до конца строки. Интерпретатор языка перед выполнением разбирает программу и компилирует в свой внутренний формат. Поэтому после ее запуска невозможно получить сообщение о синтаксической ошибке — это происходит только в процессе отладки программы в командной строке. В результате обеспечивается быстрое выполнение операций языка Perl после запуска.

Для написания программы можно использовать любой текстовый редактор. Так, в Windows это Notepad (Блокнот) или WordPad, в OS/2 — e или epm, в Unix — vi или emacs. Обычно лучшему пониманию языка способствует разбор небольшой программы (см. листинг 1).

Листинг 1. Пример программы на Perl

#!/usr/local/bin/perl
@passwords = qw (inet basic net);
print ?Enter the login: ?;
$login = ;
chomp ($login);
if ($login eq ?Root?) {
	print ?Hello, Administrator!
 Glad to see you again!
 ?;
} else {
	print ?Enter password: ?;
	$pass = ;
	chomp ($pass);
	$i = 0;
	$flag = ?no?;
	while ($flag eq ?no?) {
		if ($passwords[$i] eq $pass) {
			$flag = ?yes?;
		} elseif ($i <2) {
			$i = $i + 1;
		} else {
print ?Incorrect password for $login,
 try again.
?;
			print ?Enter
 password: ?;
			$pass = ;
			chomp ($pass);
			$i = 0;
		}
	}
}

Интересной особенностью Perl является то, что программист не объявляет типы применяемых переменных. В первой строке описывается физический путь к выполняемому модулю интерпретатора (например, PERL.EXE), который должен начинаться со знака комментария (#):

#!/usr/local/bin/perl

В рассматриваемом случае массив @passwords включает три элемента: inet, basic, net. Команда qw(), заключающая их в скобки, освобождает от ввода кавычек, необходимого при использовании общепринятой конструкции вида

@passwords = (?inet?, ?basic?, ?net?);
Оператор print служит для вывода на экран символьной информации
print ?Enter the login: ?;

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

print ?Enter the login: ?;
$login = ;

Следовательно, сначала располагается оператор вывода информации (print), а затем оператор ввода строки с терминала, выполняющегося в Perl с помощью считывающей одну строку данных конструкции . Переменная $login содержит и завершающий символ строки, например, Root будет введено как Root . Чтобы убрать лишний символ, требуется функция chomp, которая в качестве своего единственного параметра принимает скалярную переменную и удаляет из нее завершающий символ перехода на новую строку, если этот символ там присутствует:

chomp ($login);
Далее используется конструкция if-then-else:
if ($login eq ?Root?) {
	print ?Hello, Administrator!
 Glad to see you again!
?;
} else { 
... 
}

Наличие значения переменной $pass среди элементов массива @passwords проверяет $passwords[$i] eq $pass.

Следующая ниже операция сложения
$i = $i + 1;
увеличивает текущее значение счетчика на одну позицию. В строке
	
print ?Incorrect password for $login,
 try again.
?; 

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

print ?Incorrect password for?; login$ ;
 ? try again.?

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

Все числа и строки — это скалярные данные, или просто скаляры. Их названия начинаются со знака доллара (?$?). Perl различает регистр символов, так, $Name и $name — две разные переменные. Числа, в отличие от строк, не нужно заключать в кавычки, например,

$a = 2;
$b = 6;
$c = $a . $b;	# ?.? — операция
 конкатенации двух строк
$d = $c / 2;
print $d;	# — результат равен 13

Аналогичный пример для строковых значений выглядит иначе (см. листинг 2).

Листинг 2
#!/usr/local/bin/perl -w
                  # режим отображения
                          
                # предупреждений 
                            
              # о возможности ошибок
$who = ?Michael Yevdokimov?;
$where = ?Moscow?;
$what = ?in MSATU?;
print ?My name is $who,
?; 
              # представимся
print ?I live in $where,
?,
      ?I study $what there.
?;
           # где учимся
print ?
Signed: 	$who,
		$where.
?;

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

Массив — это список скаляров. Название переменных такого типа начинается с символа ?@?. Каждый элемент массива — это отдельная скалярная переменная, которой можно присваивать значение и затем использовать ее независимо от других. Однако можно присвоить значение и всем элементам массива сразу, например

@passwords = qw(inet basic net);

Проведя такую операцию, затем легко будет обращаться к каждому из скаляров с помощью индексных ссылок. В рассматриваемом примере $passwords[0] имеет значение inet, $passwords[1] — basic, а $passwords[2] — net. В качестве индекса может быть принято выражение, поэтому если присвоить $i значение 1 ($i = 1), то элементом массива $passwords[$i] будет basic. Поскольку каждый элемент массива — скаляр, при адресации ставится знак доллара, а не ?@?. В отличие от других языков программирования, в массиве на Perl можно объединять скаляры разных типов данных. Если записать

@items = (20, ?10.00?, ?диск?);
print ?Купи мне $items[0] $items[2]ет за
 $$items[1].
?;
то в результате получится текст «Купи мне 20
 дискет за $10.00.»

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

@A = (1, 2, 3);
@B = (4, 5, 6);
@C = (7, 8, 9);
@D = (@A, @B, @C);

Результирующий массив D будет содержать числовые значения от 1 до 9.

Большинство встроенных функций в Perl используют массивы как аргументы, например sort и join. Первая возвращает массив, но уже в отсортированном виде. Результатом операции

print sort (?Beta?,?Gamma?,?Alpha?);

будет последовательность AlphaBetaGamma. Функция join имеет два входных параметра — строку и массив строк. Она возвращает строку, которая состоит из всех элементов массива, разделенных значением входной строки, т. е.

print join (?:?,?Name?,?Address?,?Phone?);

и выдает на печать Name : Address : Phone.

Может возникнуть вопрос, как добавить к уже существующему массиву какой-нибудь элемент, не создавая при этом дополнительный массив. Так, массив @letters содержит элементы Beta, Gamma и Alpha. Если в него нужно добавить значение Tetta, то следует использовать возможности функции push (см. листинг 3).

Листинг 3
@b = (?Beta?, ?Gamma?, ?Alpha?);
push @b, ?Tetta?; # добавим в массив @b
 новый элемент
@w = sort @b; 	# отсортируем массив @b
 по алфавиту
$c=0; 		# инициализируем переменную $c
foreach (@w) {	
	print ?$w[$c]
?; # выведем
 отсортированные значения
	$c++;
}

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

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

%fruit=(?Green?,?Apple?,?Orange?,?Orange?,
?Yellow?,?Banana?);
print $fruit{?Yellow?};

В результате из-за структуры АМ «ключ, значение», получается «Banana». Ключом также является «Green», которому будет соответствовать элемент массива «Apple». Для лучшего понимания использования АМ следует сопоставить ключи с ID в таблицах реляционных баз данных, которые представляют собой практически одно и то же. Рассмотрим пример из листинга 4.

Листинг 4
%Folk  =  (?BG?, ?Bill Gates?,
           ?MY?, ?Michael Yevdokimov?,
           ?BC?, ?Bill Clinton?);

%State =  (?BG?, ?California?,
           ?MY?, ?Moscow?,
           ?BC?, ?Washington? );

%Job   =  (?BG?, ?work in Microsoft?,
           ?MY?, ?write this article?,
           ?BC?, ?work as the President
 of USA?);

foreach $person (?MY?, ?BG?, ?BC?) {
        print ?My name is $Folk{$person},
?,
              ?I live in $State{$person},
?,
              ?I $Job{$person} there.

?;
}

Содержимое массивов можно представить и в другой форме, например

%Job   =  (?BG? => ?work in Microsoft?,
           ?MY? => ?write this article?,
           ?BC? => ?work as the President
 of USA?);

Индексы и элементы массива можно заключать как в апострофы, так и в кавычки. Чтобы перебрать все значения АМ, нужно использовать оператор foreach. Он предназначен для организации циклов, как и некоторые другие, в частности while. Можно обращаться к ключам и значениям с помощью операторов keys и values.

Специальный ассоциативный массив %ENV хранит содержимое всех переменных, индексированных по имени. Так, $ENV{?PATH?} возвращает текущее значение пути поиска. Существует также функция each, приводящая список, который состоит из двух элементов — ключа и значения. При каждом следующем вызове она возвращает новую пару, к примеру

while (($key,$value) = each %ENV) {
    print ?$key = $value
?;
}

Работа с файлами и каталогами

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

Дескриптор представляет собой особый вид символьных переменных (literal string). Дескрипторы файлов, так же как и метки, применяются без специального префиксного символа, поэтому их можно спутать с существующими или зарезервированными словами (для подпрограмм, команд и пр.). При программировании названия дескрипторов рекомендуется писать только прописными буквами. Во-первых, они легко различимы среди остального текста, во-вторых, благодаря этому программа будет правильно выполняться. Дескриптор обычно представляет собой «название» файла, на который ссылается пользователь. Как и при программировании на Basic, Паскале или Cи/Cи++, дескрипторы в Perl подобны переменным, присутствующим в синтаксисе операций открытия, закрытия, считывания или записи в файл. Подобно другим языкам, Perl также использует дескрипторы в операциях манипулирования содержимым файлов. Однако есть и другие варианты их применения.

Существует три разных способа открытия файла для проведения чтения (read), дополнения (append) и записи (write).

Режим чтения (Read) — самый простой. Синтаксис операции open следующий:

open (HANDLE,?filename.txt?);

Оператор open используется для открытия файла. В круглых скобках заключен дескриптор файла HANDLE. В дальнейшем при выполнении операций над файлом filename.txt и его содержимым на него будут приведены ссылки в программе. В кавычках стоит имя файла.

Для считывания информации из файла выполняется так называемая операция ромба, обозначаемая символами (<>):

open (HANDLE,?filename.txt?);
while () {
# Этот цикл будет считывать информацию
 из файла построчно
}

Режим записи (Write) имеет следующий вид:

open (HANDLE,?>filename.txt?);

Отличие синтаксиса операций записи от синтаксиса чтения заключается лишь в том, что перед именем файла стоит символ «больше чем» (>). Этот знак сообщает, что следует создать указанный в кавычках файл и записать или обновить (если он уже существует) его содержимое. Чтобы записать в него информацию, нужно обратиться к помощи оператора print:

open (HANDLE,?>filename.txt?);
print HANDLE ?Записать этот текст в файл...?;

Режим добавления (Append) синтаксически выглядит так:

open (HANDLE,?>>filename.txt?);
print HANDLE ?Дописать этот текст в файл...?;

Он схож с режимом записи. Разница лишь в том, что при его описании ставится двойной знак «больше чем» (>>). Если указанный файл не существует, то он будет создан, в противном случае введенная информация будет добавлена в конец. Когда выполняется операция дополнения, данные можно не только записывать, но и считывать из файла.

В случае успешного выполнения все формы функции open возвращают значение true, а в случае неудачи — false. Например, если при попытке открыть файл для чтения выдается значение false, то это означает, что файла нет или доступ к нему запрещен. А когда при открытии файла для ввода информации это значение возвращается, то можно сделать вывод, что либо файл защищен от записи, либо невозможна запись в каталог или доступ к нему. Если затем программа завершит свою работу или файл заново откроется, то не нужно закрывать его после окончания работы с дескриптором — операция открытия файла закрывает ранее задействованный дескриптор. Тем не менее лучше все же закрыть файл с помощью операции close. Подобная структура является «хорошим тоном» при программировании:

close (HANDLE);

Отладка. Всякий раз при открытии файла разумно использовать вместе с оператором open оператор die. Бывает, что файл по какой-то причине нельзя правильно открыть. Программа вроде бы выполнилась, как требовалось, а в файл записалось вовсе не то, что ожидалось. В подобном случае оператор die прерывает выполнение программы и выдает сообщение об ошибке при открытии файла.

В синтаксисе совмещения open и die используется «логическое ИЛИ» (||):

open (HANDLE,?>>filename.txt?) || die
 ?Ошибка добавления в файл filename.txt $!
?;

Функция die, название которой можно перевести с английского как «откройся или умри», прерывает выполнение программы. Выдается сообщение об ошибке, а также информация о том, что ее вызвало. Perl сохраняет сведения о последней системной ошибке в специальной переменной $!. Если после функции die вставить $!, то от ОС будут получены дополнительные данные, которые помогут отладить программу.

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

$filename = ?filename.txt?;
if (-e $filename) {
	print ?Файл $filename уже существует
?;
} else {
	print ?Файл $filename не найден
?;
}

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

if (-e ?filename.001? && -e ?filename.002?) {

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

$filename = ;
chomp ($filename);# убрать символ новой строки
if (-r $filename && -w $filename) {
	# файл существует, мы можем читать
 из него 
# и записывать в него 
. . .
}

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

@files = <*.txt>;
foreach (@files) {
	print ?$_ is readable
? if -r;
}

При большинстве подобных проверок, а их около 20, возвращается значение true или false.

Отличия от Win32. При работе на Perl под управлением Windows существуют некоторые нюансы, о которых следует знать. Во-первых, нужно указывать полный путь к файлу (вместе с именем диска), над которым будут выполняться какие-либо действия, например

open (HANDLE,?c:/scripts/newfile.txt?) || die
 ?Error opening c:/scripts/newfile.txt $!
?;
...
close (HANDLE);

Во-вторых, блокировка файла происходит иначе, чем в Unix-системах. При использовании Windows 9.x эта операция вообще не поддерживается, а в Windows NT выполняется весьма своеобразно — перед выполнением команд копирования или изменения имени файла нужно удостовериться, что вы уже закрыли его. Иначе они просто не выполнятся.

ОБ АВТОРЕ

Михаил Евдокимов — программист, координатор проекта Developers Support Site; е-mail: flanker@sonnet.ru; http://www.basicnet.sonnet.ru

ДескрипторОписание
STDINСтандартный ввод (по умолчанию с клавиатуры)
STDOUTСтандартный вывод (обычно на консоль; во многих Web-приложениях в браузер)
STDERRУстройство, в которое выводится сообщение об ошибке (обычно на консоль, а на Web-сервере это зачастую лог-файл ошибок сервера)