Изучаем Perl

         

Изучаем Perl


В этой главе:

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

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

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

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

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

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

Упражнения



Управление процессами"


1. Вот один из способов решения этой задачи:

if ('date' =~ /"S/) (

print "Go play!\n";

} else (

print "Get to work!\n";

}

Оказывается, команда date выводит букву s в качестве первого символа только по выходным (Sat или sun), что делает задачу тривиальной. Мы вызываем date, затем с помощью регулярного выражения смотрим, является ли первый символ буквой s. На основании результата мы выводим одно сообщение или другое.

2. Вот один из способов решения этой задачи:

open(PW,"/etc/passwd") ;



while (<PW>) {

chomp;

($user,$gcos) = (split /:/)[0,4];

($real) = split (/,/, $gcos); '

$real($user) = $real;

)• close(PW);

open(WHO,"who I") || die "cannot open who pipe";

while (<WHO>) (

($login, $rest) =/" (\S+) \s+(.*)/;

$login = $real($login) if $real($login);

printf "%-30s ts\n",$login,$rest;

}

В первом цикле создается хеш %real, ключи которого — регистрационные имена, а значения — соответствующие реальные имена. Этот хеш используется в следующем цикле для замены регистрационного имени на реальное.

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

Операции открытия дескриптора файла и начала цикла можно заменить строкой

foreach $_ ('who') (

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

3. Вот один из способов решения этой задачи:

open(PW,"/etc/passwd");


while (<PW>) (
chomp;
($user,$gcos) = (split /:/)[0,4];
($real) = split(/,/, $gcos);
$real($user} •= $real;
} close(PW) ;
open(LPR,"1Ipr") I I die "cannot open LPR pipe";
open (WHO,"who]") || die "cannot open who pipe";
while (<WHO>) {
# или заменить предыдущие две строки на foreach $_ ('who') (
($login, $rest) = /л (\S+) \s+(.*)/;
$login = $real($loginl if $real($login} ;
printf LPR "%-30s %s\n",$login,$rest;
}
Разница между этой программой и программой из предыдущего упражнения состоит в том, что мы добавили дескриптор файла lpr, открытый для процесса Ipr, и модифицировали оператор printf так, чтобы он посылал данные не в stdout, а в этот дескриптор.
4. Вот один из способов решения этой задачи:
sub mkdir f
'system "/bin/mkdir", @_;
}
Здесь команда mkdir
получает аргументы прямо из аргументов подпрограммы. Однако возвращаемое значение должно подвергнуться операции логического отрицания, потому что ненулевой код выхода из system должен быть преобразован в значение "ложь" для вызывающей Perl-программы.
5. Вот один из способов решения этой задачи:
sub mkdir (
my($dir, $mode) = 8_;
('system "/bin/mkdir", $dir) && chmod($mode, $dir) ;
1
Сначала мы описываем локальные аргументы этой подпрограммы — $dir и $ mode. Затем мы вызываем mkdir для каталога с именем $dir. В случае успеха операция chmod присваивает этому каталогу соответствующие права доступа.

Изучаем Perl


В этой главе:

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

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

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

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

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

Упражнения



Другие операции преобразования данных "


1. Вот один из способов решения этой задачи:

while (о) { chomp;

$slash = rindex ($_,"/");

if ($slash > -1) (

$head = substr($_,0,$slash);

$tail = substr($_,$slash+l);

} else (

($head,$tail) = ("", $_) ;

) print "head = '$head', tail = '$tail'\n";

>

Каждая строка, прочитанная операцией "ромб", сначала пропускается через операцию chomp, которая удаляет символ новой строки. Затем с помощью rindex () мы ищем в этой строке крайнюю справа косую черту. Следующие две строки разбивают строку на части, используя substr (). Если косой черты нет, то результат rindex равен -1, и этот вариант мы не рассматриваем. Последняя строка цикла выводит результат.

2. Вот один из способов решения данной задачи:

chomp(@nums = <STDIM>); # обратите внимание на особый случай

# использования chomp @nuros = sort ( $а <=>

$b } @nums;

foreach (@nums) (

printf "%30g\n", $_;

}

В первой строке в массив @nums вводятся все числа. Во второй строке этот массив сортируется в числовом порядке, для чего используется встроенный оператор. Цикл foreach обеспечивает вывод результатов.

3. Вот один из способов решения этой задачи:

open(PW,"/etc/passwd") || die "How did you get logged in?";

while “PW” (

chomp;

($user, $gcos) = (split /:/)[0,4];

($real) = split!/,/, $gcos) ;

$real($user} = $real;

($last) = (split /\s+/, $real)[-l];

$last{$user} = "\L$last";

} close(PW) ;

for (sort by_last keys %last) (

printf "%30s %8s\n", $real($_}, $_;

>

sub by_last ( ($last($a} cmp $last($b}) || ($a cmp $b). }

В первом цикле создается хеш %last, ключи которого — регистрационные имена, а соответствующие им значения — фамилии пользователей, и хеш %геа1, содержащий полные реальные имена пользователей. Все символы переводятся в нижний регистр, чтобы, к примеру, FLINT-STONE, Flintstone и flintstone стояли рядом друг с другом.

Во втором цикле выводится %геа1, упорядоченный по значениям %iast. Это делается с использованием определения сортировки, предоставленного подпрограммой by_last.

4. Вот один из способов решения этой задачи:

while (<>) (

substr($_,0,I) =~ tr/a-z/A-Z/;

substr($_,!) — tr/A-Z/a-z/;

print;

1

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

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

while (О) {

print "\u\L$_";

}

Если вы самостоятельно нашли это решение, поставьте себе дополнительно пять баллов.



Изучаем Perl


В этой главе:

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

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

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

Упражнение



Доступ к системным базам данныхff


1. Вот один из способов решения этой задачи:

while (@pw == getpwent) {

($user, $gid, $gcos) - @pw(0,3,6);

($real) = split /,/, $gcos;

$real($user) = $real;

$members($gid} .= " $user";

($last) = (split /\s+/, $real)(-l);

51ast($user) " "\L$last";

)

while (@gr - getgrent) (

($gnarae, $gid, $meinbers) = @gr[ 0,2,3);

$rnembers( $gid) .=“ " $merabers";

$gname($gid) - $gname;

)

for $gid (sort by_gname keys %gname) (

tall = ();

for (split (/\s+/, $members($gidl)) ( $all{$_)++ if length $_;

1

Omembers = () ;

foreach (sort by_last keys %all) (

push(@members, "$real($_} ($_)");

}

$meinberlist = join(", ", @members);

write;

)

sub by_gname ( $gname($al cmp $gname($bl; ) sub by_last ( ($last(a) cmp $last($b)) || ($a cmp $b); )

format STDOUT = @<<<< @<<<< A<<<<<<<<<<<<<<<<<<<

$gname($gid), "($gid)", $memberlist

^<<<<<<<<<<<<<<<<<<<

$memberlist

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



Изучаем Perl


В этой главе:

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

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

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

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

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

Упражнения



Работа с пользовательскими базами данных"


1. Вот один из способов решения этой задачи:

dbmopen(%ALIAS, "/etc/aliases", undef) II

die "No aliases!: $!";

while (($key,$value) - each(tALIAS)) (

chop($key,$value) ;

print "$key $value\n";

1

Первая строка открывает DBM псевдонимов. (В вашей системе DBM псевдонимов может находиться в каталоге /usr/lib/aliases — попробуйте этот вариант, если наш не сработает.) В цикле while осуществляется проход по DBM-массиву. Первая строка цикла служит для удаления символа NUL, которым завершались ключ и значение. Последняя строка цикла обеспечивает вывод результата.

2. Вот один из способов решения этой задачи:

# program 1:

dbmopen(%WORDS,"words",0644) ;

while (О) {

foreach $word (split(/\W+/)) ( $WORDS($word)++;

> } dbmclose(%WORDS) ;

Первая программа (записывающая) открывает DBM words в текущем каталоге, создавая файлы words, dir и words.pag.

Цикл while получает каждую строку, используя операцию "ромб". Эта строка разбивается с помощью операции split и разделителя /\w+/, который обозначает нетекстовые символы. Затем производится подсчет всех слов, имеющихся в DBM-массиве, причем для осуществления прохода в цикле по всем словам используется оператор foreach.

# program 2:

dbmopen(%WORDS,"words",undef) ;

foreach $word (sort { SWORDS)$b} <=> SWORDS($a[ }

keys %WORDS) ( print "Sword SWORDS(Sword)\n";

1 dbmclose(%WORDS) ;

Вторая программа также открывает DBM words в текущем каталоге. Сложная на первый взгляд строка foreach делает почти всю "грязную" работу. При каждом выполнении цикла переменной $word в качестве значения будет присваиваться следующий элемент списка. Этот список состоит из ключей хеша %words, рассортированных по их значениям (т.е. количеству повторений) в убывающем порядке. Для каждого элемента списка мы выводим слово и количество его повторений.



Изучаем Perl


В этой главе:

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

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

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

Упражнение



Преобразование других программ в Perl-программы "


1. Вот один из способов решения этой задачи:

for (;;) (

($user,$home) = (getpwent)[0,7];

last unless $user;

next unless open(N,"$home/.newsrc");

next unless -M N < 30; ## added value :-) while (<N>) f

if (/^comp\ . lang\ .perl\ .announce: /) { print "$user is a good person, ", "and reads comp.lang.peri.announce!\n");

last;

} } }

Самый внешний цикл — это цикл for, который выполняется "вечно";

выход из этого цикла осуществляется посредством операции last, стоящей внутри него. При каждом выполнении цикла операция getpwent выбирает новое значение переменной $user (имя пользователя) и переменной $home (его начальный каталог).

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

В цикле while осуществляется чтение по одной строке из файла .newsrc. Если строка начинается с comp.lang.perl.announce:,

то оператор print сообщает об этом, и осуществляется преждевременный выход из цикла

while.



Изучаем Perl


В этой главе:

Модуль CGI.pm

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

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

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

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

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

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

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

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

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

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

Упражнения



CG1-программирование"


1. Вот один из способов решения этой задачи:

use strict;

use CGI qw (:standard);

print header(), start_html("Add Me"It-print hi("Add Me") ;

if (paramO) {

my $nl = param('fieldl');

my $n2 = param('field2');

my $n3 = $n2 + $nl;

print p("$nl + $n2 = <strong>$n3</strong>\n") ;

} else (

print hr(), start_form();

print pC'First Number:", textfield("fieldl"));

print p("Second Number:", textfield("field2"));

print p(submit("add"), reset ("clear"));

print end_form(), hr () ;

} print end_html();

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

2. Вот один из способов решения этой задачи:

use strict;

use CGI qw(:standard);

print header(), start_html("Browser Detective");

print hi("Browser Detective"), hr();

my $browser = $ENV('HTTP_USER_AGENT'};

$_ '" $browser;

BROWSER:(

if (/msie/i) (

msie($_) ;

) elsif (/mozilla/i) (

netscape($_) ;

) elsif (/lynx/i) (

lynx($_);

1 else (

default($_) ;

> >

print end_html() ;

sub msie)

print pC'Internet Explorer: @_. Good Choice\n") ;

}

sub netscape (

print pC'Netscape: @_. Good Choice\n") ;

t

sub lynx {

print p("Lynx: @_. Shudder...");

(

sub default (

print pC'What the heck is a @_?");

}

Главный момент здесь — проверка переменной среды HTTP_USER_ AGENT на предмет наличия одного из значений, определяющих известный тип броузера (MS Internet Explorer, Netscape Navigator, Lynx). Такая операция проводится не на всех серверах, но на большинстве из них. Это хороший способ генерировать содержимое возвращаемого документа, ориентированное на возможности конкретного броузера. Обратите внимание: чтобы посмотреть, какой именно броузер применяется пользователем, мы выполняем только тривиальное строковое сопоставление (без учета регистра).

|     Назад    

|     Вперед    

|

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

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

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

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



Хеш-функции


В этом разделе перечислены некоторые функции, предназначенные для обработки хешей.

Функция keys

Функция keys( %имя_хеша) выдает список всех текущих ключей, имеющихся в хеше %имя_хеша. Другими словами, применение этой функции эквивалентно возвращению всех элементов списка с нечетными номерами (первый, третий, пятый и т.д.) путем развертывания хеша %имя_хеша в списочном контексте, причем функция keys возвращает их именно в этом порядке. Если элементы в хеше отсутствуют, функция keys возвращает пустой список.

Применим эту функцию к хешу из предыдущих примеров:

$fred("aaa"} = "bbb";

$fred{234.5) = 456.7;

Olist = keys(%fred); # @list получает значение ("ааа",234.5) # или (234.5,"ааа")

Как и во всех остальных встроенных функциях, круглые скобки не обязательны: функция keys %fred

полностью идентична keys (%fred).

foreach $key (keys (%fred)) ( # однократно для каждого значения хеша %fred

print "at $key we have $fred($key}\n"; # показать ключ и значение }

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

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

if (keys(%xeni)) { # если keys() не равно 0:

...; # массив не пустой )

# ... или ...

while (keys(%xem) < 10) {

. . .; # продолжать цикл, пока меньше 10 элементов }

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

if (%хеш) ( # если "истина", в нем что-то есть

# что-то сделать )

Функция values

Функция values (%имя_массива) возвращает список всех текущих значений указанного массива в том же порядке, в каком функция keys ( %имя_массива}

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

%lastname =0; # сделать %lastname пустым $lastname("fred"} = "flintstone";


$lastname("barney"} = "rubble";

Olastnames = values(tiastname); # получить значения



Массив

@lastnames будет содержать либо значения ("flintstone", "rubble"), либо ("rubble", "flintstone").



Функция each



Для выполнения цикла над всем хешем (т.е. для проверки каждого его элемента) можно использовать функцию keys и получать значения по возвращаемым ею ключам. Действительно, этот метод широко используется, но есть и более эффективный способ — функция each ( %имя_хеша),

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

* Можно, в принципе, с помощью среза, но здесь о срезах мы не говорим.

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

while ( ($first,$last) = each(%lastname)) {

print "The last name of $first is $last\n";

}

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



Функция delete



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

%fred = ("aaa","bbb",234.5,34.56); # добавить в %fred два элемента delete $fred("aaa"}; # теперь в хеше %fred только одна пара ключ-значение


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


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

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

** Модули типа IxHash и DB_fiIe обеспечивают некоторую степень упорядочения, но ценой существенного снижения производительности.

соответствующую часть имен скалярных переменных и массивов. Кроме того, точно так же, как нет никакой связи между $fred и @fred, хеш-переменная %fred не имеет ничего общего с названными объектами.

Чаще всего хеш создается и используется путем обращения к его элементам, а не ко всему хешу. Каждый элемент хеша — отдельная скалярная переменная, доступная через индекс, представляющий собой строковое значение и называемый ключом. Так, обращение к элементам хеша %fred производится путем указания $fred{ $ключ},

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

Как и в случае с массивами, новые элементы хеша создаются путем присваивания значения:

$fred{"ааа"} == "bbb"; # создает ключ "ааа", значение "bbb" $fred(234.5} = 456.7; # создает ключ "234.5", значение 456.7

С помощью этих операторов в хеше создаются два элемента. При последующем обращении к элементам (по указанным ключам) возвращаются ранее записанные значения:

print $fred("ааа"); # выводит на экран "bbb" $fred{234.5) += 3; # делает значение равным 459.7

При обращении к несуществующему элементу возвращается значение undef (как и при обращении к отсутствующему элементу массива или к неопределенной скалярной переменной).



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


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

Ofred = ("hello","dolly");

$у = 2;

$х = "This is $fred[l]'s place"; # "This is dolly's place" $x = "This is $fred($y-l]'s place"; # To же самое

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

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

Ofred = ("hello","dolly"); # присвоить массиву @fred значение для проверки $fred = "right";

# мы пытаемся сказать "this is right[1]" $х = "this is $fred[l]"; t неправильно, дает "this is dolly" $x = "this is ${fred}[l]"; t правильно (защищено фигурными скобками) $х = "this is $fred"."[1]"; # правильно (другая строка) $х = "this is $fred\[l]"; t правильно (скобку защищает обратная косая)

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

@fred = ("а", "bb","ccc",1,2,3) ;

$а11 = "Mow for Sfred here!";

# $all получает значение "Now for a bb ccc 123 here!"

Можно также выбрать часть массива с помощью среза:

@fred = ("а","bb","ccc",1,2,3);

$а11 = "Now for @fred[2,3] here!";

# $all получает значение "Now for ccc 1 here!" $all = "Now for @fred[@fred[4,5]] here!"; ” то же самое

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



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


Если вам никогда не приходилось пользоваться анонимным FTP, разберите приведенный ниже пример сеанса с комментариями. Текст, набранный жир-ным шрифтом — это то, что вы должны вводить с клавиатуры; комментарии набраны курсивом. Символ % — это приглашение, которое вводить не нужно.

% ftp ftp.CPAN.org

(на самом деле такого узла нет)

Connected to ftp.CPAN.org 220 CPAN FTP server (Version wu-2.4(l) Fri Dee 1 00:00:00 EST 1995} ready.

Name (ftp.CPAN.orgiCPAN) : anonymous

331 Guest login ok, send your complete e-mail address as password. Password: camal@nutshall.ccm

(здесь введите свое пользовательское имя и имя своего хоста)

230 Guest login ok, access restrictions apply. ftp> cd pub/perl/CPAN/src 250 CWD command successful -

ftp> binary

(для сжатых файлов нужно задать двоичный режим передачи)

ftp> get latest.tar.gz 200 PORT command successful. 150 Opening BINARY mode data connection for FILE. 226 Transfer complete.

{повторите этот шаг для каждого из нужных вам файлов)

ftp> quit 221 Goodbye. %

    Получив файлы, распакуйте их, а затем сконфигурируйте, постройте и инсталлируйте Perl:

'"., gunzip < latest.tar.gz I tar xvf -% cd perl5.004 (используйте реальное имя каталога) Теперь любую из следующих двух строк:

% sh configure

{строчная буква с — для автоматического конфигурирования)

% sh Configure

{прописная буква С - для конфигурирования вручную)

% make

(построить весь Perl)

'i. make test

(проверить, работает ли он)

% make install

(для этого вы должны быть привилегированным пользователем)

* Суффикс .tar.gz. означает, что это стандартный Internet-формат архива, созданного програм-мой tar.



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


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

$FRED{"fred"} = "bedrock"; # создать (или обновить) злемент delete $FRED("barney"}; # удалить злемент базн данных foreach $key (keys %FRED) ( # пройти по всем значенням print "$key has value of $FRED{$key)\n";

}

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

while (($key, $value) = each(%FRED) ) ( print "$key has value of $value\n";

}

Если вы обращаетесь к системним DBM-базам данных, например к базам данных, созданным системами sendmail

и NIS, вы должны иметь в виду, что в плохо написанных С-программах в конце строк иногда стоит символ NUL (\0). Программам библиотеки DBM зтот NUL не нужен (они обрабатывают двоичные данные с помощью счетчика байтов, а не строки с символом NUL на конце), позтому он хранится как часть данных. В таком случае вы должны добавлять символ NUL в конец своих ключей и отбрасывать NUL, стоящий в конце возвращаемых значений, иначе данные не будут иметь смысла.

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

dbmopen(%ALI, "/etc/aliases", undef) I I die "no aliases?";

$value °= $ALI {"merlyn\0" 1; # обратите внимание на добавленный NUL

chop ($value) ; # удалить добавленный NUL

print "Randal's mail is headed for: $value\n"; # показать результат

В вашей версии UNIX база данных псевдонимов может храниться не в каталоге /etc, а в каталоге /usr/lib.

Чтобы вияснить, где именно она хранится, придется провести маленькое расследование. Новые версии sendmail

зтим NUL-дефектом не страдают. ''



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


После того как дескриптор файла открыт для чтения, из него можно читать строки точно так же, как со стандартного ввода stdin. Например, для чтения строк из файла паролей используется такой код:

open (ЕР,"/etc/passwd");

while (<EP>) (

chomp;

print "I saw $_ in the password file!\n";

}

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

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

print LOGFILE "Finished item $n of $max\n";

print STDOUT "hi, world!\n"; # как print "hi, world!\n"

В этом случае сообщение, начинающееся со слова Finished, посылается в дескриптор файла logfile, который, предположительно, был открыт в программе ранее. Сообщение hi, world направляется на стандартный вывод, как и раньше, когда вы не указывали дескриптор. Мы говорим, что stdout — это дескриптор файла по умолчанию для оператора print.

Предлагаем вам способ копирования данных из файла, заданного в переменной $а, в файл, указанный в переменной $Ь. Он иллюстрирует почти все, о чем мы рассказывали на последних нескольких страницах:*

open(IN,$a) || die "cannot open $a for reading: $!";

open(OUT,">$b") || die "cannot create $b: $!";

while (<IN>) { # прочитать строку из файла $а в $_

print ОПТ $_; # вывести эту строку в файл $Ь ) close(IN) || die "can't close $a: $!";

close (ОПТ) || die "can't close $b: $!";



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


Еще один способ создания нового процесса — клонирование текущего Perl-процесса с помощью UNIX-функции fork. Функция fork делает то же самое, что и системний вызов fork(2): создает клон текущего процесса. Зтот клон (он называется порожденным процессом, а оригинал — родительским) использует тот же выполняемый код, те же переменные и даже те же открьггые файлы. Различаются зти два процесса по возвращаемому значенню функции fork: для порожденного процесса оно равно нулю, а для родительского — ненулевое (или undef, если зтот системний вызов окажется неудачним). Ненулевое значение, получаемое родительским процессом,— зто не что иное как идентификатор порожденного процесса. Ви можете проверить возвращае-мое значение и действовать соответственно:

if (!defined($child_pid = fork()) {

die "cannot fork: $!";

} elsif ($pid) {

4s я —

родительский процесе } else (

# я — порожденннй процесе >

Чтоби максимально зффективно использовать зтот клон, нам нужно изучить еще несколько функции, которьге восьма похожи на своих UNIX-тезок: зто функции wait, exit и ехес.

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

ехес "date";

заменяет текущую Perl-программу командой date, направляя результат зтой команди на стандартний вивод Perl-программи. После завершення команди date делать больше нечего, потому что Perl-программа давно исчезла.

Все зто можно рассматривать и по-другому: функция system похожа на комбинацию функции fork c функцией ехес, например:

# МЕТОД І... использование system:

system("date");

t МЕТОД 2... использование fork/exec:

unless (fork) (

# fork видала нуль, позтому я — порожденный процесе и я выполняю:

exec ("date") ; t порожденный процесе становится командой date


}

Использовать fork и exec таким способом — не совсем правильно, потому что команда date и родительский процесе "пыхтят" одновременно, их результати могут переметаться и испортить все дело. Как дать родительскому процессу указание подождать, пока не завершится порожденный процесе? Именно зто и делает функция wait; она ждет завершення данного (да и любого, если быть точним) порожденного процесса. Функция waitpid более разбор-чива: она ждет завершення не любого, а определенного порожденного процесса:

if (!defined($kidpid = fork()) (

# fork возвратила undef, т.е. неудача die "cannot fork: $!";

” elsif ($pid =" 0) {

# fork возвратила О, позтому данная ветвь — порожденный процесе exec("date") ;

# если exec терпит неудачу, перейти к следующему оператору die "can't exec date: $!";

} else {

# fork возвратила не 0 и не undef,

# позтому данная ветвь — родительский процесе waitpid($kidpid, 0) ;

}

Если все зто кажется вам слишком сложным, изучите системные вызовы fork(2) и ехес(2), отыскав материалы о них в каком-нибудь руководстве по ОС UNIX, потому что Perl просто передает вызовы зтих функций прямо в системные вызовы UNIX.

Функция exit обеспечивает немедленньш выход из текущего Perl-про-цесса. Она используется для прерывания Perl-программы где-нибудь посе-редине или — вместе с функцией fork — для вьшолнения Perl-кода в процессе с последующим выходом. Вот пример удаления нескольких файлов из каталога///?у? в фоновом режиме с помощью порожденного Perl-процесса:

unless (defined ($pid = fork)) ( die "cannot fork: $!";

}

unless ($pid) (

unlink </tmp/badrock.*>; # удалить зти файлн exit; # порожденный процесе останавливается здесь

)

# родительский процесе продолжается здесь

waitpid($pid, 0); # после уничтожения порожденного процесса нужно все убрать

Без использования функций exit порожденный процесе продолжал бы вьшолнять Perl-код (со строки "# родительский процесе продолжается здесь") — а как раз зтого нам и не нужно.

Функция exit может иметь необязательный параметр, служащий числовим кодом выхода, который воспринимается родительским процессом. По умолчанию выход производится с нулевым кодом, показывающим, что все прошло нормально.


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


Когда вы из командной строки shell задаєте выполнение какой-либо команды, он обычно создает новый процесе. Зтот новый процесе становится порожденным процессом shell, выполняется независимо, но в координации с последним.

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

Самий простой способ запуска нового процесса — использовать для зтого функцию system. В простейшей форме зта функция передает в совершенно новый shell /bin/sh одну строку, которая будет выполняться как команда. По выполнении команды функция system возвращает код завершення данной команды (если все было нормально — зто, как правило, 0). Вот пример того, как Perl-программа выполняет команду date c помощью shell*:

system("date");

Здесь мы не проверяем возвращаемое значение, но неудачный исход выполнения команд н date вряд ли возможен.

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

* В данном случае shell фактически не используется, поскольку Рсгі сам выполняет операций shell, если командная строка достаточно проста, как в данном случае.

Три стандартних файла для функции system (стандартний ввод, стандартний внвод и стандартний вивод ошибок) наследуются от Perl-процесса. Таким образом, результат выполнения команди dale в приведенном више примере направляется туда же, куда поступает результат выполнения функции print stdout — скореє всего, на дисплей визвавшего ее пользователя. Поскольку ви запускаєте shell, то можете переадресовать стандартний вивод, пользуясь обычными для /Ып/sh

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

system("date >right_now") && die "cannot create right_now";

На зтот раз ми не только посилаєм результат команди date в файл, внполняя переадресацию в shell, но и проверяем статус возврата. Если статус возврата — значение "истина" (не нуль), зто значит, что с командой shell что-то произошло, и функция die внполнит свою миссию. Данное правило обратно обичним правилам выполнения операций в Perl: ненулевое возвра-щаемое значение операций system, как правило, указивает на какую-то ошибку.


Аргументом функции system может бить все, что пригодно для передачи в /Ып/sh,

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

Вот пример задания команд date

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

$where = "who_out.".++$і; t получить новое имя файла system "(date; who) >$where &";

В зтом случае функция system возвращает код выхода shell и показывает таким образом, успешно ли бил запущен фоновий процесе, но не сообщает, били ли успешно выполнени команди date и who.

В зтой заключенной в двойные кавички строке производится интерполяция переменних, позтому переменная $ where заменяется своим значением (зто делает Perl, а не shell). Если би ви хотели обратиться к переменной shell с именем $where, вам нужно било би поставить перед знаком доллара обратную косую или использовать строку в одинарних кавнчках.

Помимо стандартних дескрипторов файлов, порожденний процесе на-следует от родительского процесса много других вещей. Зто текущее значение, заданное командой umask,

текущий каталог й, конечно, идентификатор пользователя.

Кроме того, порожденний процесе наследует все переменнне среды. Зти переменные обычно изменяются командой csh setenv или же соответствующим присваиванием и команд ой export shell (/bin/sh). Переменные среды исполь-зуются многими утилитами, включая сам shell, для изменения порядка работы зтих утилит и управления йми.

В Perl предусмотрена возможность проверки и изменения текущих пере-менных среды посредством специального хеша, который называется %env. Каждый ключ зтого хеша соответствует имени переменной среды, а соответ-ствующее значение — значенню переменной. Содержимое данного хеша отражает параметры среды, переданные Perl родительским shell; изменение хеша изменяет параметри среды, которую использует Perl и порожденные им процессы, но не среды, используемой родительскими процессами.



Вот простая программа, которая работает, как printenv:

foreach $key (sort keys %ENV) ( print "$key = $ENV($key)\n";

> . ,

Обратите внимание: знак равенства здесь — зто не символ операции присваивания, а просто текстовый символ, с помощью которого функция print выдает сообщения вида TERM=xterm или U3ER=merlyn.

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

$oldPATH = $ENV{ "PATH"); # сохранить предыдущий путь $ENV("PATH") = "/bin:/usr/bin:/usr/ucb"; # ввести известньй путь systemC'grep fred bedrock >output") ; # запустить команду $ENV{"PATH"} =

$oldPATH; # восстановить предьшуший путь

Как много текста придется набирать! Гораздо быстрее будет просто установить локальнеє значение для зтого злемента хеша.

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

{

local $ENV{"PATH"> " "/bin:/usr/bin:/usr/ucb"; ! systemC'grep fred bedrock >output");

>

Функция system может принимать не один аргумент, а список аргумен-тов. В зтом случае Perl не передает список аргументов в shell, а рассматривает первый аргумент как подлежащую выполнению команду (при необходимости производится ее поиск согласно переменной path), а остальные аргумен-ты — как аргументи команды без обычной для shell интерпретации. Другими словами, вам не нужно заключать в кавычки пробельные символы и беспо-коиться об аргументах, которые содержат угловые скобки, потому что все зто — просто символы, передаваемые в программу. Таким образом, следую-щие две команды зквивалентны:

system "grep 'fred flintstone' buffaloes"; # с использованием shell system "grep","fred flintstone","buffaloes"; # без использования shell

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

Вот еще один пример зквивалентных форм:

Ocfiles = ("fred.c","barney.c"); # что компилировать Soptions = ("-DHARD","-DGRANITE"); # опции system "cc -o slate Soptions Bcfiles"; # c shell system "cc","-o","slate",goptions,Scfiles"; # без shell


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


Еще один способ запуска процесса — заключить командную строку для /Ып/sh в обратные кавычки. Как и в shell, зтот механизм запускает команду и ожидает ее завершення, получая данные со стандартного вывода по мере их поступления:

$now = "the time is now".'date'; # получает текст и дату

Значение переменной $now теперь представляет собой текст the time is now и результат выполнения команди date(l) (включая конечний символ новой строки):

the time is now Fri Aug 13 23:59:59 PDT 1996

Если взятая в обратные кавычки команда используется не в скалярном, а в списочном контексте, то возвращается список строкових значений, каждое из которых представляет собой строку (оканчивающуюся символом новой строки*) из результата выполнения команды. В примере с командой date у нас был бы всего один злемент, потому что она видала всего одну строку текста. Результат работы команды who выглядит так:

merlyn tty42 Dec 7 19:41 fred ttylA Aug 31 07:02 barney ttylF Sep 1 09:22

Вот как можно получить зтот результат в списочном контексте:

foreach $_ ('who') ( # один раз для каждой строки текста из who <$who,$where,$when) = /(\S+)\s+(\S+)\s+(.*)/;

print "$who on $where at $when\n";

}

* Или символом, которьш у вас занесен в переменную $/.

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

Стандартний ввод и стандартний вывод ошибок команди, взятой в обратные кавички, наследуются от Perl-процесса*. Зто значит, что обычно стандартний вывод таких команд ви можете получить как значение строки, заключенной в обратные кавички. Одна из распространенных операций — обьединение стандартного внвода ошибок со стандартним виводом, чтобы команда в обратннх кавычках "подбирала" их оба. Для зтого используется конструкция shell 2>&1:

die "rm spoke!" if 'rm fred 2>&1';

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



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


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

open(WHOPROC, "who 1"); # открнть who для чтения

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

* На самом деле все не так просто. См. соответствующий ответ в разделе 8 сборника часто задаваемых вопросов по Perl ("Как перехватить stderr из внешней команды?"). Если у вас Perl версии 5.004, зтот сборник распространяется как обычная man-страница — в данном случас per/faq8( 1).

** Ho не одновременно. Примеры двунаправленной связи приведены в главе 6 книги Programming Perl и на man-странице рег1ірс(1).

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

массив:

@whosaid = <WHOPROC>;

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

open(LPR,"|Ipr -Psiatewriter") ;

print LPR Srockreport;

close(LPR) ;

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


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

open(LPR,"|Ipr -Psiatewriter >/dev/null 2>&1");

С помощью операций >/dev/null обеспечивается отбраснвание стандартного вивода путем переадресации его на нулевое устройство. Операция 2>&1 обеспечивает передачу стандартного вивода ошибок туда, куда направляется стандартний вывод, позтому сообщения об ошибках также отбрасываются.

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

open (WHO,"who[ ") ;

open (LPR,"|Ipr - Psiatewriter");

while (<WHO>) (

unless (/fred/) { # не показывать имя Фред print LPR $_:

> } close WHO;

close LPR;

Считивая из дескриптора who по одной строке, зтот фрагмент кода выводит в дескриптор lpr все строки, которне не содержат строкового значення fred. В результате на принтер внводятся только те строки, которне не содержат имени fred.

Вовсе не обязательно указывать в команде open только по одной команде за один прием. В ней можно задать сразу весь конвейєр. Например, следую-щая строка запускает процесе ls(l),

который передает свои результати по каналу в процесе tail(l), который, в свою очередь, передает свои результати в дескриптор файла who pr:

open(WHOPR, "Is I tail -r I");


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


Слово Perl является аббревиатурой выражения Practical Extraction and Report Language (практический язык извлечений и отчетов), хотя иногда его называют Pathologically Eclectic Rubbish Lister (патологически эклектичный мусорный листер). Не стоит спорить о том, какое из этих названий более правильное, потому что оба они принадлежат Ларри Уоллу, создателю и главному архитектору, распространителю и опекуну языка Perl. Ларри создал этот язык, когда пытался формировать отчеты из иерархии файлов системы оповещения об ошибках, похожей на Usenet-новости, а возможности применявшегося в то время обработчика потоков данных awk

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

Позабавившись немного с этой версией, добавив кое-что, Ларри предложил ее сообществу читателей материалов телеконференций Usenet, известному также как "Сеть" (the Net). Пользователи, имеющие доступ к входящим в систему Usenet компьютерам, разбросанным по всему свету (а их в то время было несколько десятков тысяч), обеспечили для создателя Perl эффективную "обратную связь", спрашивая, как делать одно, другое, третье. Многие из этих задач Ларри даже и не собирался ставить перед своим маленьким новым языком программирования.

В результате Perl все рос и рос, причем почти с той же скоростью, что и операционная система UNIX. (Специально для новичков: все ядро UNIX тогда требовало памяти объемом 32 К! Теперь мы счастливы, если нам удается уместить его в несколько мегабайтов.) Выросли и его возможности.

Повысилась переносимость. То, что когда-то было компактным языком, теперь сопровождается сотнями страниц документации, в состав которой входят десятки man-страниц, 600-страничный справочник серии Nutshell, материалы множества телеконференций Usenet с 200000 подписчиков, — а теперь еще и эта скромная книга.

Ларри уже не сопровождает Perl в одиночку, но сохраняет свой эксклюзивный титул главного архитектора. A Perl все растет и растет.

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



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


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

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

Зти значення устанавливаются во внутреннем формате времени, а имен-но в количестве секунд, прошедших после полуночи 1 января 1970 года по среднегринвичскому времени. Когда мы писали нашу книгу, зта цифра достигла 800 миллионов с небольшим. (Во внутреннем формате она пред-ставляется как 32-разрядное число без знака, и если все мы не перейдем на 64-разрядные (й более) машины, то переполнение наступит где-то в следую-щем столетии. У нас будут гораздо более серьезные проблеми в 2000-м году*.)

Функция utime работает аналогично функциям chmod и unlink. Она получает список имен файлов и возвращает число файлов, параметри времени которых были изменены. Вот что нужно сделать, чтобы файли fred и barney

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

$atime = $mtime = 700_000_000; # некоторое время назад utime($atime,$mtime,"fred","barney")

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

был изменен спустя 20 минут после текущего момента времени:

$when = time() + 20*60; # 20 минут с текущего момента utime($when,$when,"max headroom");

* Perl-функции localtime nqmtime

работаюттак, каквС: они возвращают год, изкоторого вычтена цифра 1900. В 2003-м году localtime выдаст год как 103.



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


Права доступа к файлу или каталогу определяют, кто (в широком смысле слова) и что может делать с зтим файлом или каталогом. В UNIX общепри-нятый метод изменения прав доступа к файлу — применение команды chmod(l). (Если вы не знакомы с зтой операцией, обратитесь к ее man-странице.) В Perl права доступа изменяются с помощью функции chmod. Зта функция получает в качестве аргументов заданный восьмеричним числом режим доступа и список имен фаилов и пытается изменить права доступа ко всем зтим файлам в соответствии с указанным режимом. Чтобы сделать файлы fred и Ьатеу

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

chmod(0666,"fred","barney");

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

* В данном случае вы не создаете каталог с самими широкими правами доступа. Определить права доступа вам также поможет текущая маска доступа umask вашего процесса. В UNIX-системах см. описание комавды shell umask

или man-страницу umask(2).

Функция chmod возвращает число файлов, для которых были успешно изменены права доступа (даже если в результате фактически ничего не изменилось). Таким образом, в отношении контроля ошибок она работает аналогично функции unlink. Позтому, чтобы изменить права доступа к файлам fred и Ьагпеу

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

foreach $file ("fred","barney") f unless chmod (0666,$file) (

warn "hmm... couldn't chmod $file.\$!";



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


Каждый файл в файловой системе (обычный, каталог, файл устройства и т.д.) имеет владельца и группу. Зги параметры определяют, кому принадлежат права доступа, установленные для файла по категориям "владелец" и "группа" (чтение, запись и (или) выполнение). Владелец и группа определяются в момент создания файла, но при определенных обстоятельствах вы можете изменить их. (Зти обстоятельства зависят от конкретной разновидности UNIX, c которой вы работаете; подробности см. на man-странице chown.)

Функция chown получает идентификатор пользователя (UID), иденти-фикатор группы (GID) и список имен файлов и пытается изменить принад-лежность каждого из перечисленных файлов в соответствии с указанными идентификаторами. Успешному результату соответствует ненулевое значе-ние, равное числу файлов, принадлежность которых изменена (как в функ-циях chmod и unlink). Обратите внимание: вы одновременно меняете и владельца, и группу. Если какой-то из зтих идентификаторов вы менять не хотите, поставьте вместо него -1. Помните, что нужно использовать числовые UID и GID, а не соответствующие символические имена (хотя команда chmod и принимает такие имена). Например, если UID файла fred — 1234, а идентификатор группы stoners, которой зтот файл принадлежит по умолчанию,— 35, то в результате применения следующей команды файлы slate и granite

переходят к пользователю fred и его группе:

chown(1234, 35, "slate", "granite"); # то же, что й

# chown fred slate granite

# chgrp stoners slate granite

В главе 16 вы узнаете, как преобразовать

fred в 1234 и stoners в 35.



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


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

Изменение дескриптора файла с помощью функции select()

Когда мы говорили о функции print в главе 6, я упомянул, что print и print stdout идентичны, потому что stdout — это установка по умолчанию для print. Это не совсем так. Настоящая установка по умолчанию для print (а также для write и еще нескольких операций, до которых мы скоро доберемся) — это выбранный в текущий момент дескриптор файла.

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

print "hello world\n"; # аналогично print STDOUT "hello worldVn"

select (LOGFILE); # выбрать новый дескриптор файла

print "howdy, world\n"; # аналогично print LOGFILE "howdy, world\n"

print "more for the log\n"; # еще в LOGFILE

select (STDOOT); # вновь выбрать STDOUT

print "back to stdout\n"; # это идет на стандартный вывод

Отметим, что операция select — "липкая"; после выбора нового дескриптора он остается "выбранным в текущий момент" до проведения следующей операции select.

Таким образом, более удачное определение stdout по отношению к функциям print и write будет звучать так: stdout — выбранный в текущий момент дескриптор по умолчанию, или просто дескриптор по умолчанию.

В подпрограммах может возникать необходимость смены выбранного в текущий момент дескриптора файла. Представляете, какое неприятное чувство можно испытать, вызвав подпрограмму и обнаружив, что все тщательно проверенные строки текста уходили куда-то "налево", потому что подпрограмма, оказывается, изменила выбранный в текущий момент дескриптор файла и не восстановила его! Что же должна делать "хорошо воспитанная" подпрограмма? Если она "знает", что текущий дескриптор — stdout, она может восстановить выбранный дескриптор с помощью кода, похожего на приведенный выше. А если программа, которая вызвала подпрограмму, уже изменила выбранный дескриптор файла — что тогда?


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

$oldhandle = select LOGFILE;

print "this goes to LOGPILEW;

select ($oldhandle); # восстановить предыдущий дескриптор

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



Изменение имени формата



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

Например, чтобы использовать формат addresslabel с дескриптором stdout, следует просто записать:

$~ = "ADDRESSLABEL";

А что, если нужно установить для дескриптора report формат summary? Для этого необходимо сделать всего лишь следующее:

$oldhanlde =• select REPORT;

$~ ° "SUMMARY";

select ($oldhandle);

Когда в следующий раз мы напишем

write (REPORT) ;



то

тем самым передадим текст на дескриптор report, но в формате summary*.

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

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



Изменение имени формата начала страницы



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



* Объектно-ориентированный модуль FileHandle, входящий в состав стандартного дистрибутива Perl, обеспечивает выполнение этой задачи более простым способом.



Изменение длины страницы



Если определен формат начала страницы, длина страницы приобретает особое значение. По умолчанию длина страницы равна 60 строкам, т.е. если результаты работы функции write не умещаются до конца 60-й строки, то перед дальнейшим выводом текста автоматически вызывается формат начала страницы.

Иногда 60 строк — не то, что нужно. Этот параметр можно изменить, установив переменную $=. Данная переменная содержит текущую длину страницы для выбранного в текущий момент дескриптора файла. Опять-таки, для замены дескриптора stdout (выбранного в текущий момент дескриптора файла по умолчанию) на другой нужно использовать операцию select. Вот как следует изменить дескриптор файла logfile так, чтобы страница содержала 30 строк:

$old = select LOGFILE; # выбрать LOGFILE и сохранить старый дескриптор $= ° 30;

select $old;

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



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



Если вы выводите свой текст в дескриптор файла с помощью функции print, то значение счетчика строк будет неправильным, потому что Perl проводит подсчет строк только для функции write. Если вы хотите дать Perl знать, что выводите несколько дополнительных строк, можно настроить внутренний счетчик строк Perl, изменив значение переменной $-. Эта переменная содержит число строк, оставшихся на текущей странице для выбранного в текущий момент дескриптора файла. Каждая функция write уменьшает число оставшихся строк на число фактически выведенных строк. Когда значение этого счетчика достигает нуля, вызывается формат начала страницы и из переменной $= (задающей длину страницы) копируется значение $-.

Например, чтобы сообщить Perl, что вы послали в STDOUT дополнительную строку, нужно сделать следующее:

write; # вызвать формат STDOUT для STDOUT

print "An extra line... oops!\n"; # это идет в STDOUT $- --; # декрементировать $-, чтобы показать, что в STDOUT пошла строка не из write

write; # сработает, учтя дополнительную строку

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


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


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

$s = substr ($строка, $нач<зло, $длина) ;

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

$hello = "hello, world!";

$grab ” substr($hello, 3, 2); t $grab получает "lo" $grab = substr($hello, 7, 100); # 7 до конца, или "world!"

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

$big = substr("10000000000",0,$power+l); # 10 ** $power

Если количество символов равно нулю, то возвращается пустая строка. Если либо начальная, либо конечная позиция меньше нуля, то такая позиция отсчитывается на соответствующее число символов, начиная с конца строки. Так, начальная позиция -їй длина 1 (или более) дает последний символ. Аналогичным образом начальная позиция -2 отсчитывается от второго символа относительно конца строки:

$stuff = substr("a very long string",-3,3); # последние три символа $stuff = substr("a very long string",-3,1); # буква і

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


Отсутствие аргумента "длина" зквивалентно взятию в качестве зтого аргумента большого числа — в зтом случае извлекается все от выбранной позиции до конца строки*.

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

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

В результате такого присваивания изменяется та часть строки, которая была бы возвращена, будь substr использована не в левой, а в правой части выражения. Нопример, substr ($var, 3,2) возвращает четвертьш и пятый символы (начиная с 3 в количестве 2), позтому присваивание изменяет указанные два символа в $var подобно тому, как зто приведено ниже:

$hw = "hello world!";

substr($hw, 0, 5) = "howdy"; # $hw теперь равна "howdy world!"

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

substr($hw, 0, 5) = "hi"; # $hw теперь равна "hi world!"

В следующем примере зта строка удлиняется:

substr($hw, -б, 5) = "nationwide news"; # заменяет "world"

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


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


Основной пункт распространения Perl — это Comprehensive Perl Archive Network, или CPAN (Сеть полных Perl-архивов). Эти архивы содержат не только исходный код, но и практически все материалы, которые вам когда-либо понадобятся для работы с Perl. CPAN зеркально копируется десятками узлов по всему свету. Главный узел — ftp.funet.fi (128.214.248.6). Вы можете найти и более близкий к вам узел CPAN, получив файл /pub/languages/perl/CPAN/MIR-RORS с этого узла. Можно также обратиться с помощью Web-броузера к сервису мультиплексирования CPAN по адресу www.perl.com. Если вы запросите с этого Web-сервера файл, имя которого начинается на /CPAN/, он соединит вас с ближайшим узлом CPAN, выбрав его по вашему доменному имени. Вот некоторые популярные адреса (URL) в CPAN:

http://www.perl.corn/CPAN/

http://www.perl.com/CPAN/README.html

http://www.perl.corn/CPAN/modules

http://www.perl.com/CPAN/ports

http://www.perl.corn/CPAN/doc

http://www.perl.com/CPAN/sre/latest.tar.Gz

    Сервис мультиплексирования CPAN пробует соединить вас с локальной быстродействующей машиной через высокопроизводительный концентра-тор. Это, однако, получается не всегда, потому что доменные имена могут и не отражать схемы сетевых соединений. Например, ваше хост-имя может заканчиваться на .se, но при этом вам лучше будет соединяться не со Швецией, а с Северной Америкой. В этом случае вы можете самостоятельно выбрать узел по следующему URL:

http://www.perl.com/CPAN

    Обратите внимание на отсутствие косой черты в конце этого URL. Если конечная косая опущена, мультиплексор CPAN выдает меню дублирующих серверов CPAN, из который вы можете выбрать подходящий. Если ваш Web-броузер поддерживает встроенные переменные cookies, то мультиплек-сор CPAN автоматически запомнит выбранный вами узел.

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


ftp.perl.corn (199.45.129.30)

ftp.cs.Colorado.edu (131.211.80.17)

ftp.funet.fi (128.214.248.6)

ftp.cs.run.nl (131.211.80.17)

     Местонахождение главного каталога зеркального сервера CPAN на этих машинах может быть разным, но скорее всего это нечто вроде /pub/perl/CPAN.

    Где находятся файлы В главном каталоге CPAN вы увидите как минимум следующие подкаталоги:

authors

Этот каталог содержит многочисленные подкаталоги, по одному для каждого автора программного обеспечения. Например, если вы захотите найти знаменитый модуль CGI.pm Линкольна Штейна, будучи твердо уверены, что именно он его автор, то можете посмотреть в каталоге authors/Lincoln__Stein. Если бы вы не знали, что Штейн — автор этого модуля, то можно было бы посмотреть в каталоге модулей, который описывается ниже.

doc

Каталог, содержащий всевозможную документацию на Perl. Это вся официальная документация (man-страницы) в нескольких форматах (тек-стовый ASCII, HTML, PostScript и собственный формат Perl POD), а также сборники часто задаваемых вопросов и интересные дополнитель-ные документы.

modules

Каталог содержит отдельные модули, написанные на С, Perl или обоих этих языках. Расширения позволяют вам эмулировать и использовать функцио-нальные возможности других программных продуктов, например, графиче-ских средств Tk, UNIX-библиотеки curses и математических библиотек. Они позволяют также взаимодействовать с базами данных (Oracle, Sybase и др.) и создавать HTML-файлы и CGI-сценарии.

ports

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

scripts

Набор разнообразных сценариев, собранных со всего мира. Если вам нужно узнать, как сделать что-либо, или если вы просто хотите посмотреть, как другие пишут программы, просмотрите этот каталог. Подкаталог nutshell содержит примеры, приведенные в нашей книге. (Эти тексты можно также получить на узле издательства O'Reilly & Associates, ftp,ora.com, в каталоге /published/oreMy/nutshell/leammg_perl2/.

src

В этом каталоге вы найдете исходный код стандартного дистрибутива Perl. Его текущая редакция всегда находится в файле src/lutest.tar.gz. Этот боль-шой файл содержит весь исходный код и полную документацию. Конфи-гурирование и инсталляция должны быть относительно простыми как в UNIX- и UNIX-подобных системах, так и в VMS и OS/2. Начиная с версии 5.004, Perl инсталлируется также в 32-разрядных Windows-системах.


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


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

    В каталоге/^дистрибутива Perl вы найдете ряд программ-примеров. Есть и другие лакомые кусочки — можете посвятить их поиску один из дождливых дней. Изучите исходный код Perl (если вы — С-хакер с мазохистскими наклонностями). Взгляните на тестовый комплект. Посмотрите, как Configure определяет наличие системного вызова mkdir(l). Определите, как Perl выпол-няет динамическую загрузку С-модулей. В общем, делайте все, что вам по душе.



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


Вводить все равно приходится очень много, но в CGI.pm есть множество удобных функций, упрощающих набор. Каждая из этих функций возвращает строковое значение, которое вы будете выводить. Например, header () возвращает строковое значение, содержащее строку Content-type с последующей пустой строкой, start_html (строка) возвращает указанную строку как HTML-титул (название документа), hi (строка) возвращает указанную строку как HTML-заголовок первого уровня, а р (строка) возвращает указанную строку как новый HTML-абзац.

Мы могли бы перечислить все эти функции в списке, прилагаемом к оператору use, но такой список разросся бы до небывалых размеров. В CGI.pm, как и во многих других модулях, имеются так называемые директивы импорта —

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

: cgi

Импортировать все методы обработки аргументов, например param ().

: form

Импортировать все методы создания заполняемых форм, например text-field().

:html2

Импортировать все методы, которые генерируют стандартные элементы HTML 2.0.

:htmi3

Импортировать все методы, которые генерируют элементы, предложенные в HTML 3.0 (такие как <table>, <super> и <sub>).

:netscape

Импортировать все методы, которые генерируют расширения HTML, характерные для Netscape.

:shortcuts

Импортировать все сокращения, генерируемые HTML (т.е. "html2" + "html3" + "netscape").

:standard

Импортировать "стандартные" возможности: "html2", "form" и "cgi".

:all

Импортировать все имеющиеся методы. Полный список приведен в модуле CGI.pm, где определяется переменная %tags.

Мы будем использовать только директиву : standard. (Подробная информация об импортировании функций и переменных из модулей приведена в главе 7 книги Programming Perl, а также на man-странице Exporter 3).}

Вот как выглядит наша программа со всеми сокращениями, которые используются в CGI.pm:

#!/usr/local/bin/perlS -w

# cgi-bin/ice_cream # программа ответа на форму о любимом

t

сорте мороженого (версия 2) use CGI qw(:standard);

print header() ;

print start_html("Hello World"), hi ("Hello World");

my $favorite = param("flavor");

print p("Your favorite flavor is $favorite.");

print end_html();

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



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


Процесс выборки и построения отдельных модулей Perl протекает немного по-другому. Скажем, вы хотите построить и инсталлировать модуль CoolMod. Сначала нужно выбрать его, воспользовавшись для этого командой ^(1) или с помощью Web-броузера обратиться к сервису модулей из hffp://www.perLcom/, который всегда выбирает самую последнюю версию конкретного зарегистри-рованного модуля. Адрес, который нужно ввести в броузер, выглядит так: http://www.perl.com/cgi-bin/cpanmod?module=CoolMod

    Получив этот файл, сделайте следующее:

% gunzip < CoolMod-2.34. tar .gz I tar xvf -% cd CoolMod-2 .34

'ъ perl Makefile.PL

(создает реальный Makefile)

^ make

(построить весь модуль)

^ make test

(проверить, работает ли он)

°J make install

(для этого вы, наверное, должны быть привилегированным пользователем)

    После успешной инсталляции модуля CoolMod (он автоматически поме-щается в директорию Perl-библиотеки вашей системы) в ваших программах можно использовать use CooiMod; а вы сможете читать документацию этого модуля с помощью команды man CoolMod (или perldoc CoolMod).



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


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

(способ представления значения списка в программе) состоит из значений, отделенных друг от друга запятыми и заключенными в круглые скобки. Эти значения образуют элементы списка. Например:

(1,2,3) # массив из трех значений 1, 2 и 3 ("fred",4.5) # два значения — "fred" и 4,5

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

($а, 17; # два значения: текущее значение переменной $а и 17 ($b+$c,$d+$e) # два значения

Пустой список (список, не содержащий элементов) представляется пустой парой круглых скобок:

() # пустой список (нуль элементов)

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

(1 .. 5) # то же самое, что (1, 2, 3, 4, 5)

(1.2 .. 5.2) # то же самое, что (1.2, 2.2, 3.2, 4.2, 5.2)

(2 .. 6,10,12) # тоже самое, что (2,3,4,5,6,10,12)

($а .. $b) # диапазон, заданный текущими значениями переменных $а и $Ь

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

(1.3 .. 6.1) # то же самое, что (1.3,2.3,3.3,4.3,5.3)

Списочные литералы с множеством коротких текстовых строк со всеми этими кавычками и запятыми выглядят не очень привлекательно:

@а = ("fred","barney","betty","wilma"); # уф-ф!

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

@а = qw(fred barney betty wilma); # так-то лучше! @а = qw(

fred

barney

betty

wilma ); # то же самое

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

print("The answer is ",$a,"\n"); # трехэлементный списочный литерал

Этот оператор выводит на экран слова The answer is, затем пробел, значение переменной $а и символ новой строки. Другие способы использования списочных литералов мы рассмотрим позднее.

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



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


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

@fred_list = %fred; # @fred_list получает значение

# ("ааа","bbb","234.5","456.7") %barney = @fred_list; # создать %barney как %fred %barney = %fred; # ускоренный метод выполнения этой задачи %smooth ” ("ааа","bbb","234.5","456.7") ;

# создать %smooth как %fred из литеральных значений

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

Одно из применений такого свертывания-развертывания — копирование хеш-значения в другую хеш-переменную:

%сору = %original; # копировать из %original в %сору

Используя операцию reverse, можно создать хеш, в котором ключи и значения поменяются местами:

%backwards = reverse %normal;

Конечно, если %normal имеет два идентичных значения, то в %backwards они превратятся в один элемент, поэтому данную операцию лучше всего выполнять только над хешами с уникальными ключами и значениями.



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


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

если вам так больше нравятся). Вот снова функция add, на этот раз построенная на основе операции ту:

sub add {

ту ($sum); # сделать $sum локальной переменной $sum =0; # инициализировать сумму

*

foreach $_ (@_) (

$sum += $ ; # прибавить все элементы

}

return $sum # последнее вычисленное выражение: сумма всех элементов }

Когда выполняется первый оператор в теле подпрограммы, текущее значение глобальной переменной $sum сохраняется и создается совершенно новая переменная с именем $sum (и значением undef). При выходе из подпрограммы Perl отбрасывает эту локальную переменную и восстанавливает предыдущее (глобальное) значение. Эта схема работает даже в том случае, если переменная $sum в текущий момент является локальной переменной, взятой из другой подпрограммы (той, которая вызывает данную подпрограмму, или той, которая вызывает ту, которая вызывает данную подпрограмму, и т.д.). Переменные могут иметь много вложенных локальных версий, но одновременно разрешается обращаться только к одной из них.

Вот способ создания списка всех элементов массива, значения которых превышают число 100:

sub bigger_than_100 (

my (Oresult); # временная для хранения возвращаемого значения foreach $_ (@_) ( # проход по списку аргументов if ($_ > 100) ( # подходит?

push(@result,$_); # прибавить } )

return Oresult; # возвратить окончательный список }

Что, если бы нам понадобились все элементы, значения которых превышают 50, а не 100? Пришлось бы отредактировать программу, заменив 100 на 50. А если бы нам было нужно и то, и другое? В этом случае следовало бы заменить 50 или 100 ссылкой на переменную. Тогда программа выглядела бы так:


sub bigger_than (

my($n,@values); # создать локальные переменные ($n,@values) = @_; # выделить из списка аргументов значение предела

# и элементы массива my (@result); # временная переменная для хранения возвращаемого

4 значения

foreach $_ (@values) ( # проход по списку аргументов if ($_ > $n) { # подходит?

push(Oresult,$_); # прибавить } )

@result; t возвратить окончательный список }

# примеры вызова:

@new = bigger_than(100,@list); # @new содержит все значения Olist > 100 @this " bigger_than(5,l,5,15,30); # @this содержит значение (15,30)

Обратите внимание: в этот раз мы использовали еще две локальные переменные, чтобы присвоить имена аргументам. Это — весьма распространенная практика, потому что гораздо удобнее (да и безопаснее) использовать переменные $п и $values, чем указывать $_[0] и @_[1. . $#_].

В результате выполнения операции my создается список, который можно использовать в левой части операции присваивания. Этому списку можно присваивать начальные значения для каждой из вновь созданных переменных. (Если значения списку не присвоить, то новые переменные вначале получат значение undef, как и любая другая переменная.) Это значит, что мы можем объединить первые два оператора этой подпрограммы, т.е. операторы

my($n,@values) ;

($n,@values) = @_; # выделить из списка аргументов значение предела # и элементы массива

заменить на

my($n,@values)= @_;

Этот прием очень распространен в Perl. Локальным переменным, не являющимся аргументами, можно таким же способом присваивать литеральные значения, например:

my($sum) = 0; # инициализировать локальную переменную

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


Манипулирование файлами и каталогами


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



Метки


Что делать, если вы хотите выйти из блока, в которым содержится самый внутренний блок, иными словами, выйти сразу из двух вложенных блоков? В С вы могли бы обратиться к оклеветанному всеми оператору goto. В Perl такая хитрость не нужна — здесь по отношению к любому охватывающему блоку можно использовать операторы last, next и redo, присвоив этому блоку имя с помощью метки.

Метка — это еще один тип имени из еще одного пространства имен, отвечающего тем же правилам, что и имена скаляров, массивов, хешей и подпрограмм. Мы увидим, однако, что в метке нет специального префиксного символа (аналогичного $ в скалярах, символу & в подпрограммах и т.д.), поэтому метка print, к примеру, конфликтовала бы с зарезервированным словом print, в силу чего ее использование не допускается. По этой причине лучше создавать метки с именами, состоящими только из прописных букв и цифр, которые в будущем никогда не будут использоваться в зарезервированных словах. Кроме того, прописные буквы и цифры хорошо выделяются в тексте программы, который набирается в основном с использованием нижнего регистра.

Выбрав имя метки, введите его прямо перед оператором, содержащим блок, и поставьте двоеточие:

МЕТКА: while (условие) { оператор;

оператор;

оператор;

if (другое_условие) { last МЕТКА;

} )

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

OUTER: for ($i=l; $i <= 10; $i++) {

INNER: for ($j=l; $j <= 10; $j++) ( if ($i * $j == 63) (

print "$i times $j is 63!\n";

last OUTER;

}

if ($j>=$i) ( next OUTER;

} }

}

Этот набор операторов позволяет перебирать все результаты перемножения небольших чисел до тех пор, пока не будет найдена пара, произведение которой равно 63 (7 и 9). После того как эта пара будет найдена, проверка остальных чисел теряет смысл, поэтому первый оператор if обеспечивает выход из обоих циклов for, используя для этого оператор last с меткой. Второй оператор if гарантирует, что большим из двух чисел всегда будет первое, переходя к следующей итерации внешнего цикла, как только поставленное условие перестанет быть истинным. Это значит, что числа будут проверяться при значениях переменных ($i,$j) = (1,1), (2,1), (2,2), (3,1), (3,2), (3,3), (4,1) ИТ.Д.

Даже если самый внутренний блок имеет метку, действие операторов last, next и redo без необязательного параметра (метки) все равно распространяется на него. Кроме того, с помощью меток нельзя переходить в блок — можно только выходить из него. Операторы last, next или redo должны находиться внутри блока.



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


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

выражение if удравляющее_выражение;

В данном случае управляющее_выражение

проверяется на истинность (по тем же правилам, что и всегда). Если оно истинно, то вычисляется выражение. Это приблизительно эквивалентно конструкции

if (управляющее_выражение) { выражение;

}

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

LINE: while (<STDIN>) (

last LINE if /"From: /;

}

Видите, насколько проще такая запись? Более того, вы даже можете прочитать ее на нормальном языке: "последняя строка, если она начинается со слова From".

Кроме описанной выше, существуют и другие формы с модификаторами:

выраж2

unless выраж!; # как unless (выражД) ( выраж2; ) выраж2 while выраж!; # как while (выраж!) ( выраж2; } выраж2 until выраж!;

# как until (выраж!) { выраж2; }

Во всех этих формах сначала вычисляется выраж!, а затем в зависимости от результата что-то делается или не делается с выраж2.

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

chomp($n = <STDIN>) ;

$i = 1; # исходное предположение $i *= 2 until $i > $n; # вычислять, пока не будет найдена

Как видите, мы опять добиваемся ясности и убираем лишнее. Эти формы нельзя вкладывать одну в другую: нельзя сказать выражЗ while выраж2

if выраж!. Это объясняется тем, что форма выраж2 if выраж! — уже не выражение, а полноценный оператор; после оператора модификатор ставить нельзя.



Модуль CGI.pm


Начиная с версии 5.004, в состав стандартного дистрибутива Perl вклю-чается модуль CGI.pm, который все знает и все умеет*.

Зтот модуль, который написал Линкольн Штейн, автор хорошо извест-ной книги How to Setup and Maintain Your Web Site,

превращает процедуру создания CGI-программ на Perl в легкую прогулку. Как и сам Perl, CGI.pm является платформо-независимым, позтому его можно использовать прак-тически с любой ОС, от UNIX и Linux до VMS; он работает даже в таких системах, как Windows и MacOS.

* Если у вас инсталлирована одна из более ранних версии Perl (но как минимум 5.001) и вы еще не собрались переходить на новую, просто получите CGI.pm из CPAN.

Если CGI.pm уже инсталлирован у вас в системо, вы можете прочесть его полную документацию, воспользовавшись любым из способов, которые вы используете для чтения man-страниц Perl, например с помощью команд тап(1) или perldoc(l)

либо обратившись к HTML-варианту документации. Если ничего не получается, прочтите файл CGI.pm'. документация на модуль встроена в сам модуль, представленими в простом формате pod *.

Разрабатывая CGI-программы, держите зкземпляр man-страницы модуля CGI.pm под рукой. Она не только содержит описание функций зтого модуля, но и загружается вместе со всевозможными примерами и советами.



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


Назначение языка Perl — помочь программисту в выполнении рутинных задач, которые для shell слишком трудны или плохо переносимы, а также чересчур заумны, одноразовы или сложны для кодирования на С или ином используемом в UNIX языке.

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

Как и любой язык, Perl может быть языком "только_для_написания" программ, которые потом будет невозможно прочитать. Однако при правильном подходе вы можете избежать этого весьма распространенного недостатка. Да, иногда Perl-текст выглядит для непосвященных как случайный набор символов, но умудренный опытом Perl-программист знает, что у этого набора есть контрольная сумма и каждый его символ имеет свое предназначение. Если вы будете следовать указаниям, приведенным в нашей книге, ваши программы будут легкими для чтения и простыми для сопровождения — но, вероятно, не выиграют никаких "состязаний по заумности".



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


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

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

unless (open (DATAPLACE,">/tmp/dataplace") ) (

print "Sorry, I couldn't create /tmp/dataplace\n";

} else {

# остальная часть программы }

Но это очень объемная задача, и встречается она достаточно часто, поэтому в Perl для нее предусмотрено специальное сокращение. Функция die получает список, который может быть заключен в круглые скобки, выводит этот список (как это делает print) на стандартное устройство вывода ошибок, а затем завершает Perl-процесс (тот, который выполняет Perl-программу) с ненулевым кодом выхода (который, как правило, означает, что произошло нечто необычное**). Используя эту функцию, приведенный выше код можно переписать так:

unless (open DATAPLACE,">/tmp/dataplace") f

die "Sorry, I couldn't create /tmp/dataplace\n";

}

* остальная часть программы

Можно пойти еще дальше. Вспомним, что для сокращения записи можно использовать операцию | (логическое ИЛИ):

open(DATAPLACE,">/tmp/dataplace") I I

die "Sorry, I couldn't create /tmp/dataplace\n";

Таким образом, die выполняется только в том случае, если значение, получаемое в результате выполнения функции open, — "ложь". Читать это нужно так: "открыть этот файл или умереть!" Это простой способ запомнить, какую логическую операцию использовать — И либо ИЛИ.

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


** Фактически die просто генерирует исключение, но поскольку мы не показываем вам, как обрабатывать исключения, она ведет себя так, как здесь написано. Подробности см. в главе 3 книги Programming Perl (функция eval) или на man-странице perlfunc{\).

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

die "you gravy-sucking pigs";

выводит файл и номер строки, а

die "you gravy-sucking pigs\n";

не выводит.

Еще одна удобная штука внутри die-строк — переменная $!, которая содержит строку с описанием самой последней ошибки операционной системы. Используется она так:

open (LOG, "”logfile") || die "cannot append: $!";

Например, в результате может быть выдано сообщение " cannot append:

Permission denied".

Имеется также функция "вызова при закрытии", которую большинство пользователей знают как warn. Она делает все, что делает die, только "не умирает". Используйте ее для выдачи сообщений об ошибках на стандартный вывод:

open(LOG,"”log") 11 warn "discarding logfile output\n";


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


В нашей книге используются следующие обозначения:

Курсив

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

Моноширинный шрифт

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

Моноширинный жирный

используется в примерах для выделения данных, которые вводятся поль-ювателем с терминала.

Моноширинный курсив

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

Сноски

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



Образцы


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

Образцы, обозначающие один символ

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

Следующий из самых известных символов сопоставления — точка ("."). Точка обозначает любой одиночный символ, кроме символа новой строки (\п). Например, образцу /а. / соответствует любая двухбуквенная последовательность, которая начинается с буквы а и не является последовательностью "а\п".

Класс символов

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

/[abode]

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

/[aeiouAEIQU]

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

[0123456789] # обозначает любую цифру

[0-9] # то же самое

[0-9\-] # обозначает цифры 0-9 или знак минус

[a-z0-9] # обозначает любую строчную букву или цифру

[a-zA-ZO-9_] # обозначает любую букву, цифру или знак подчеркивания

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


["0-9]

# обозначает любой нецифровой символ

["aeiouAElOU] # обозначает любую негласную букву

["\"]

# обозначает любой символ, кроме символа "

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

Таблица 7.1. Предопределенные классы символов

Конструкция Эквивалентный класс Конструкция с отрицанием Эквивалентный класс с отрицанием
\d (цифра)

\w (обычный символ)

\s (пробельный символ)
[0-9] [a-zA-ZO-9] [ \r\t\n\f] \d (нецифровые символы)

\w (специальные символы)

\s (непробельный символ)
^0-9] [^a-zA-ZO-9] [" \r\t\n\f]
Образцу \d соответствует одна цифра. Образцу \w формально соответствует один обычный символ, но на самом деле ему соответствует любой символ, который допустим в именах переменных Perl. Образцу \s соответствует один пробельный символ. К пробельным символам относятся пробел, возврат каретки (редко используемый в UNIX), символ табуляции, символы перехода на новую строку и на новую страницу. Варианты конструкций с 'использованием прописных букв соответствуют дополнениям (отрицаниям) этих классов. Так, \w обозначает один специальный символ, \s — один символ, который не является пробельным (т.е. является буквой, знаком препинания, управляющим символом и т.д.), a \D — один нецифровой символ.

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

[\da-fA-F] # соответствует одной шестнадцатеричной цифре



Образцы, обозначающие группу символов



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



Последовательность



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

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





Множители



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

Есть еще два образца, работающих подобным образом: знак "плюс" (+), который обозначает один или более экземпляров стоящего непосредственно перед ним символа, и вопросительный знак (?), который обозначает ни одного или один экземпляр стоящего непосредственно перед ним символа. Например, регулярное выражение /fo+ba?r/ обозначает символ f, за которым следует один или более символов о, затем символ Ь, затем ни одного или один символ а и, наконец, символ г.

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

$_ = "fred xxxxxxxxxx barney";

s/x+/boom/;

всегда заменяет словом boom все символы х (что в результате дает fred boom barney), а не только один или два, несмотря на то, что более короткий набор иксов соответствовал бы этому же регулярному выражению.

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

Общий множитель состоит из пары фигурных скобок, между которыми заключены одно-два числа, например /х{5,10}. Необходимо найти символ, стоящий непосредственно перед скобками (в данном случае это буква х), повторяющийся указанное число раз (в рассматриваемом случае — от пяти до десяти)*.

Если второе число не указано (например, /х {5, } /), это означает "столько или больше" (в данном случае пять и более), а если выпущена и запятая (например, /х{5}/), это означает "ровно столько" (в данном случае пять символов х). Чтобы получить пять или менее символов х, нужно перед запятой поставить нуль: /х {0, 5} /.



Так, регулярное выражение /а. {5} b/ соответствует букве а, отделенной от буквы b любыми пятью символами, кроме символов новой строки, и все это может быть в любом месте строки. (Вспомните, что точка соответствует любому символу, кроме символа новой строки, а нам здесь нужно пять таких символов.) Эти пять символов не обязательно должны быть одинаковыми. (В следующем разделе мы увидим, как заставить их быть одинаковыми.)

Можно было бы вполне обойтись без *, + и ?, потому что эти образцы полностью эквивалентны образцам {0,},(!,} и {0,1}, но проще ввести один эквивалентный знак препинания, к тому же это более привычно.

Если в одном выражении используются два множителя, то "правило прожорливости" дополняется правилом "чем левее, тем прожорливее". Например:



$_ = "а ххх с хххххххх с ххх d";



/a.*c.*d/;

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

Можно заставить любой множитель перестать быть "прожорливым" (т.е. сделать его ленивым), поставив после него вопросительный знак:



$_ = "а ххх с хххххххх с ххх d";



/a.*?c.*d/;

Здесь а. * ? с теперь соответствует минимальному числу символов между а и с, а не максимальному. Это значит, что с образцом совпадает часть

* Конечно, /\d(3}/ соответствует не только трехзначным числам, но и любому числу с количеством знаков больше трех. Чтобы задать именно трехзначное число, нужно использовать фиксирующие точки, которые рассматриваются ниже в разделе "Фиксирующие образцы".

строки до первой буквы с, а не до второй. Такой модификатор можно ставить после любого множителя (?,+,* и {m,n}).

Что, если строка и регулярное выражение несколько изменятся, скажем, так:



$_ ° " а ххх се хххххххх ci xxx d";

/a.*ce.*d/;

Символы .* в этом случае соответствуют максимально возможному числу символов, стоящих до следующей буквы с, но очередной символ регулярного выражения (е) не совпадает с очередным символом строки (i). В этом случае мы получаем автоматический поиск с возвратом: поиск начинается сначала и завершается остановкой в некоторой позиции до выбранной на первом этапе (в нашем случае — в позиции предыдущей с, рядом с е)*. Сложное регулярное выражение может включать множество уровней поиска с возвратом, в результате чего время выполнения значительно увеличивается. В данном случае превращение множителя в "ленивый" (с помощью вопросительного знака) упрощает задачу, которую должен выполнить Perl, поэтому рекомендуем хорошо изучить этот метод.



Круглые скобки как способ запоминания



Следующая групповая операция — пара круглых скобок, в которую заключается часть образца. При совпадении с образцом никаких изменений не происходит, просто совпавшая часть строки запоминается, и к ней можно впоследствии обращаться. Например, (а) продолжает соответствовать букве а, а ([a-z] ) — любой строчной букве.

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

/fred(.)barney\l/;

соответствует строке, состоящей из слова fred, любого символа, кроме символа новой строки, слова barney и еще одного такого же символа. Таким образом, данному образцу соответствует последовательность символов fredxbarneyx, a не fredxbarneyy. Сравните это с

/fred.barney./;

где два обозначенных точками символа могут быть одинаковыми или разными; роли это не играет.

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

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



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

/a(.)b(.)c\2d\l/;

обозначает а, какой-то символ (назовем его #1), b, еще один символ (назовем его #2), с, символ #2, d и символ #1. Таким образом, этот образец соответствует, в частности, строке axbycydx.

Запоминаемая часть может состоять не только из одного символа. Например,

/а(.*)Ь\1с/;

обозначает а, любое количество символов (даже нуль), b, ту же последовательность символов и, наконец, с. Следовательно, этот образец совпадет со строкой aFREDbFREDc и даже со строкой abc, но не со строкой аХХЬХХХс.



Дизъюнкция



Следующая групповая конструкция — дизъюнкция, т.е. а | b | с. Это значит, что данный образец соответствует только одному из указанных вариантов (в данном случае — а, b или с). Такая конструкция работает даже в том случае, если варианты содержат несколько символов, как в образце /song | blue/, что соответствует либо song, либо blue. (Для односимвольных альтернатив определенно лучше будет использовать класс символов, например, / [ abc ] /.)

Что, если бы мы хотели найти songbird или bluebird? Мы могли бы написать /songbird | bluebird/, но часть bird не хотелось бы указывать дважды. Из такой ситуации есть выход, однако вначале нам следует поговорить о приоритете группирующих образцов, который рассматривается ниже, в разделе "Приоритет".



Фиксирование образцов



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

Первая пара фиксирующих директив требует, чтобы определенная часть символов, соответствующих образцу, была расположена либо на границе слова, либо не на границе слова. Фиксирующая директива \Ь требует, чтобы совпадение с образцом b происходило только на границе слова. Граница слова — это место между символами, которые соответствуют предопределенным классам \w или \w, либо между символами, которые соответствуют классу \w, а также начало или окончание строки. Отметим, что все это больше предназначено для работы с С, а не с английскими словами, но вполне применимо и к словам. Например:



/fred\b/; # соответствует слову fred, но не Frederick /\bmo/; # соответствует словам тое и mole, но не Eimo /\bFred\b/; # соответствует слову Fred, но не Frederick или alFred /\b\+\b/; # соответствует "х+у", но не "++" или " + " /abc/bdef/; # никогда не дает совпадения(границы там быть не может)

Аналогичным образом \в требует, чтобы в указанной точке границы слова не было. Например:

/\bFred\B/; # соответствует "Frederick", но не "Fred Flintstone"

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

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

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

Символ $, как и л, фиксирует образец, но не по началу, а по концу строки. Другими словами, с$ соответствует символу с только в том случае, если он стоит в конце строки*. Знак доллара в любом другом месте образца, вероятно, будет интерпретироваться как представление скалярного значения, поэтому для того, чтобы использовать его в строке буквально, перед ним следует поставить обратную косую.

Поддерживаются и другие фиксирующие точки, включая \А, \2 и упреждающие фиксирующие точки, создаваемые с помощью комбинаций (?=...) и (?!...). Они подробно описаны в главе 2 книги Programming Perl и на man-странице perlre(Y).



Приоритет



Что произойдет, если объединить а | Ь*? Что будет отыскиваться — любое количество символов а или Ь или один символ а и любое количество Ь?

Групповые и фиксированные образцы, как и операции, имеют приоритет. Приоритет образцов (от высшего к низшему) приведен в таблице 7.2.

Таблица 7.2. Приоритет групповых регулярных выражений**



Наименование Обозначение
Круглые скобки Множители Последовательность и фиксация Дизъюнкция ( ) (?: ) ? + * {m,n} ?? +? *? (m,n}? abc л $ \А \Z (?= ) (?! )
* Или прямо перед символом новой строки в конце строки.

** Некоторые из этих символов в нашей книге не описываются. См. книгу Programming Perl или man-страницу perlreii(l).

Согласно этой таблице, специальный символ * имеет более высокий приоритет, чем |. В силу этого /а |Ь*/ интерпретируется как один символ а или любое число символов ь.

Что, если нам понадобится другое — например, "любое число символов а или Ь"? В этом случае нужно просто использовать пару круглых скобок. В нашем примере в скобки нужно заключить ту часть выражения, к которой должна относиться *, т.е. (а|Ь)*. Если вы хотите подчеркнуть, какое выражение вычисляется первым, можно дать избыточные круглые скобки:

а (Ь*).

Изменение приоритета с помощью круглых скобок одновременно активизирует режим запоминания для данного образца, как мы рассказывали выше. То есть эти круглые скобки учитываются, когда вы определяете, соответствует ли какой-то элемент \2, \3 и т.д. Если вы хотите использовать круглые скобки без включения режима запоминания, применяйте форму (?:...), а не (...). Она тоже позволяет указывать множители, но не изменяет значение счетчика подлежащих запоминанию лексем, используя, например, переменную $4 и т.п. Например,/(?: Fred |Wilma) Flintstone/ ничего не записывает в переменную $ 1; здесь просто предполагается группирование.

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

abc* # соответствует ab, abc, abcc, abccc, abcccc, и т.д. (abc)* # соответствует "", ab, abc, abcabc, abcabcabc, и т.д. ^х |у # соответствует х в начале строки или у в любом месте л^x.^y)

# соответствует х или у в начале строки а|be Id # либо а, либо be, либо d (alb)(с Id) # ас, ad, be или bd (song|blue)bird # songbird или bluebird


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


Теперь вы знаете, как открыть дескриптор файла для вывода, уничтожив существующий файл с таким же именем. Предположим, вы хотите удостовериться, что файла с таким именем не существует (чтобы избежать случайного уничтожения своей электронной таблицы или очень важного календаря дней рождений). Если бы вы писали сценарий shell, вы использовали бы для проверки существования файла нечто вроде -е имя_фаила. Аналогичным образом в Perl применяется операция -е $filevar,

которая проверяет факт существования файла, заданного в скалярной переменной $filevar.

Если этот файл существует, результат — "истина"; в противном случае операция дает "ложь"**. Например:

$name = "index.html";

if (-e $name) (

print "I see you already have a file named $name\n";

} else (

print "Perhaps you'd like to make a file called $name\n";

}

* Хотя при наличии модуля File:: Copy этот способ оказывается лишним.

** Это не совсем хорошо, если вы работаете с lock-файлами или если файлы часто появляются и исчезают. В этом случае вам нужно обратиться к функциям sysopen и flock, которые описаны в книге Programming Perl, или изучить примеры, приведенные в главе 19.

Операнд операции -е — любое скалярное выражение, вычисление которого дает некоторую строку, включая строковый литерал. Вот пример, в котором проверяется наличие файлов index-html и index.cgi

в текущем каталоге:

if (-е "index.html" && -е "index.cgi") (

print "You have both styles of index files here.\n";

1

Существуют и другие операции. Например, -r $filevar возвращает значение "истина", если заданный в $filevar

файл существует и может быть прочитан. Операция -w $filevar проверяет возможность записи в файл. В следующем примере файл с заданным пользователем именем проверяется на возможность чтения и записи:

print "where? ";

$filename “ <STDIN>;

chomp $filename; # выбросить этот надоедливый символ новой строки if (-r $filename &S -w $filename) (


# файл существует, я могу читать его и записывать в него

}

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

Таблица 10.1. Операции для проверки файлов и их описание

Обозначение Описание
-r Файл или каталог доступен для чтения
-w Файл или каталог доступен для записи
-X Файл или каталог доступен для выполнения
Файл или каталог принадлежит владельцу
-R Файл или каталог доступен для чтения реальным пользователем, но не "эффективным" пользователем (отличается от -r для программ с установленным битом смены идентификатора пользователя)
-W Файл или каталог доступен для записи реальным пользователем, но не "эффективным" пользователем (отличается от -w для программ с установленным битом смены идентификатора пользователя)
-X Файл или каталог доступен для выполнения реальным пользователем, но не "эффективным" пользователем (отличается от -х для программ с установленным битом смены идентификатора пользователя)
-0 Файл или каталог принадлежит реальному пользователю, но не "эффективному"пользователю (отличается от -о для программ с установленным битом смены идентификатора пользователя)
Обозначение Описание
Файл или каталог существует
-2 Файл существует и имеет нулевой размер (каталоги пустыми не бывают)
-s Файл или каталог существует и имеет ненулевой размер (значение — размер в байтах)
-f Данный элемент — обычный файл
-d Данный элемент — каталог
-1 Данный элемент — символическая ссылка
-S Данный элемент — порт
-P Данный элемент — именованный канал (FIFO-файл)
-b Данный элемент — блок-ориентированный файл (например, монтируемый диск)
Данный элемент — байт-ориентированный файл (например, файл устройства ввода-вывода)
-u У файла или каталога установлен идентификатор пользо
вателя
-g У файла или каталога установлен идентификатор группы
-k У файла или каталога установлен бит-липучка
-t Выполнение операции isatty() над дескриптором файла дало значение "истина"
-T Файл — текстовый
-B Файл — двоичный
-M Время с момента последнего изменения (в днях)
-A Время с момента последнего доступа (в днях)
-C Время с момента последнего изменения индексного дескриптора (в днях)
<


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

Операция -s возвращает значение "истина", если файл непустой, но это значение особого вида. Это длина файла в байтах, которая интерпретируется как "истина" при ненулевом значении.

Операции -м, -а и -с (да-да, в верхнем регистре) возвращают количество дней соответственно с момента последнего изменения файла, доступа к нему и изменения его индексного дескриптора*. (Индексный дескриптор содержит всю информацию о файле; подробности см. на man-странице, посвященной системному вызову stat.) Возвращаемое значение — десятичное число,

* Эти значения определяются относительно времени запуска программы, занесенного в системном формате времени в переменную $ "т.

Если запрашиваемое значение относится к событию, которое произошло после начала работы программы, оно может быть отрицательным.

соответствующее прошедшему времени с точностью до 1 секунды: 36 часов возвращается как 1,5 дня. Если при отборе файлов будет выполнено сравнение этого показателя с целым числом (например, с 3), то вы получите только те файлы, которые были изменены ровно столько дней назад, ни секундой раньше и ни секундой позже. Это значит, что для получения всех файлов, значение определенного показателя для которых находится в диапазоне от трех до четырех дней, вам, вероятно, нужно будет использовать операцию сравнения диапазонов*, а не операцию сравнения значений.

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

if (-х SOMEFILE) (

# файл, открытый как SOMEFILE, доступен для выполнения

}

Если имя или дескриптор файла не указаны (т.е. даны только операции

*г или -s), то по умолчанию в качестве операнда берется файл, указанный в переменной $_ (опять эта переменная!). Так, чтобы проверить список имен файлов и установить, какие из них доступны для чтения, нужно просто-напросто написать следующее:

foreach (@some_list_of_filenames) (

print "5_ is readable\n" if -r; # то же, что и -г $_ >


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


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

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

if (это) ( то; } # один способ то if это; # другой способ

Вот третья (хотите верьте, хотите нет — но есть и другие):

это && то;

Почему она работает? Разве это не операция "логическое И"? Давайте посмотрим, что происходит, когда это принимает значения "истина" или "ложь".

• Если это — истина, то значение всего выражения все равно не известно, потому что оно зависит от значения элемента то. Поэтому нужно вычислить то.

• Если это — ложь, то все выражение будет ложным и нет никакого смысла смотреть на то. Поскольку вычислять то смысла нет, его можно пропустить.

Как раз это и делает Perl — вычисляет то лишь в том случае, если это истинно, что делает данную форму эквивалентной двум предыдущим.

Аналогичным образом "логическое ИЛИ" работает, как оператор unless (или модификатор unless). Вы можете заменить

unless (это) ( то; )

на

это 11 то;

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

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



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


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

Присваивание

Вероятно, самая важная операция, проводимая над массивами -— операция присваивания, посредством которой переменной-массиву присваивается значение. Эта операция, как и скалярная операция присваивания, обозначается знаком равенства. Perl определяет тип присваивания (скалярное или для массива), анализируя, какой переменной присваивается значение — скалярной или массиву. Например:

@fred = (1,2,3); # массив fred получает трехэлементное литеральное значение Qbarney = @fred; # теперь оно копируется в @barney

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

@huh =1; #1 автоматически становится списком (1)

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

@fred = qw(one two);

@barney = (4,5,@fred,6,7) ; # @barney превращается в (4,5,"one","two",6,7) @barney = (8,@barney); # в начале списка элементов Qbarney ставится 8

# и "last" в конце. Qbarney = (@barney,"last") ; # @barney превращается в

# (8,4,5,"one","two",6,7,"last")

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

Если списочный литерал содержит только ссылки на переменные (а не выражения), то этот литерал также можно рассматривать как переменную. Другими словами, такой списочный литерал можно использовать в левой части операции присваивания. Каждая скалярная переменная в списочном литерале принимает соответствующее значение из списка, стоящего в правой части операции присваивания. Например:


($а,$Ь,$с) = (1,2,3); # присвоить 1 переменной $а, 2 — переменной $Ь,

# 3 — переменной $с ($a,$b) = ($b,$a); # поменять местами $а и $Ь ($d,@fred) = ($a,$b,$c) # присвоить $d значение $а, a @fred — значение ($Ь,$с) ($e,@fred) = @fred; # переместить первый элемент массива @fred

# в переменную $е. В результате @fred = ($с),

# а $е = $Ь

Если число присваиваемых элементов не соответствует числу переменных, то лишние значения (стоящие справа от знака равенства) просто отбрасываются, а все лишние переменные (слева от знака равенства) получают значение undef.

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

Если переменная-массив присваивается скалярной переменной, то присваиваемое число является размером

массива, например:

@fred = (4,5,6); # инициализация массива @fred $а = @fred; # $а получает значение 3, текущий размер массива @fred

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

* Хотя ссылка на список и может быть элементом списка, на самом деле это не означает использование списка в качестве элемента другого списка. Впрочем, работает это почти так же, что позволяет создавать многомерные массивы. См. главу 4 книги Programming Perl и man-страницу perllol(

I).

массива в скалярном контексте.) Например, чтобы получить число на единицу меньшее, чем размер массива, можно использовать @fred-l, так как скалярная операция вычитания требует наличия скаляров в обеих частях. Обратите внимание на следующий пример:

$а = @fred; # переменной $а присваивается размер массива @fred ($а) = @fred; # переменной $а присваивается первый элемент @fred

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



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

@fred = (Qbarney = (2,3,4)); # @fred и @barney получают значения (2,3,4) @fred = @barney = (2,3,4); # то же самое



Обращение к элементам массива



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

Элементы массива нумеруются последовательными целыми числами с шагом 1, начиная с нуля*. Первый элемент массива @fred обозначается как $fred[0]. Обратите внимание: при ссылке на элемент вместо знака @ в имени массива используется знак $ . Это объясняется тем, что обращение к элементу массива идентифицирует скалярную переменную (часть массива), которой в результате может быть присвоено значение или которая используется в выражении, например:

@fred = (7,8,9);

$b = $fred[0]; # присвоить $Ь значение 7 (первый элемент массива @fred) $fred[0] = 5; # теперь @fred = (5,8,9)

Точно так же можно обращаться к другим элементам:

$с = $fred[l]; # присвоить $с значение 8

$fred[2]++; # инкрементировать третий элемент массива @fred

$fred[l] +=4; # прибавить 4 ко второму элементу

($fred[0],$fred[l]) = ($fred[l],$fred[0]) ;

# поменять местами первые два элемента

* Значение индекса первого элемента можно изменить на что-нибудь другое (например, на "1"). Это, однако, повлечет за собой очень серьезные последствия: может запутать тех, кто будет сопровождать ваш код, и повредить программы, которые вы заимствуете у других. По этой причине настоятельно рекомендуем считать такую возможность отсутствующей.

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



@fred[0,l] * то же, что и ($fred[0],$fred[l]) @fred[0,l] = @fred[l,0] # поменять местами первые два элемента @fred[0,l,2] = @fred[l,l,l] # сделать все три элемента такими, как второй @fred[l,2] =

(9,10); # заменить последние два значения на 9 и 10

Обратите внимание: в этом срезе используется не $, а @. Это вызвано тем, что вы создаете переменную-массив (выбирая для этого часть массива), а не скалярную переменную (обращаясь к одному элементу массива).

Срезы работают также с литеральными списками и вообще с любой функцией, которая возвращает список:

@who = (qw(fred barney betty wilma))[2,31;

# как @х = qw(fred barney betty wilma); @who = @x[2,3]

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

@fred ” (7,8,9) ;

$а = 2;

$b " $fred[$a); f как $fred[2], или 9

$с = $fred[$a-l]; # $c получает значение $fred[l], или 8

($с) = (7.8,9)[$а-1]; # то же самое, но с помощью среза

Таким образом, обращение к массивам в Perl-программах может производиться так же, как во многих других языках программирования.

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

Sfred = (7,8,9); f как в предыдущем примере Bbarney =(2,1,0);

Obackfred = @fred[@barney];

# то же, что и @fred[2,l,0], или ($fred[2],$fred[l],$fred[0]), или (9,8,7)

Если обратиться к элементу, находящемуся за пределами массива (т.е. задав индекс меньше нуля или больше индекса последнего элемента), то возвращается значение undef. Например:

@fred = (1,2,3) ;

$barney = $fred[7]; # $barney теперь имеет значение undef

* Это перевод английского термина slice, использованный в данной книге. Более точно суть операции можно было бы выразить термином вырезка,

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



Присваивание значение элементу, находящемуся за пределами текущего массива, автоматически расширяет его (с присваиванием всем промежуточным значениям, если таковые имеются, значения undef). Например:

@fred = (1,2,3);

fred[3] = "hi"; # @fred теперь имеет значение (1,2,3,"hi") $fred[6] = "ho"; # @fred теперь имеет значение

# (1,2,3,"hi",undef,undef,"ho")

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

Для получения значения индекса последнего элемента массива @fred можно использовать операцию $#fred. Можно даже задать это значение, чтобы изменить размер массива @fred, но это, как правило, не нужно, потому что размер массива увеличивается и уменьшается автоматически.

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

@fred = ("fred", "wilma", "pebbles", "dino");

print $fred(-l]; # выводит "dino" print $#fred; # выводит 3 print $fred[$#fred]; # выводит "dino"



Функции push и pop



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

push(@mylist,$newvalue); # означает Omylist =• (@mylist,$newvalue) $oldvalue = pop($mylist); # удаляет последний элемент из @mylist

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

Функция push также принимает список значений, подлежащих помещению в стек. Эти значения вводятся в конец списка. Например:

@mylist = (1,2,3);



push(@mylist,4,5,6) ; # @mylist = (1,2,3,4,5,6)

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



Функции shift и unshift



Функции push и pop действуют в "правой" части списка (части со старшими индексами). Функции unshift и shift выполняют соответствующие действия в "левой" части списка (части с младшими индексами). Вот несколько примеров:

unshift(@fred,$a); # соответствует Bfred = ($a,@fred);

unshift (@fred,$a,$b,$c); # соответствует @fred = ($а,$b,$c,@fred);

$х = shift(@fred); # соответствует ($x,@fred) = @fred;

# с реальными значениями @fred = (5,6,7) ;

unshift(@fred,2,3,4); # @fred теперь имеет значение (2,3,4,5,6,7) $х = shift(@fred) ;

# $х получает значение 2, @fred теперь имеет значение (3,4,5,6,7)

Как и функция pop, функция shift, если в нее ввести пустую перемен-ную-массив, возвращает значение undef.



Функция reverse



Функция reverse изменяет порядок следования элементов аргумента на противоположный и возвращает список-результат. Например:

@а = (7,8,9) ;

@b = reverse(@a); t присваивает @Ь значение (9,8,7) @b == reverse (7,8,9); # делает то же самое

Обратите внимание: список-аргумент не изменяется, так как функция reverse работает с копией. Если вы хотите изменить порядок элементов "на месте", список-аргумент следует затем присвоить той же переменной:

@Ь = reverse (@b); t присвоить массиву @Ь его же значения,

# но расположить его элементы в обратном порядке



Функция sort



Функция sort сортирует аргументы так, как будто это отдельные строки, в порядке возрастания их кодов ASCII. Она возвращает отсортированный список, не изменяя оригинал. Например:

@х ° sort("small","medium","large") ;

# @х получает значение "large", "medium", "small" @у = (1,2,4,8,16,32,64) ;

@у = sort (@y); # @у получает значение 1, 16, 2, 32, 4, 64, 8

Отметим, что сортировка чисел производится не по их числовым значениям, а по их строковым представлениям (1,16, 2, 32 и т.д.). Изучив главу 15, вы научитесь выполнять сортировку по числовым значениям, по убыванию, по третьему символу строки и вообще каким угодно методом.



Функция chomp



Функция chomp работает не только со скалярной переменной, но и с массивом. У каждого элемента массива удаляется последний пробельный символ. Это удобно, когда вы, прочитав несколько строк как список отдельных элементов массива, хотите одновременно убрать из всех строк символы новой строки. Например:

@stuff = ("hello\n","world\n","happy days") ;

chomp(@stuff); # Sstuff теперь имеет значение ("hello","world","happy day")


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


Мы уже говорили о простейшей форме операции замены: s/ регуляр-ное_выражение/новая_строка/. Пора рассмотреть несколько разновидностей этой операции.

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

$_ = "foot fool buffoon";

s/foo/bar/g; # $_ теперь содержит "bart barl bufbarn"

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

$_ = "hello, world";

$new = "goodbye";

s/hello/$new/; # заменяет hello на goodbye

Символы сопоставления (метасимволы) в регулярном выражении позволяют выполнять сопоставление с образцом, а не просто с символами, трактуемыми буквально:

$_ = "this is a test";

s/(\w+()/<$l>/g; # $_ теперь содержит "<this> <is> <a> <test>"

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

Суффикс i (перед буквой g или после нее, если она есть) заставляет используемое в операции замены регулярное выражение игнорировать регистр, как и аналогичная опция в ранее рассмотренной нами операции сопоставления.

* О влиянии этих переменных на производительность рассказывается в книге Mastering Regular Expressions (издательство O'Reilly).

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

s#fred#barney#; # заменить fred на barney, как в s/fred/barney/

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

$which = "this is a test";

$which =~ s/test/quiz/; # $which теперь содержит "this is a quiz"

$someplace[$here] =~ s/left/right/; # заменить элемент массива

$d{"t") =~ s/^/x /; # поставить "х " перед элементом массива



Оператор for


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

for ( начальное_выражение; проверочное_выражение ,• возобновляющее выражение ) (

оператор_1;

оператор_2;

оператор_3;

1

Если преобразовать этот оператор в те формы, которые мы рассмотрели раньше, то он станет таким:

начальное выражение;

while (проверочное выражение) {

оператор_1;

оператор_2;

оператор_3;

возобяовляющее_выраи:ение;

}

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

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

for ($i = 1; $i <- 10; $i++) ( print "$i ";

)

Сначала переменная $i устанавливается в 1. Затем эта переменная сравнивается с числом 10. Поскольку она меньше или равна 10, то выполняется тело цикла (один оператор print), а затем вычисляется возобновляющее выражение ($i++), в результате чего значение $i изменяется на 2. Поскольку эта переменная все еще меньше или равна 10, процесс повторяется до тех пор, пока в последней итерации значение 10 в $i не изменится на 11. Поскольку переменная $i уже не меньше и не равна 10, цикл завершается (при этом $i становится равным 11).



Оператор foreach


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

foreach

$i (@список) {

оператор_1;

оператор_2;

оператор_3;

}

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

Вот пример использования оператора foreach:

@а = (1,2,3,4,5);

foreach $b (reverse @a) { print $b;

1

Эта программа выводит на экран 54321. Отметим, что список, используемый в операторе foreach, может быть произвольным списочным литералом, а не просто переменной-массивом. (Это справедливо для всех конструкций Perl, которым требуется список.)

Имя скалярной переменной можно опустить. В этом случае Perl будет действовать так, как будто вы указали имя переменной $_. Вы увидите, что переменная $_ используется по умолчанию во многих операциях языка Perl, поэтому ее можно рассматривать как неявную временную переменную. (Все операции, в которых по умолчанию используется $_, могут использовать и обычную скалярную переменную.) Например, функция print выводит значение переменной $_, если другие значения не указаны, поэтому следующий пример дает такой же результат, как и предыдущий:

@а = (1,2,3,4,5);

foreach (reverse @a)

{

print ;

}

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

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

@а = (3,5,7,9) ;

foreach $one (@a) { $one *= 3;

> # @а теперь равно (9,15,21,27)

Обратите внимание на то, что изменение переменной $опе привело к изменению каждого элемента массива @а.



Оператор if/unless


Следующей по сложности управляющей структурой является оператор if. Эта конструкция состоит из управляющего выражения (проверяемого на истинность) и блока. В ней также может быть часть, начинающаяся оператором else, за которой следует еще один блок операторов. Другими словами, все это выглядит так:

if (выражение) (

оператор_1

оператор_2

оператор_3 }

else {

оператор_1

оператор_2

оператор_3 }

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

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

Что же такое "истина" и "ложь"? В Perl эти правила несколько странноваты, но, тем не менее, дают ожидаемые результаты. Управляющее выражение вычисляется как строковая

величина в скалярном контексте (если это уже строка, ничего не изменяется, а если это число, то оно преобразуется в строку*). Если данная строка либо пуста (т.е. имеет нулевую длину), либо состоит из одного символа "О" (цифры нуль), то значение выражения — "ложь". Все остальное автоматически дает значение "истина". Почему же в Perl столь забавные правила? А потому, что это облегчает условный переход не только по нулю (в противоположность ненулевому числу), но и по пустой (в противоположность непустой) строке, причем без необходимости создания двух версий интерпретируемых значений "истина" и "ложь". Вот несколько примеров интерпретации этих значений.

О # преобразуется в "О", поэтому "ложь"

1-1 # дает в результате 0, затем преобразуется в "О", поэтому "ложь"

1 # преобразуется в "I", поэтому "истина"

"" # пустая строка, поэтому "ложь"

"1" # не "" или "О", поэтому "истина"


"00" # не "" или "О", поэтому "истина" (это странно, поэтому будьте настороже)

"0.000" # "истина" — будьте внимательны, по той же причине

undef # дает в результате "", поэтому "ложь"

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

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

print "how old are you? ";

$a = <STDIN>;

chomp($a) ;

if ($a < 18) (

print "So, you're not old enough to vote, eh?\n";

( else (

print "Old enough! Cool! So go vote! \n";

$voter++; # count the voters for later )

Блок else можно опустить, оставив только часть, касающуюся then:

print "how old are you? ";

$a - <STDIN>;

chomp($a) ;

if ($a < 18) (

print "So, you're not old enough to vote, eh?\n";

}

Иногда бывает удобно часть "then" опустить и оставить только else, потому что более естественно сказать "сделайте то, если это ложь", нежели "сделайте то, если это — не истина". В Perl этот вопрос решается с помощью оператора unless:

print "how old are you? ";

$a = <STDIN>;

chomp ($a) ;

unless ($a < 18) (

print "Old enough! Cool! So go vote!\n";

$voter++;

>

Заменить if на unless — это все равно что сказать "Если управляющее выражение ложно, сделать..." (Оператор unless может содержать блок else, как и оператор if.)

Если у вас больше двух возможных вариантов, введите в оператор if ветвь elsif, например:

if (выражение один) {

оператор_1_при_истине_ один;

оператор_2 _при_истине_один;

оператор_ 3_при_истине_ один ;

} elsif (выражение_два) {

оператор_1_при_истине_два

оператор_2_при_истине_два

олератор_3_при_истине_два }

elsif (варажение_три) (

оператор_1_при_истине_три

оператор_2_при_истине_три

оператор _ 3_при_истине_ три

} else ( -

оператор__ 1_при_всеи_ложных;

оператор_2_при_в

сех_ложных/ оператор_3_при_всех_ложных;

}

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


Оператор last


Выполняя некоторые из предыдущих упражнений, вы, возможно, иногда думали: "Если бы в моем распоряжении был С-оператор break, все было бы нормально". Даже если такая мысль и не приходила вам в голову — все равно позвольте мне рассказать о Perl-операторе для преждевременного выхода из цикла last.

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

while (что-то) ( что-то;

ЧТО-ТО! ЧТО-ТО;

if (условие) {

что-то или другое;

что-то или другое;

last; # выход из цикла while }

еще что-то/ еще что-то;

}

# last передает управление в эту точку программы

* Конструкция типа do (} while/until с точки зрения операторов next, last и redo циклом не считается.

если условие истинно, то выполняются строки что-то_или_другое, после чего оператор last завершает цикл while.

Оператор last учитывает только блоки, образующие цикл, а не блоки, необходимые для построения определенных синтаксических конструкций. Это значит, что блоки для операторов if и else, равно как и для конструкций do {} while/until, "не считаются"; учитываются только блоки, которые образуют циклы for, foreach, while, until и "голые" блоки. ("Голый" блок — это блок, не являющийся частью более крупной конструкции, например цикла, подпрограммы, оператора if/then/else.)

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

From: merlyn@stonehenge.com (Randal L. Schwartz)

To: stevet@ora.com

Date: Ol-DEC-96 08:16:24 PM -0700

Subject: A sample mail message

Here's the body of the mail message. And here is some more.

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

Это можно было бы сделать так:

while (<STDIN>) ( # читать входные строки

if (/^From: /) ( # начинается ли строка со слова From:? Если да... if (/merlyn/) ( # то сообщение от merlyn!

print "Email from Randal! It's about time!\n";

}

last; # дальше искать строки From: не нужно, поэтому выйти } # конец цикла "if from:" if (/^5/) ( # пустая строка?

last; # если да, больше строки не проверять > } # конец цикла while

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



Оператор next


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

while (что-то) ( первая_часть;

первая_ часть;

первая_часть;

if (условие) {

какая-то часть;

какая-то_часты

next;

)

другая_часть;

другая_ ча

сть ;

# next передает управление в эту точку программы )

Если условие истинно, то выполняется какая-то_часть, а другая_часть пропускается.

Как и при использовании оператора last, блок оператора if не считается блоком, образующим цикл.



Оператор redo


Третий способ передачи управления в блоке цикла — оператор redo. Эта конструкция обеспечивает переход в начало текущего блока (без повторного вычисления контрольного выражения):

while (условие) {

# redo передает управление в эту точку программы

ЧТО-ТО;

ЧТО-ТО;

ЧТО-ТО;

if {условие) {

какие-то действия;

какие-то действия;

redo;

еще_что-то;

еще_что-то;

еще что-то;

}

*

Если в данном цикле есть оператор continue, который мы не рассматривали, next переходит в начало блока continue, а не в конец блока цикла. Это практически одно и то же.

Блок if здесь тоже не учитывается; считаются только циклообразующие блоки.

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

( -...,. .

начальные_действия;

начальные_действия;

на чалы1ые_действия;

if (условие) ( last;

} последующк1е_действия;

последух1щие_действия;

последухщие_действия;

redo;

}

Такая схема годится для while-подобного цикла, некоторая часть которого должна выполняться как инициализационная перед первой проверкой. (В разделе "Модификаторы выражений"

мы покажем, как можно использовать оператор if с меньшим числом знаков препинания.)



Оператор while/until


Ни один язык программирования не был бы полным без какой-нибудь формы организации цикла* (повторяющегося выполнения блока операторов). Perl может организовать цикл с помощью оператора while:

while (выражение) {

оператор_1; '

оператор_2;

оператор_3;

}

Чтобы выполнить оператор while, Perl вычисляет управляющее выражение (в данном примере — выражение). Если полученное значение — "истина" (по принятым в Perl правилам установления истинности), то один раз вычисляется тело оператора while. Это повторяется до тех пор, пока управляющее выражение не станет ложным. Тогда Perl переходит к оператору, следующему после цикла while. Например:

print "how old are you? " ;

$a = <STDIN>;

chomp($a) ;

while ($a > 0) {

print "At one time, you were $a years old.\n";

$a—;

}

Иногда легче сказать "до тех пор, пока что-то не станет истинным", чем "пока не это — истина". Для этого случая у Perl тоже есть ответ. Требуемый эффект дает замена while на until:

until (выражение) { оператор_1;

оператор_2;

оператор_3;

} * Вот почему HTML — не язык программирования.

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

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

Оператор do {} while/until

Оператор while/until, который вы видели в предыдущем разделе, проверяет условие в начале каждого цикла, до входа в него. Если результат проверки условия — "ложь", цикл не будет выполнен вообще.


Иногда возникает необходимость проверять условие не в начале, а в конце цикла. Для этого в Perl есть оператор do {} while, который очень похож на обычный оператор while*, за исключением того, что он проверяет выражение только после однократного выполнения цикла.

do (

оператор_1;

опвратор_2;

оператор_3;

}

while выражение;

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

Как и в обычном цикле while, условие проверки можно инвертировать, заменив do {} while на do {} until. Выражение все равно проверяется в конце цикла, но на обратное условие. В некоторых случаях, особенно сложных, такой способ записи условия представляется более естественным.

$stops = 0;

do (

$stops++;

print "Next stop? " ;

chomp($location = <STDIN>) ;

} until $stops > 5 I I $location eq 'home';

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






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


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

format имя_формата =

строка_полей

значение_один, значение_два, значение_три

строка_полей

значение_один, значение_два

строка_полей

значение_один, значение_два, значение_три

Первая строка содержит зарезервированное слово format, за которым следует имя формата и знак равенства (=). Имя формата выбирается из отдельного пространства имен в соответствии с теми же правилами, что и все прочие имена. Поскольку имена форматов в теле программы никогда не используются (кроме как в строковых значениях), вы спокойно можете брать имена, совпадающие с зарезервированными словами. Как вы увидите в следующем разделе, "Вызов формата", большинство имен форматов будут, вероятно, совпадать с именами дескрипторов файлов (что, конечно, делает их не идентичными зарезервированным словам, и это хорошо).

За первой строкой идет сам шаблон,

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

Определение шаблона содержит ряд строк полей. Каждая строка полей может содержать неизменяемый текст — текст, который будет выведен буквально при вызове формата. Вот пример строки полей с неизменяемым текстом:

Hello, my name is Fred Flintstone.

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


Hello, my name is @“““““ $name

* В текстовых файлах последняя строка должна заканчиваться символом новой строки.

Поледержатель здесь — @“““““. Этот поледержатель задает текстовое поле, которое состоит из 11 символов и выравнивается по левому краю. Подробно поледержатели описаны в разделе "Еще о поледержателях".

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

Hello, my name is @“““““ and I'm @“ years old. $name, $age

Собрав все вместе, мы создадим простой формат для вывода адреса:

format ADDRESSLABEL =

$name

$ address | @<“““““““, @< @““ |

$city, $state, $zip

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

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

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

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


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


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

sub имя {

оператор_1;

оператор_2 ;

оператор 3;

}

Здесь имя — это имя подпрограммы, которое может быть любым именем вроде тех, которые мы давали скалярным переменным, массивам и хешам. Вновь подчеркнем, что эти имена хранятся в другом пространстве имен, поэтому у вас может быть скалярная переменная $fred, массив @fred, хеш %fred, а теперь и подпрограмма fred*.

* Более правильно эту подпрограмму следовало бы назвать sfred, но пользоваться такого рода именами приходится редко.

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

Вот, например, подпрограмма, которая выдает знаменитую фразу:

sub say_hello (

print "hello, world!\n";

}

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

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

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

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

В следующем примере:

sub say_what {

print "hello, $what\n";

}

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



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


Если бы нам нужно было найти в каком-то файле все строки, содержащие строку abc, мы могли бы использовать команду grep:

grep abc somefile >results

В этом случае abc — регулярное выражение, которое команда grep

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

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

if (/abc/) ( print $_;

}

Но что же сверяется с регулярным выражением abc в данном случае? Да наша старая подруга, переменная $_! Если регулярное выражение заключено между косыми (как в этом примере), то переменная $_ сверяется с регулярным выражением. Если значение переменной совпадает с регулярным выражением, операция сопоставления возвращает значение "истина". В противном случае она возвращает "ложь".

В данном примере предполагается, что переменная $_ содержит какую-то строку текста и выводится, если в любом месте этой строки обнаруживается последовательность символов abc (аналогичные действия производит приведенная выше команда grep. Однако в отличие от grep,

которая оперирует всеми строками файла, данный фрагмент Perl-программы просматривает только одну строку). Чтобы обрабатывались все строки, добавьте операцию цикла:

while (о) (

if (/abc/) { print $_;

> )

А что, если мы не знаем, сколько символов b стоит между а и с? То есть что нужно делать, если мы хотим вывести на экран строку только в том случае, если она содержит символ а, за которым следует ни одного или более символов b и символ с? Работая с grep, мы написали бы так:

grep "ab*c" somefile >results

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

while (о) {


if (/ab*c/) ( print $_;

) >

Как и в grep, такая запись обозначает последовательность, содержащую символ а, ни одного или более символов b и символ с.

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

Еще одна простая операция, в которой используются регулярные выражения, — операция замены, посредством которой часть строки, соответствующая регулярному выражению, заменяется другой строкой. Операция замены похожа на команду s UNIX-утилиты sed:

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

s/ab*c/def/;

Переменная (в данном случае $_) сопоставляется с регулярным выражением (ab*c). Если сопоставление оказалось успешным, то соответствующая часть строки отбрасывается и заменяется строкой (def). Если сопоставление неудачно, ничего не происходит.

Позже, в разделе


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


Сценарий shell — это не что иное, как последовательность команд shell, оформленная в виде текстового файла. Этот файл затем превращается в исполняемый путем включения бита исполнения (посредством команды chmod + х имя_файла), после чего по приглашению shell вводится имя файла. Давайте рассмотрим какой-нибудь сценарий shell. Например, сценарий выполнения команды date, а затем команды who можно записать и выполнить так:

% echo date >somescript % echo who ”somescript % cat somescript date

who '

% chmod +x somescript % somescript

[результат выполнения команды date, затем команды who] %

Аналогичным образом Perl-программа — это набор Perl-операторов и определений, записанных в виде файла. Затем включается бит исполнения*, после чего по приглашению shell вводится имя файла. При этом, однако, файл должен иметь признак, указывающий, что это Perl-программа, а не программа shell.

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

#!/usr/bin/perl

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

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


*

Это справедливо для UNIX-систем. Указания о том, как сделать сценарий исполняемым в других системах, вы можете найти в сборнике ответов на часто задаваемые вопросы (FAQ) пп Perl или в документации, приложенной к вашей операционной системе.

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

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

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

Комментарии в Perl похожи на комментарии shell (современные). Комментарием является все, что следует за незаключенным в кавычки знаком # вплоть до конца строки. Многострочных комментариев, как в С, здесь нет.

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

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

Perl, таким образом, работает и как компилятор, и как интерпретатор. С одной стороны, это компилятор, потому что перед выполнением первого оператора программы она полностью считывается и разбирается. С другой стороны. Perl — интерпретатор, потому что никакого объектного кода, занимающего место на диске в ожидании исполнения, в данном случае нет. Другими словами, он сочетает в себе лучшее из компилятора и интерпретатора. Конечно же, было бы просто здорово, если бы выполнялось какое-то кэширование компилированного объектного кода между вызовами, а то и его трансляция в "родной" машинный код. Рабочая версия такого компилятора фактически уже существует, и сейчас планируется, что она войдет в выпуск 5.005. О текущем состоянии дел можно узнать в сборнике FAQ, посвященном Perl.


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


Чтобы связать DBM-базу данных с DBM-массивом, применяется функ-ция dbmopen, которая используется следующим образом:

dbmopen(%ИМЯ МАССИВА, "имя_ОВМ-фа{та", $режим}

Параметр %имя_массива —

зто имя Perl-хеша. (Если в данном хеше уже єсть значення, они выбрасываются.) Хеш соединяется с DBM-базой данных, заданной параметром имя_овм-файла. Она обычно хранится на диске в виде пары файлов с именами имя_ОВМ-файла.сіи и имя_ОВМ-файла.рад.

Параметр $режим — зто число, которое соответствует битам прав доступа к названным двум файлам, если файлы создаются заново. Обычно оно указывается в восьмеричном формате; часто используемое значение 0644 предоставляет право доступа только для чтения всем, кроме владельца, который имеет право на чтение и запись. Если зти файлы существуют, данный параметр не действует. Например:

dbmopen(%FRED, "mydatabase", 0644); # открьггь %FRED на mydatabase

Зтот вызов связывает хеш %fred

с файлами mydatabase. dir и ту database.pag,

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

Функция dbmopen возвращает значение "истина", если базу данных можно открыть или создать; в противном случае возвращается "ложь" — точно так же, как при вызове функции open. Если вы не хотите создавать файлы, используйте вместо параметра $режим значение undef. Например:

dbmopen(%A,"/etc/xx",undef) || die "cannot open DBM /etc/xx";

* Зто, по суги дела, просто особый случай использования общего механизма tie. Если вам понадобится что-нибудь более гибкое, обратитесь к man-страницам AnyDBM_File(3), DB_File(3) н perltie(l).

Если, как в данном случае, (^awibi/etc/xx.dirvi/etc/xx.pagoTKpbnb

нельзя, то вызов dbmopen возвращает значение "ложь" без попытки создать зти файлы.

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

dbmclose(%A);

Как и функция close, dbmclose возвращает значение "ложь", если что-нибудь происходит не так, как надо.