Создание серверных приложений на языке PERL

         

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


if(/abc/) { print $_; }

В примере с регулярным выражением abc сравнивается переменная $_. Этот фрагмент Perl программы рассметривает только одну строку. Для обработки всех строк:

while(<&gt) { if(/abc/) { print $_; } }

Пример:

while(<&gt) { if(/ab*c/) { print $_; } }

Ищется последовательность, содержащая символ a, ни одного или более символа b и символ c.

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

s/ab*c/def/;

Переменная ($_ в данном случае) сопоставляется с рег. выражением и с случае успеха заменяется строкой def.



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


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



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


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

closedir(ETC);



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




Дескрипторы "по умолчанию&quot: STDIN, STDOUT, STDERR.

Открытие декриптора файла на чтение:

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

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

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

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

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

Закрытие декриптора файла:

close(ДЕСКРИПТОР);



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


Используется модуль CGI.pm. Программа, которая запрос: serv.edu.kiae.ru/~gds/cgi-bin/test.cgi?flavor=mint:

#!/usr/bin/perl5 -w use CGI qw(param); print &lt&ltEND_of_Multiline_Text; Content-type: text/html

<HTML> <HEAD> <Hello World!<TITLE> </HEAD> <BODY> <H1&gtGreetings, Terrians!<H1&gt End_of_Multiline_Text

my $favorilte = param("flover"); print "<P&gtYour favorite flavor is $favorite."; print <<All_Done; </BODY> </HTML&gt All_Done



Переименование файлов, содержащих старые пароли.


sub init_words { while(defined($filename = glob("*.secret"))) { open(WORDLIST, $filename) || die "can't open wordlist: $!"; if(-M WORDLIST >= 7.0) { rename($filename, "$filename.old") || die "can't rename $filename to $filename.old: $! "; } while($name = <WORDLIST&gt) { chomp($name); $word = <WORDLIST&gt ; chomp($word); $words{$name} = $word; } close(WORDLIST) || die "could not open wordlist: $!"; } }



Полулокальные переменные. Функция local.


Локальные переменные, созданные при помощи функции local, видны в самой функции и функциях, вызываемых из нее.

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

$value = "original"; tellme(); spoof(); tellme();

sub spoof { local ($value) = "temporary"; tellme(); }

sub tellme { print "Current value is $value\n"; }

Результат выполнения программы:

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



Повышение уровня безопастности.


#!/usr/local/bin/perl -w init_words(); print "What is you name? "; $name = <STDIN&gt ; chomp ($name); if($name =~ /^german\b/i) { print "Hello, German! How good of you to be here!\n"; } else { print "Hello, $name! \n"; print "What is a secret word? "; $guess = <STDIN&gt ; chomp($guess); while(! good_word( $name, $guess )) { print "Wrong, try again. What is a secret word? "; $guess = <STDIN&gt ; chomp($guess); } }

sub good_word { my($somename, $someguess) = @_; $somename =~ s/\W.*//; $somename =~ tr/A-Z/a-z/; if($somename eq "german") { return 1; } elsif(($words{$somename} || "grouhno") eq $someguess) { return 1; } else { return 0; } }

sub init_words { open(WORDLIST, "wordlist") || die "can't open wordlist: $!"; if(-M WORDLIST >= 7.0) { die "Sorry, the wordlist is older than seven days."; } while($name = <WORDLIST&gt) { chomp($name); $word = <WORDLIST&gt ; chomp($word); $words{$name} = $word; } close(WORDLIST) || die "could not open wordlist: $!"; }



Предупреждение о попытке доступа.


#!/usr/local/bin/perl init_words(); print "What is you name? "; $name = <STDIN&gt ; chomp ($name); if($name =~ /^german\b/i) { print "Hello, German! How good of you to be here!\n"; } else { print "Hello, $name! \n"; print "What is a secret word? "; $guess = <STDIN&gt ; chomp($guess); while(! good_word( $name, $guess )) { print "Wrong, try again. What is a secret word? "; $guess = <STDIN&gt ; chomp($guess); } }

sub good_word { my($somename, $someguess) = @_; $somename =~ s/\W.*//; $somename =~ tr/A-Z/a-z/; if($somename eq "german") { return 1; } elsif(($words{$somename} || "grouhno") eq $someguess) { return 1; } else { open MAIL, "|mail gera"; print MAIL "bad news: $somename guessed $someguess\n"; close MAIL; return 0; } }

sub init_words { open(WORDLIST, "wordlist") || die "can't open wordlist: $!"; if(-M WORDLIST >= 7.0) { die "Sorry, the wordlist is older than seven days."; } while($name = <WORDLIST&gt) { chomp($name); $word = <WORDLIST&gt ; chomp($word); $words{$name} = $word; } close(WORDLIST) || die "could not open wordlist: $!"; }



Print - обычный вывод.


print - обычная функция, которая принимает список аргументов, а возвращает значение истина или ложь.

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

Иногда нужно вводить скобки:

print (2+3), "hello!"; # неверно, 5 print ((2+3), "hello!"); # верно, 5hello print 2+3, "hello!"; # верно, 5hello



Приоритет.


Приоритет групповых регулярных выражений.

НаименованиеОбозначение

Круглые скобки ( ) (?: )
Множители ? + * {m, n} ?? +? {m, n}?
Последовательность и фиксация abc ^ $ \A \Z (?= ) (?! )
Дизьюнкция |

Примеры:

abc* # ab, abc, abcc, abccc ... (abc)* # "", ab, abc, abcabc, abcabcabc ... ^x|y # x в начале строки или y в любом месте ^(x|y) # x или y в начале строки a|bc|d # либо a, либо bc, либо d (a|b)(c|d) # ac, ad, bc, или bd (song|blue)bird # songbird или bluebird



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


@fred = (1, 2, 3); @barney = @fred; @huh = 1; #(1)

@fred = qw(one two); @barney = (4, 5, @fred, 6, 7); @barney = (@barney, "last");

($a, $b, $c) = (1, 2, 3); ($a, $b) = ($b, $c); ($d, @fred) = ($a, $b, $c);

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

@fred = (4, 5, 6); $a= @fred;



Программа Hello.


#!/usr/local/bin/perl -w use 5.004; use strict; print "What is you name? "; $main::name = <STDIN&gt ; chomp($main::name); print "Hello, $main::name! \n";



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


Пример:

#!/usr/bin/perl5 -w print <<END_of_Multiline_Text; Content-type: text/html

<HTML> <HEAD> <Hello World!<TITLE> </HEAD> <BODY> <H1>Greetings, Terrians!<H1> </BODY> </HTML&gt

End_of_Multiline_Text



Работа с файлами.


#!/usr/local/bin/perl -w init_words(); print "What is you name? "; $name = <STDIN&gt ; chomp ($name); if($name =~ /^german\b/i) { print "Hello, German! How good of you to be here!\n"; } else { print "Hello, $name! \n"; print "What is a secret word? "; $guess = <STDIN&gt ; chomp($guess); while(! good_word( $name, $guess )) { print "Wrong, try again. What is a secret word? "; $guess = <STDIN&gt ; chomp($guess); } }

sub good_word { my($somename, $someguess) = @_; $somename =~ s/\W.*//; $somename =~ tr/A-Z/a-z/; if($somename eq "german") { return 1; } elsif(($words{$somename} || "grouhno") eq $someguess) { return 1; } else { return 0; } }

sub init_words { open(WORDLIST, "wordlist") || die "can't open wordlist: $!"; while(defined($name = <WORDLIST&gt)) { chomp($name); $word = <WORDLIST&gt ; chomp($word); $words{$name} = $word; } close(WORDLIST) || die "could not close wordlist: $!"; }



Распечатка списка секретных слов.


#!/usr/local/bin/perl -w while(defined($filename = glob("*.secret"))) { open(WORDLIST, $filename) || die "can't open wordlist: $!"; if(-M WORDLIST >= 7.0) { die "Sorry, the wordlist is older than seven days."; } while($name = <WORDLIST&gt) { chomp($name); $word = <WORDLIST&gt ; chomp($word); write; } close(WORDLIST) || die "could not open wordlist: $!"; } format STDOUT = @&lt&lt&lt&lt&lt&lt&lt&lt&lt&lt&lt&lt&lt&lt @&lt&lt&lt&lt&lt&lt&lt&lt @&lt&lt&lt&lt&lt&lt $filename, $name, $word . format STDOUT_TOP = Page @&lt&lt $%

Filename Nname Word ================ ========= ======= .



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


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

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

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

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

В аргументе glob производится интерполяция:

if(-d "/usr/etc") { $where = "/usr/etc"; } else { $where = "/etc"; } @files = <$where/*>



Секретное слово.


#!/usr/local/bin/perl -w print "What is you name? "; $name = <STDIN&gt ; chomp($name); $secretword = "hmm"; if($name eq "German") { print "Hello, German! Good of you to be here!\n"; } else { print "Hello, $name! \n"; print "What is the secret word? "; $guess = <STDIN&gt ; while($guess ne $secretword) { print "Wrong, try again. What is the secret word? "; $guess = <STDIN&gt ; chomp($guess); } }



Скалярные типы данных.


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

Числа.

Строки.

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

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

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

<STDIN&gt как скалярное значение.

Вывод при помощи функции print.

Значение undef.



Сокращение обьема вводимого текста.


В модуле CGI.pm имеются директивы импорта - метки, которые обозначают группы импортируемых функций. В модуле имеются следущие директивы: :cgi, :form, :html2, :html3, :netscape, :shortcuts, :standard, :all.

Пример использования сокращений:

#!/usr/bin/perl5 -w

use CGI qw(:standard); print header(); print start_html("Hello World!"), h1("Hello World!"); my $favorite = param("flavor"); print p("Your favorite flavor is $favorite"); print end_html();



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


Операцию my() можно использовать на внешнем уровне программы, что необходимо при использовании в сочетании с PERL-прагмой.

Если поставить прагму strict в начало программы, то становится небходимо использовать обьявление переменных с помощь операции my().

use strict; my $a; my @b = qw(fred mike igor); ... push @b, qw(betty); @c = sort @b; # mistake

Преимущества:

Программа работает несколько быстрее Легче искать ошибки



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


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



Срезы хешей.


Использование среза хеша позволяет обращаться не к одному его элементу, а одновременно к набору.

$score{"mike"} = 205; $score{"igor"} = 150; $score{"nick"} = 30; # or ($score{"mike"}, $score{"igor"}, $score{"nick"}) = (205, 150, 30); # or @score{"mike", "igor", "nick"} = (205, 150, 30); # срез

Можно сочетать использование среза и интерполяции пременных:

@players = qw(mike igor nick); print "scores are: @score{@players}\n";

Срезы хешей можно использовать для слияния меньшего хеша с большим:

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

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



Ссылки.


В функции popup_menu() квадратные скобки создают ссылку на анонимный массив. Другой способ создания ссылки на массив:

@choises = ('mint', 'cherry', 'mocha'); print p("What flavor: ", popup_menu("flavor",\@choises));

Ссылки работают примерно как ссылки в C. Они указывают на другие значения или переменные. Сылки PERL нельзя приводить. Если область памяти, на которую указывают ссылки, больше не используется, автоматически возвращается в использование.

Методом \@array можно создавать ссылки на именованные массивы, а посредством указания [ list ] - на анонимные массивы.

Методом \%hesh можно создавать ссылки на именованные хеши, а посредством указания { key1, val1, key2, val2, .. } - на анонимные хеши.



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


Используется дескриптор <STDIN&gt.

$a = <STDIN&gt; chomp($a);

или

chomp($a = <STDIN&gt);



Строки.


Строки - последовательности символов. Символ - 8-битовое значение из 256-символьного набора (при этом сивол NUL ничего особенного , в отличии от C, собой не представляет).



Строки в двойных кавычках.


Обратная косая черта может задавить определенные управляющие символы и вообще любой символ в 8 и 16 - ричном формате:

"hello world\n" "new \177" # new, пробел и символ удаления "coke\tsprite"

Управляющие последовательности.

КонструкцияЗначение

\nПереход на новую строку
\rВозврат к началу строки
\tТабуляция
\fПереход к новой странице
\bBackspace
\aСигнал
\eEscape
\007Восьмеричное ASCII-значение
\x7fШестнадцатеричное ASCII-значение
\cCУправляющий символ (здесь Ctrl+C)
\\Обратная косая
\"Двойная кавычка
\lПеревод следующей буквы в нижний регистр
\LПеревод в нижний регистр всех последующих букв до \E
\uПеревод следующей буквы в верхний регистр
\UПеревод в верхний регистр всех последующих букв до \E
\QЗаключить в обратные косые все небуквенные и все нецифровые символы до \E
\EОтменить действие последовательности \L, \U, \Q



Строки в одинарных кавычках.


Для экранирования одинарной кавычки используется обратная косая черта. Для вставки \ - ее нужно заэкранировать. Примеры:

'hello' 'don\'t' '' 'silly\\me' 'hello there'



Управляющие структуры.


Оператор last.

Оператор next.

Оператор redo.

Метки.

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

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


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

Оператор if/unless.

Оператор while/until.

Оператор do {} while/until.

Оператор for.

Оператор foreach.



Ведение базы данных.


Ведение базы данных:

dbmopen(%last_good, "lastdb", 0666) || die "can't dbmopen lastdb: $!"; $last_good{$name} = time; dbmclose(%last_good) || die "can't dbmclose lastdb: $!";

Получение информации из базы данных:

#!/usr/local/bin/perl -w dbmopen(%last_good, "lastdb", 0666) || die "can't dbmopen lastdb: $!"; foreach $name (sort key (%last_good)) { $when = $last_good{$name}; $hours = (time() - $when) / 3600; write; }

format STDOUT = User @&lt&lt&lt&lt&lt&lt&lt&lt&lt&lt: last correct guess was @&lt&lt&lt hour ago. $name, $hours .



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


sub sum_of_a_and_b { return $a + $b; }

При вычислении в списочном контексте подпрограмма может возвращать список

sub list_of_a_and_b { return ($a, $b); } $a = 5; $b = 6; @c = list_of_a_and_b();



Practical Extraction and Report Language


Perl - Practical Extraction and Report Language (или Phatologically Eclectic Rubbish Lister).
Программа Hello.
Выбор.
Секретное слово.
Несколько секретных слов.
Использование таблицы секретных слов - хеш.
Обработка различных вариантов ввода.
Использование подпрограмм.
Работа с файлами.
Повышение уровня безопастности.
Предупреждение о попытке доступа.
Несколько файлов секретных слов в текущем каталоге.
Распечатка списка секретных слов.
Переименование файлов, содержащих старые пароли.
Ведение базы данных.
Окончательный вариант программ.

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


Операция похожа на <STDIN&gt, однако, она читает данные из файлов, заданных в командной строке при вызове программы.

Программа test:

#!/usr/local/bin/perl -w while (<&gt) { print $_; }

Вызов: test file1 file2 file3

"ромб&quot работает с массивом @ARGV:

@ARGV = qw(aa bb cc); while (<&gt) { print "this line is: $_"; }



Ввод из STDIN.


Ввод со стандартного ввода (через Perl-дескриптор файла STDIN)

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

В списочном контексте - все оставшиеся строки в виде списка, каждый элемент которого одна строка с \n

@a = <STDIN&gt ;

Чтение строк по одной:

while(defined($line_ = <STDIN&gt)) { # обработка строк }

Операция чтения скалярного значения из <STDIN&gt в $_

и использование этого значения в качестве переменной цикла:

while(<STDIN&gt) { # как "while(defined($_ = <STDIN&gt))" chomp; # как "chomp($_)" # другие опрации с $_ }



Ввод-вывод.


Ввод из STDIN.

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

Вывод в STDOUT.



Выбор.


#!/usr/local/bin/perl -w print "What is you name? "; $name = <STDIN&gt ; chomp($name); if($name eq "German") { print "Hello, German! Good of you to be here!\n"; } else { print "Hello, $name! \n"; }



Выбор другого обьекта для сопоставления.


Если строка, которую нужно сопоставить с образцом, не находится в переменной $_, то можно воспользоваться операцией =~. Пример:

$a = " hello world"; $a =~ /^he/; # истина $a =~ /(.)\1/; # истина (соответствует двум l) if($a =~ /(.)\1/) { #истина }

Слева от знака операции =~ может стоять любое выражение:

print "any last request? "; if( <STDIN&gt =~ /^[yY]/) { print "And just what might that request be? ";

<STDIN>; print "Sorry, I'm unable to do that.\n"; }



Значение undef.


До присваивания значения переменные имеют значения undef. При работе с ключем - w вы получите предупреждение.



Apache


Web-сервер APACHE

Данный документ я взял на сайте http://www.dizain.ru, и он помог мне установить на моем компьютере сервер Apache, благодаря чему я смог настраивать php-скрипты непосредственно у себя. Из-за важности этой информации я включаю этот документ в дистрибьютив программы Паутина.

Оглавление

Введение - зачем нужен домашний виртуальный сервер?

От слов к делу - установка Apache версии 1.3.6

Виртуальные хосты Apache - как это настроить?

Установка PHP версии 4.1.0

Установка Perl версии 5.6.0.6

Установка MySQL версии 3.23.49

Дистрибутивы

Apache версии 1.3.9

PHP версии 4.1.0

Perl версии 5.6.0.6

MySQL версии 3.23.49

1. Введение - зачем нужен домашний виртуальный сервер?

"Ну к чему все это, лучше бы водки выпили"

Из писем Белинского Гоголю

Если Вы читаете этот документ, а также если у Вас установлен Windows 95/98 (а наше личное мнение такое, что эта операционная система наиболее сбалансирована с точки зрения интерфейса и удобства работы), значит, Вы уже столкнулись с проблемой виртуального домашнего сервера, а точнее, с проблемой его отсутствия! Эта небольшая статья поможет Вам скачать и установить один из лучших серверов - Apache, а также те приложения, из-за отсутствия которых народ в бешенстве сметает все остальные сервера (например, Sambar Server) со своего многострадального жесткого диска и устанавливает Apache для Windows 95/98. Имеются в виду, конечно, Perl, PHP3 и MySQL, также работающие под Windows. Прочитав эту статью и скачав дистрибутивы, Вы будете вооружены всеми инструментами, которые так необходимы для профессиональной работы в Web!

Обращаем Ваше внимание: бытует мнение, что MySQL (а тем более для Windows 95/98) нельзя получить бесплатно, а можно только купить. Так вот, можете вздохнуть с облегчением: MySQL для Windows 95/98 существует, и ее установка не будет стоить Вам и копейки!

Поговорим теперь с теми пользователями Windows 95/98, которые заглянули сюда из простого любопытства. Часто возникает ситуация, когда необходимо проверить полный вид html-страницы. Однако чаще всего это невозможно при работе дома - технологии SSI, CGI и, конечно, PHP, например, точно требуют сервера. Как же быть? Не стоит впадать в апатию - нужно просто установить на Ваш домашний компьютер (пусть даже и не подключенный к Интернет) специальную программу - Web-сервер. Вообще-то серверов существует множество - плохие и хорошие, медленные и быстрые... Мы же выбрали сервер, подходящий под последние две категории, - Apache. Самое главное то, что это чуть ли не единственный сервер, который позволяет работать в Windows 95/98 с технологиями PHP, CGI и Perl-скриптами одновременно так же просто и непринужденно, как будто у Вас стоит Unix.


2. От слов к делу - установка Apache

" У меня для вас две новости: плохая и хорошая. Плохая: мяса

мало, будем есть бизоний помет. И хорошая: его-то у нас много!.."

Из выступления вождя апачей

Итак, Вы решились установить на свой компьютер Apache для Windows 95/98. В таком случае Вам следует запастись терпением и для начала скачать дистрибутив сервера - файл с именем apache_setup.exe (3.061.629 байт). Скачали? Прекрасно. Теперь самое интересное - настройка Apache для Вашей системы.

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

Этап первый - установка

Определитесь с директорией, в которую Вы будите устанавливать Apache. Все дальнейшие рассуждения основаны на том, что Вы выбрали для этой цели такой каталог: f:\usr\local\apache Если диска F: у Вас нет, или если Вы не хотите его захламлять, советуем сделать одно из трех:

Создайте диск F: с помощью какой-нибудь программы для виртуальных разделов (например, с помощью встроенной в Windows 95/98 программы DriveSpace). Это самое лучшее решение, и с точки зрения экономии памяти, и с точки зрения быстродействия. Ведь что такое Web-сайт, как не набор очень небольших файлов? А DriveSpace как раз и оптимизирует работу с такими файлами.

Сделайте виртуальный диск F:. Для этого создайте где-нибудь на любом диске директорию, которая в будущем будет являться корневой для диска F:. Предположим, Вы выбрали C:\INTERNET. Далее, в начале файла c:\autoexec.bat пропишите такую строку:

subst f: C:\INTERNET

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

ВНИМАНИЕ: имеются сведения, что в Windows 95/98 есть ошибка, в результате которой иногда subst-пути "сами по себе" преобразуются в абсолютные. То есть, например, иногда в рассмотренном выше примере команды

f:

cd \

cd \

dir

(а точнее, команда dir в своем заголовке) ошибочно выведут, что текущая директория C:\ (а не F:\, как это должно быть). Указанная ошибка чаще всего проявляется в неработоспособности Perl-транслятора. Так что лично мы не рекомендуем Вам использовать subst. Вместо этого воспользуйтесь пунктом 1.



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

Рекомендуем все же разместить Apache в указанном в начале каталоге, так как он максимально соответствует каталогу для реального Web-сервера Интернета. Ведь чем ближе в плане конфигурации мы будем к такому серверу, тем лучше и эффективнее сможем работать.

Запустите только что скачанный файл. В появившемся диалоге нажмите кнопку Yes, а затем - кнопку Next.

Теперь нажмите Browse. Вручную задайте директорию для установки: f:\usr\local\apache и нажмите кнопку OK.

Выберите тип установки - Сustom и уберите флажок Source Code (если, конечно, не хотите посмотреть исходные тексты Apache). Этим Вы сэкономите себе 3 Мбайта.

Нажмите Next и подождите, пока будут копироваться файлы Apache.

На запрос о перезагрузке компьютера ответьте "Перезагрузить".

Поздравляем - Apache установлен! Теперь самое неприятное - его настройка.

Этап второй - настройка файла конфигурации Apache mime.types

Откройте директорию f:\usr\local\apache\conf. Откройте находящийся там файл mime.types.

Найдите в нем такую строчку:

text/html html htm

Измените ее на

text/html html htm shtml shtm sht

Следует заметить, что если Вы по каким-то причинам не хотите портить файл mime.types, то можно вместо этого прописать в файле httpd.conf (см. ниже) строки вида

AddType text/html html htm shtml shtm sht

Этап третий - настройка файла httpd.conf

Внимание! Это - самый ответственный момент установки. Просим соблюдать инструкции БУКВАЛЬНО.

Откройте директорию f:\usr\local\apache\conf Откройте находящийся там файл httpd.conf. Это - единственный файл, который Вам осталось настроить. Вам предстоит найти и изменить в нем некоторые строки, а именно те, о которых упоминается далее. Во избежание недоразумений не трогайте все остальное. Следует заметить, что в нем каждый параметр сопровождается несколькими строками комментариев, разобраться в которых с первого раза довольно тяжело. Поэтому не обращайте на них внимание.



В поле ServerAdmin укажите Ваш E-mail адрес, который будет показываться в сообщениях об ошибке сервера. Например:

ServerAdmin my@email.com

В поле ServerName напишите любое слово - на работе это не сказывается, например:

ServerName ApacheServer

Только не забудьте раскомментировать поле ServerName, то есть убрать символ "#" перед этим параметром (по умолчанию он закомментирован)!

В поле DocumentRoot укажите ту директорию, в которой будут храниться Ваши html-файлы, например:

DocumentRoot f:/www

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

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

Options Indexes Includes

AllowOverride All

Таким образом, в этом блоке будут храниться установки для всех директорий по умолчанию (т.к. это - корневая директория).

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

Options Indexes Includes

AllowOverride All

Order allow,deny

Allow from all

Это - установки для директории с Вашими html-документами. Если хотите, можете установить другую директорию, главное, чтобы она совпадала с той, которая прописана в параметре DocumentRoot

Идем дальше. Установите UserDir, например так:

UserDir f:/home

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

Установите DirectoryIndex так:

DirectoryIndex index.htm index.html

Это - так называемые файлы индекса, которые автоматически выдаются сервером при обращении к какой-либо директории, если не указано имя html-документа. В принципе, можно добавить сюда и другие имена, например, index.phtml, если Вы будите работать с PHP и т.д.



Найдите и пропишите такой параметр:

ScriptAlias /cgi-bin/ "f:/cgi-bin/"

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

ScriptAlias /mycgi/ "f:/mycgidir/"

Подобный параметр говорит Apache о том, что, если будет указан путь вида http://localhost/cgi-bin, то на самом деле следует обратиться к директории f:/cgi-bin.

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

AllowOverride All

Options ExecCGI

Настройте следующий параметр:

AddHandler cgi-script .bat .exe

Это говорит Apache о том, что файлы с расширением .exe и .bat нужно рассматривать как CGI-скрипты.

И последнее - установите:

AddHandler server-parsed .shtml .shtm .sht

Или, если Вы хотите, чтобы и обычные файлы html обрабатывались SSI, напишите так:

AddHandler server-parsed .shtml .shtm .sht .html .htm

Поздравляем - Вы настроили свой Apache, и он должен уже работать! Для запуска сервера нажмите Пуск->Программы->Apache Web Server->Start Apache as console app, при этом появится окно, очень похожее на Сеанс MS-DOS, и ничего больше не произойдет. Не закрывайте его и не трогайте до конца работы с Apache.

Несколько слов о том, как можно упростить запуск и завершение сервера. В Windows можно назначить любому ярлыку функциональную комбинацию клавиш, нажав которые, Вы запустите этот ярлык. Так что щелкните правой кнопкой на панели задач, в контекстном меню выберите Свойства, затем Настройка меню и кнопку Дополнительно. В открывшемся Проводнике назначьте ярлыку Start Apache as console app комбинацию Ctrl+Alt+A, а ярлыку Shutdown Apache as console app - Ctrl+Alt+S

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

Проверка html: в директории f:/www с html-документами Apache создайте файл index.html. Теперь запустите браузер и наберите:



http://localhost/index.html

или просто

http://localhost/

Загрузится Ваш файл.

Проверка CGI: в директории f:/cgi-bin для CGI-скриптов создайте файл test.bat с таким содержанием:

@echo off

echo Content-type: text/html

echo.

echo.

dir

Теперь в браузере наберите:

http://localhost/cgi-bin/test.bat

В окне отобразится результат команды DOS dir.

Проверка SSI: аналогична проверке html. Используйте, например, директиву

[an error occurred while processing this directive]

3. Виртуальные хосты Apache - как это настроить?

"Виртуальные хосты - хосты, имеющие уникальный адрес

в Интернет, эмулируемые и поддерживаемые сервером"

Древнее языческое заклинание

Итак, Вы установили Apache. Получили, таким образом, директорию f:/www для хранения документов и f:/cgi-bin для CGI. Но вот беда: в Интернете вы поддерживаете несколько серверов, а Apache создал для вас только один. Конечно, можно структуру этих несколькох серверов хранить на одном сервере, однако проще и удобнее было бы создать несколько виртуальных хостов с помощью Apache, например, один с именем serv1 и адресом 127.0.0.2, а другой - с именем serv2 и адресом 127.0.0.3. (Конечно, вместо "serv1" и "serv2" Вам нужно будет указать желаемые имена Ваших виртуальных хостов. Советуем назвать их так же, как и на Вашем настоящем Web-сервере - это может многое упростить при программировании скриптов.)

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

файл access.log с журналом доступа к виртуальному серверу.

файл errors.log с журналом ошибок сервера.

директория www, где будут храниться html-документы.

директория cgi для хранения CGI-программ.

Последние две директории (www и cgi) Вам тоже необходимо создать прямо сейчас.

Далее, для установки виртуального хоста необходимо сделать некоторые изменеия в файле конфигурации Apache httpd.conf (см. выше), а также в некоторых файлах Windows. Вот необходимые действия:



Откройте директорию f:\usr\local\apache\conf. Откройте находящийся там файл httpd.conf. Перейдите в его конец, Вам предстоит добавить туда несколько строк.

Пропишите следующие строки в конце файла после всех комментариев:

#----serv1

ServerAdmin webmaster@serv1.ru

ServerName serv1

DocumentRoot "f:/home/serv1/www"

ScriptAlias /cgi/ "f:/home/serv1/cgi/"

ErrorLog f:/home/serv1/error.log

CustomLog f:/home/serv1/access.log common

При желании можно добавить и другие параметры (например, DirectoryIndex и т.д.) Вообще, не переопределенные параметры наследуются виртуальным хостом от главного.

Теперь надо немного подправить системный файл hosts, который находится в C:\WINDOWS\hosts (такого файла может не быть по умолчанию - в этом случае его надо создать). hosts - обычный текстовый файл, и в нем обычно заранее прописана только одна строка:

127.0.0.1 localhost

именно эта строка и задает соответствие имени localhost адресу 127.0.0.1. (Ради справедливости следует сказать, что имя localhost работает и без указанной выше строки. Ну и выдумщики же эти парни из фирмы Microsoft!) Для нашего виртуального хоста надо добавить соответствующую строчку, чтобы файл выглядел так:

127.0.0.1 localhost

127.0.0.2 serv1

Этим Вы создадите виртуальных хост со следующими свойствами:

Имя - serv1

Доступен по адресу http://serv1 (или http://127.0.0.2).

Расположен, соответственно, в директории f:/home/serv1.

Директория для хранения документов - f:/home/serv1/www, доступная по адресу http://serv1/.

Директория для CGI - f:/home/serv1/cgi, доступная по адресу http://serv1/cgi/

Файлы журналов хранятся в f:/home/serv1

Ну вот, мы создали один виртуальный хост! Если будет необходимо сделать второй, нужно просто проделать аналогичные действия, заменив параметры, связанные с расположением хоста на диске. Главное, не забудьте в этом случае указать другой IP-адрес (лучше всего указывать их последовательно, начиная с 127.0.0.2, затем 127.0.0.3 и т.д. - в этом случае все работает корректно). Желательно также для этих целей не указывать IP-адрус http://127.0.0.1, так как это - адрес главного сервера.



Кстати, необходимо заметить, что главный хост (невиртуальный, тот, который мы создали в раздулах 1 и 2) по-прежнему доступен по адресу http://127.0.0.1 или http://localhost. Более того, его директория cgi-bin "видна" всем созданным виртуальным хостам, так что Вы можете ее использовать.

4. Установка PHP версии 4

В отличие от установки Apache, установка PHP короче, однако мы бы не сказали, что проще. Дело в том, что, во-первых, у PHP нет нормальной setup-программы, как у Apache, а во-вторых, при его установке необходимо также настраивать сервер.

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

f:/usr/local/php3

Если Вы физически не можете или просто не хотите иметь такой каталог (хотя, если Вы читали инструкцию по установке Apache, все должно быть в порядке), то Вы вольны установить PHP в другой каталог, но тогда Вам предстоит следующее: зайти в директорию sapi и поочередно открыть файлы с расширением .reg (их там 2 штуки), вставив вместо "[PUT PATH HERE]" системный путь к файлу php.exe . Затемм, соответственно, Вы должны запустить их.

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

Установка PHP

Создайте директорию f:/usr/local/php3 (если хотите другое имя, см. рассуждения выше). Это - та директория, в которую будет установлен PHP.

Скачайте дистрибутив PHP - файл с именем php-4.1.0-Win32.zip (4,897,626 байт), желательно в только что созданную директорию. Это zip-архив, который Вы должны будете разархивировать.

Еще раз напоминаем: если Вы решили установить PHP в другую директорию, Вам необходимо вручную отредактировать файлы .reg с целью замены в нем имен директории на нужную (см. выше). Затем переименуйте файл "php.ini-recommended" в php.ini просто.

В файле php.ini из дистрибутива есть закомментированные строки, выглядящие так:

;extension=имя_модуля.dll

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



Скопируйте файл php.ini в каталог с Windows (например, в c:\windows);

Настройка Apache

В файл конфигурации Apache conf/mime.types добавтьте такую строку:

application/x-httpd-php3 phtml php3 php4

Теперь откройте файл conf/httpd.conf и добавьте в его конец ( но перед блоков виртуальных хостов, если они там есть) такие строки:

Options ExecCGI

ScriptAlias "/__php_dir__/" "f:/usr/local/php3/"

Action application/x-httpd-php3 "/__php_dir__/php.exe"

Ну вот, пожалуй, и все. Если Вы все сделали правильно, то PHP установлен. Проверьте его работоспособность с помощью простого скрипта, например такого:

<?

echo "It works!
\n";

phpinfo();

?>

Напоминаем, что php-скрипты - не то же самое, что cgi-скрипты. В частности, если cgi-скрипты обычно располагают в /cgi-bin/, то php-скрипт должен лежать в директории с документами. Иными словами, файл в этом примере должен называеться примерно так: f:/www/test.php3

5. Установка Perl

Это совсем просто, за исключением, может быть, выбора директории для Perl. А именно, Вы ДОЛЖНЫ разместить Perl в той же директории, в которой он находится на Вашем настоящем Web-сервере. Заметьте, что это очень важно, так как Perl требует, чтобы в каждом скрипте первой строкой стоял путь к Perl-интерпретатору; например, эта строка может выглядеть так:

#!/usr/local/bin/perl

Эту же строку можно было бы написать и так:

#!/usr/local/bin/perl.exe

или даже так:

#!f:\usr\local\bin\perl.exe

Это заставляет искать Perl-интерпретатор в директории f:/usr/local/bin/ (если диск f: не указан, это означает, что он совпадает с диском, на котором расположен Apache). Ясно, что если Вы установите Perl не в такую же директорию, как на настоящем Web-сервере, Вам придется каждый раз менять эту самую первую строку во всех скриптах при закачке их на сервер. Итак, далее мы будем считать, что эта директория такова, как на большинстве Apache-серверов:

f:/usr/local/bin

ВНИМАНИЕ: очень распространенной ошибкой является установка Perl не в ту директорию или не на тот диск. Еще раз обращаем внимание на то, где должен быть расположен транслятор. Если Вы все же по какой-то необъяснимой причине не придерживаетесь нашего совета, то проверьте первую строку в Вашем скрипте. Она должна указывать не на директорию с Perl, а на исполнимый файл perl.exe. Напоминаем, что



#!/usr/local/bin/perl

заставляет искать Perl-интерпретатор perl.exe в директории f:/usr/local/bin/, а не f:/usr/local/bin/perl

Если Вы все же установите пути неправильно, Apache выдаст непонятное сообщение об ошибке, а в errors.log появится сообщение: couldn't spawn child process.

Вот шаги, приводящие к цели:

Первым делом создайте директорию

f:/usr/local/bin

Затем скачайте дистрибутив Perl - файл с именем perl_cool.zip (7,913,776 байт), желательно в только что созданную директорию. Это zip архив, Вам нужно будет разархивировать в текущую директорию. В нем файл .msi . Для его установки Вам потребуется обновленная версия Windows Installer.

Теперь настроим сервер. Найдите в файле конфигурации Apache conf/httpd.conf строчку

AddHandler cgi-script .bat .exe

Замените ее на

AddHandler cgi-script .bat .exe .pl .cgi

Как это ни странно, но эту директиву AddHandler иногда указывать не обязательно. Однако лучше перестраховаться...

Вот, собственно, и все. Можете пользоваться Perl-транслятором. Для проверки его работоспособности используйте такой скрипт (помещенный, разумеется, в директорию cgi-bin или аналогичную):

#!/usr/local/bin/perl

print "Content-type: text/html\n\n";

print "It works!
\n";

system("dir");

5. Установка MySQL

Сначала определимся: зачем же вообще нужны базы данных Web-программисту? Неужели не проще писать все самому? Ведь обычно объем данных не очень велик (если Вы только не пишите поисковую систему). Наш личный опыт таков: оказывается, стоит затратить какое-то время на изучение MySQL - это удивительно мощный инструмент, который сэкономит в будущем немало часов, потраченных на отладку "взбесившегося" скрипта.

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

Эта статья не претендует ни в коей мере на то, чтобы быть учебником по MySQL. Предполагается, что Вы уже знаете, как работать с этой базой данных. Максимум, что здесь описывается - это то, как заставить MySQL работать под Window 95/98.



В дальнейшем будем считать, что Apache у Вас установлен именно там, где это рекомендовалось выше.

Что ж, приступим.

Для начала запаситесь терпением и скачайте дистрибутив MySQL - mysql-max-3.23.49-win1.zip . Как можно заметить, он довольно большой. Затем разверните его в любую удобную Вам директорию.

Запустите setup.exe. Он спросит, действительно ли Вы хотите установить MySQL. После того, как Вы ответите утвердительно, файлы начнут копироваться в директорию c:/mysql, т.е. он даже не спросит Вас, куда устанавливать MySQL. Ничего страшного. (Если файлы не начали копироваться, попробуйте подключиться к Internet во время установки).

Теперь, если Вы любите порядок, можете скопировать директорию c:/mysql в какое-нибудь более приличное место - например, f:/usr/local/. Только после этого строго следуйте указаниям в статье.

Создайте в директории f:/usr/ такие два .bat-файла:

server.bat:

@echo off

f:\usr\local\mysql\bin\mysqld.exe --basedir f:/usr/local/mysql

shutdown.bat:

@echo off

f:\usr\local\apache\Apache.exe -d f:\USR\LOCAL\APACHE -k shutdown

"f:\usr\local\mysql\bin\mysqladmin.exe" -u root shutdown

Файл server.bat Вы будете запускать, когда захотите "включить" Apache и одновременно MySQL (ясно, что бессмысленно запускать MySQL без сервера), а shutdown.bat - для завершения работы Apache и MySQL. Очень важно завершать работу MySQL правильно - иначе могут быть испорчены таблицы баз данных. Собственно, для этого мы и сделали эти два .bat-файла. (Кстати говоря, как и Apache, MySQL работает в своем собственном окне).

Теперь для удобства можно создать ярлыки на Рабочем столе для этих файлов. Рекомендуем также назначить этим ярлыкам "горячие" клавиши: например, для запуска сервера - Ctrl+Alt+A, а для завершения работы - Ctrl+Alt+S. Кроме того, лучше поставить у этих ярлыков параметры "Запускать свернутыми в значок". Все это сильно упростит жизнь в дальнейшем.

Что ж, считайте, MySQL уже установлена. Осталось только создать базу данных. Для этого следует запустить f:/usr/local/mysql/bin/mysqladmin с ключем create имя_базы. Например, если мы хотим создать базу testbase, нужно ввести:



f:/usr/local/mysql/bin/mysqladmin create testbase

Если Вы планируете использовать MySQL в скриптах на PHP, проверьте, раскомментирована ли в файле php3.ini (расположенном в директории с PHP и в c:\windows) следующая строка:

extension=php3_mysql.dll

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

Поздравляем - теперь можно работать! Если хотите, можете проверить работоспособность MySQL следующим скриптом на PHP3 (скажем, расположенном в f:/www/test.php3):

<?

Error_Reporting(1+2+4);

define("DBName","testbase");

define("HostName","localhost");

define("UserName","root");

define("Password","");

if(!mysql_connect(HostName,UserName,Password))

{ echo "Не могу соединиться с базой ".DBName."!
"; exit; }

// Создаем таблицу test. Если такая таблица уже есть, сообщение об ошибке будет

// подавлено, т.к. используется "@"

@mysql(DBName,"create table test(id int,a text)");

// Вставляем в таблицу 10 записей

for($i=0; $i<10; $i++)

{ $id=time();

mysql(DBName,"insert into test(id,a) values($id,'Строка $i!')");

}

// Выводим все записи

$r=mysql(DBName,"select * from test");

for($i=0; $i

{ $f=mysql_fetch_array($r);

echo "$f[id] -> $f[a]
\n";

}



?>

Обращаем Ваше внимание на макросы DBName, HostName, UserName и Password. DBName должен содержать имя базы данных. HostName - всегда localhost, ведь мы работаем на локальном компьютере. В макросе UserName проще всего подставлять root, который является собственником всех таблиц. При установке MySQL пользователю root не назначается пароль, так что макрос Password равен пустой строке.


Crack


По понятным соображениям, Crack я выкладывать здесь не могу. Попробуйте сами поискать, скажем на http://www.astalavista.com .

Для Sendmail 1.17 Вы Crack врядли найдете, поэтому если найдете для 1.14 - используйте его.

Всего хорошего!


По понятным соображениям, Crack я выкладывать здесь не могу. Попробуйте сами поискать, скажем на http://www.astalavista.com .

Всего хорошего!


Shttp


Установка Small HTTP server 3.038 или младше.

1. Запустите установочный файл, выберите путь, куда будут распакованы файлы (я рекомендую оставить по умолчанию: "c:\shttps"). Не забудьте отметить флажок о соглашении с лицензией. Нажмите кнопку "установить".

2. Запустите программу. Щелкните правой кнопкой мыши на значке в трее и выберите пункт "регистрация". В качестве имени пользователя введите "Гражданин бывшего СНГ", код "31".

3. Зайдите в директорию, куда была проинсталлирована программа, и создайте в ней директорию WWW (если она не создана), а в ней "cgi-bin".

4. Установите Perl так, чтобы путь к исполнимому файлу был таким: "C:\USR\LOCAL\BIN\perl.exe".

5. Завершите работу сервера и откройте файл "http.cfg", находящийся в его директории. Занесите в него следующие строки:

cgi_ident="\cgi-bin\"

perl="C:\usr\local\bin\Perl.exe"

ssihtm

post_limit=512000

cgi_timeout=30

6. Проверка работоспособности сервера:

Создайте файл index.htm в директории WWW, в котором содержалась бы такая строчка:

<!--#include virtual="/cgi-bin/script.pl" -->

В директории CGI-BIN создайте файл script.pl, и запишите в него такую информацию:

#!/usr/bin/perl

print "Content-Type: text/html\n\n";

print "It works!";

Теперь в браузере напишите "http://localhost" или "http://127.0.0.1". Должна открыться страница с надписью "It works!".

Если это так, значит сервер работает нормально.

Если на экране появилась строка, содержащая "Execution of script.pl aborted due to compilation errors.", значит Вы где-то допустили ошибку в скрипте script.pl .

Если экран оказался пустым, значит не выполнился SSI скрипт. Ищите причину этого. Возможно, вы забыли добавить строку "ssihtm" в файл http.cfg .



Базовые средства ввода-вывода


Ввод из STDIN

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

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

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

@а = <STDIN>;

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

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

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

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

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

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

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

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

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

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

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


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

i

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

kitty filel file2 file3

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

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

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

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

t

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



Вывод в STDOUT



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



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



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

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



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

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

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

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

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



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



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

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

Пример:

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

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



Упражнения





Ответы к упражнениям даны в приложении А.

1. Напишите программу, которая работает аналогично команде cat, но изменяет на обратный порядок следования всех строк всех файлов, указанных в командной строке, или всех строк, поступающих со стандартного ввода, если файлы не указаны. (В некоторых системах есть подобная утилита; она называется tac.)

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

3. Напишите программу, которая читает список строковых значений (каждое из которых занимает отдельную строку) и выводит эти строки в 20-символьном столбце с выравниванием справа. Например, при вводе строк hello и good-bye они выводятся в 20-символьном столбце с выравниванием справа. (Добейтесь, чтобы ваша программа действительно использовала 20-символьный столбец, а не 21-символьный. Это весьма распространенная ошибка.)

4. Измените программу из предыдущего упражнения таким образом, чтобы пользователь мог выбирать ширину столбца. Например, при вводе строк 20, hello и good-bye получатся те же результаты, что и в предыдущем упражнении, а ввод 30, hello, good-bye обеспечит размещение hello и good-bye в 30-символьном столбце.

|     Назад     |     Вперед     |



| Содержание | Предисловие | Введение | Ссылки
| Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10
| Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19
| Приложение А | Приложение Б | Приложение В | Приложение Г |


Библиотеки и модули


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

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

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

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

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

# для (большинства) Unix-подобных shell peri -e 'print "@INC\n"'

# для (некоторых) других интерпретаторов команд

peri -e "print join(' ',"@INC),\n"


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



Терминология



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

Пакет

Пакет — это простое устройство управления пространством имен, позволяющее в каждой из двух разных частей Perl-программы иметь свою переменную с именем $fred. Этими пространствами имен управляет объявление package, описанное в главе 5 книги Programming Perl.

Библиотека

Библиотека — это набор подпрограмм определенного назначения. Часто библиотека объявляет себя отдельным пакетом; это позволяет держать в одном месте соответствующие переменные и подпрограммы, чтобы они не мешали другим переменным в вашей программе. Как правило, библиотека старого стиля размещалась в отдельном файле, часто под именем с расширением р1. Библиотечные программы включались в основную программу посредством функции require. He так давно этот подход был заменен использованием модулей (см. следующий абзац), и термин библиотека теперь часто обозначает всю систему модулей, которые поставляются с Perl.

Модуль

Модуль — это библиотека, соответствующая конкретным соглашениям, которая позволяет включать библиотечные подпрограммы в основную программу во время компиляции с помощью директивы use. Имена файлов модулей имеют расширение рт, потому что это необходимо для корректного использования директивы use. Подробно Perl-модули описаны в главе 5 книги Programming Perl.

Прагма

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



Стандартные модули



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



Таблица Б. 1. Общее программирование: разное

Модуль Функция
autouse Задерживает загрузку модуля до его использования
constant Создает константы периода компиляции
Benchmark Проверяет и сравнивает временные параметры выполне
ния кода
Config Позволяет получить информацию о конфигурации Perl
Env Импортирует переменные среды
English Для пунктуационных переменных использует английские имена или имена на языке awk
FindBin Находит путь к выполняемой в данный момент программе
Getopt::Long Осуществляет расширенную обработку опций командной
строки
Getopt::Std Обрабатывает односимвольные ключи и осуществляет их
кластеризацию
lib Манипулирует массивом @INC во время компиляции
Shell Запускает команды shell прозрачно для Perl
strict Ограничивает использование небезопасных конструкций
Symbol Генерирует анонимное развертывание (glob); уточняет
имена переменных
subs Предопределяет имена подпрограмм
vars Предопределяет имена глобальных переменных
Таблица Б. 2. Общее программирование: обработка ошибок и регистрация

Модуль Функция
Сагр diagnostics sigtrap

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

Разрешает обратное прослеживание стека для неожиданных сигналов

Perl-интерфейс к UNIX-вызовам syslog(3)
Таблица Б.З. Общее программирование: доступ к файлам и их обработка
Модуль Функция
Cwd DirHandle

Fcnti File::Basename File::CheckTree File:: Copy File::Find File::Path FileCache

FileHandle SelectSaver
Получает путевое имя текущего рабочего каталога

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

Загружает С-определения Fcntl.h Разбирает спецификации файлов Выполняет всевозможные проверки для набора файлов Копирует файлы или дескрипторы файлов Обеспечивает просмотр дерева файлов Создает и удаляет ряд каталогов

Позволяет одновременно открывать больше файлов, чем разрешает система

Выдает методы объектов для работы с дескрипторами файлов

Сохраняет и восстанавливает выбранный дескриптор файла
Таблица Б. 4. Общее программирование: классы для операций ввода-вывода
Модуль Функция
10 IO::File IO::Handle IO::Pipe IO::Seekable IO::Select IO::Socket Интерфейс верхнего уровня к классам 10::* Методы объектов для работы с дескрипторами файлов Методы объектов для дескрипторов ввода-вывода Методы объектов для каналов Методы для объектов ввода-вывода на базе поиска Объектный интерфейс для выбора Объектный интерфейс для портов
Таблица Б. 5. Общее программирование: обработка текста и экранные интерфейсы
Модуль Функция
locale

Pod::HTML Pod::Text

Search::Dict Term::Cap
Использует локализацию POSIX для встроенных операций

Конвертирует pod-данные в HTML

Конвертирует pod-данные в форматированный ASCII-текст

Ищет ключ в файле словаря Интерфейс termcap
<


Модуль Функция
Term::Complete Text::Abbrev Text::ParseWords Text::Soundex Text::Tabs Text::Wrap Модуль завершения слов Создает из списка таблицу сокращений Разбирает текст на лексемы и создает из них массив Реализует алгоритм Soundex, разработанный Кнутом Раскрывает и сворачивает знаки табуляции Выделяет текст в абзац
Таблица Б. 6. Интерфейсы к базам данных
Модуль Функция
AnyDBMFile DBFile GDBMFile NDBMFile ODBMFile SDBM File Создает основу для множества DBM Доступ к Berkeley DB Связанный доступ к библиотеке GDBM Связанный доступ к файлам NDBM Связанный доступ к файлам ODBM Связанный доступ к файлам SDBM
Таблица Б. 7. Математика
Модуль Функция
Integer Math::BigFloat Math::BigInt Math::Complex Выполняет арифметические операции в целочисленном формате, а не в формате с двойной точностью

Пакет математических операций для чисел с плавающей запятой произвольной длины

Пакет математических операций для целых чисел произвольной длины

Пакет для комплексных чисел
Таблица Б. 8. World Wide Web
Модуль Функция
CGI

CGI::Apache CGI::Carp CGI::Fast CGI::Push CGI:: Switch
Интерфейс Web-сервера (Common Gateway Interface) Поддержка Perl-модуля сервера Apache Ошибки сервера регистрации с полезной информацией Поддержка FastCGI (устойчивый серверный процесс) Поддержка "выталкивания" со стороны сервера Простой интерфейс для многих типов серверов
Таблица Б. 9. Сети и межпроцессное взаимодействие
Модуль Функция
1РС::Ореп2 Открывает процесс для чтения и записи
IPC::Open3 Открывает процесс для чтения, записи и обработки ошибок
Net::Ping Проверяет, есть ли данный хост в сети
Socket Загружает С-определения socket.h и манипуляторы структур
Sys::Hostname Пытается получить хост-имя всеми возможными способами
Таблица Б. 10. Автоматизированный доступ к Comprehensive Perl Archive Network
Модуль Функция
CPAN Простой интерфейс к CPAN
CPAN::FirstTime Утилита для создания файла конфигурации CPAN
CPAN::Nox Запускает CPAN, избегая компилированных расширений
Таблица Б. 11. Время и локализация
Модуль Функция
Time:: Local Эффективно определяет местное и среднее гринвичское
время
I18N::Collate Сравнивает восьмибитовые скалярные данные
Таблица Б. 12. Объектные интерфейсы к встроенным функциям
Модуль Функция
Class::Struct Объявляет struct-подобные типы данных как Perl-классы
File::stat Объектный интерфейс к функции stat
Net::hostent Объектный интерфейс к функциям gethost*
Net::netent Объектный интерфейс к функциям getnet*
Net::protoent Объектный интерфейс к функциям getproto*
Net::servent Объектный интерфейс к функциям getserv*
Time::gmtime Объектный интерфейс к функции gmtime
Time::localtime Объектный интерфейс к функции localtime
Time::tm Внутренний объект для Time::{gm,local}time
User::grent Объектный интерфейс к функциям getgr*
User::pwent Объектный интерфейс к функциям getpw*
<


Таблица Б. 13. Для разработчиков: автозагрузка и динамическая загрузка
Модуль Функция
Autoloader Загружает функции только по требованию
AutoSplit Разбивает пакет для автозагрузки
Devel::SelfStubber Генерирует заглушки для модуля SelfLoading
DynaLoader Автоматическая динамическая загрузка Perl-модулей
SelfLoader Загружает функции только по требованию
Таблица Б. 14. Для разработчиков: расширения языка и поддержка разработки платформ
Модуль Функция
blib Определяет структуру каталогов blib во время построения модулей
ExtUtils::Embed Утилиты для встраивания Perl в С-программы
ExtUtils::Install Инсталлирует файлы
ExtUtils::Liblist Определяет библиотеки для использования и по
рядок их использования
ExtUtils:: MakeMaker Создает Makefile для расширения Perl
ExtUtils: '.Manifest Утилиты для написания и проверки файла MANIFEST
ExtUtils::Miniperl Создает С-код для perlmain.c
ExtUtils: :Mkbootstrap Создает файл самозагрузки для использования модулем DynaLoader
ExtUtils: :Mksymlists Пишет файлы опций компоновщика для динами
ческого расширения
ExtUtils: :MMOS2 Методы для отмены UNIX-режима в ExtUtils::MakeMaker
ExtUtils: :MMUnix Методы, используемые модулем ExtUtils::MakeMaker
ExtUtils::MMVMS Методы для отмены UNIX-режима в ExtUtils::MakeMaker
ExtUtils: :testlib Исправляет @INC для использования только что
созданного расширения
Opcode Блокирует коды операций при компиляции Perl-
кода
ops Прагма для использования с модулем Opcode
POSIX Интерфейс к стандарту IEEE 1003.1
Safe Создает защищенные пространства имен для оценки Perl-кода
Test:: Harness Выполняет стандартные тестовые Perl-сценарии со сбором статистических данных
vmsish Обеспечивает возможности, характерные для VMS
Таблица Б. 15. Для разработчиков: поддержка объектно-ориентированного программирования
Модуль Функция
Exporter Стандартный метод импорта для модулей
overload Перегружает математические операции Perl
Tie::RefHash Базовый класс для связанных хешей со ссылками в
качестве ключей
Tie:: Hash Содержит определения базового класса для связанных хешей
Tie:: Scalar Содержит определения базового класса для связанных
скаляров
Tie::StdHash Содержит определения базового класса для связанных хешей
Tie::StdScalar Содержит определения базового класса для связанных
скаляров
Tie::SubstrHash Обеспечивает хеширование с фиксированным размером таблицы и фиксированной длиной ключей
UNIVERSAL Базовый класс для всех классов
<




CPAN: не только стандартная библиотека



Если вы не можете найти в стандартной библиотеке модуль, соответствующий вашим потребностям, все равно существует вероятность, что кто-то уже написал код, который будет вам полезен. Есть много превосходных библиотечных модулей, которые не включены в стандартный дистрибутив — по различным причинам практического, политического и вздорного характера. Чтобы выяснить, что есть в наличии, можно заглянуть в Comprehensive Perl Archive Network (CPAN). 0 CPAN мы говорили в предисловии.

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

• Модуль формата листинга.

• Базовые модули Perl, расширения языка и средства документирования.

• Модули, обеспечивающие поддержку разработки.

• Интерфейсы операционных систем.

• Организация сетей, управление устройствами (модемами) и межпроцес-сное взаимодействие.

• Типы данных и утилиты для типов данных.

• Интерфейсы баз данных.

• Пользовательские интерфейсы.

• Интерфейсы к другим языкам программирования и средства эмуляции этих языков.

• Имена файлов, файловые системы и блокировки файлов (см. также дескрипторы файлов).

• Обработка строк, обработка текстов, синтаксический анализ и поиск.

• Обработка опций, аргументов, параметров и файлов конфигурации.

• Интернационализация и локализация.

• Аутентификация, защита и шифрование.

• World Wide Web, HTML, HTTP, CGI, MIME.

• Серверные утилиты и демоны.

• Архивирование, сжатие и преобразование.

• Изображения, манипулирование картами пикселей и растрами, рисование и построение графиков.

• Электронная почта и телеконференции Usenet.

• Утилиты управления потоком (обратные вызовы и исключительные ситуации).

• Утилиты для работы с дескрипторами файлов, дескрипторами каталогов и потоками ввода-вывода.

• Модули для Microsoft Windows.

• Прочие модули.

|     Назад     |     Вперед     |



| Содержание | Предисловие | Введение | Ссылки
| Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10
| Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19
| Приложение А | Приложение Б | Приложение В | Приложение Г |


BITFTP


    BITFTP — это почтовый сервер для пользователей сети BITNET. Вы посылаете на него сообщения электронной почты с запросами на получение файлов, а сервер посылает по электронной почте указанные вами файлы. Сейчас BITFTP обслуживает только тех пользователей, которые посылают на него почту с узлов, непосредственно включенных в BITNET, EARN или NetNorth. BITFTP — это общедоступный сервис Принстонского университета. Чтобы воспользоваться услугами BITFTP, пошлите почтовое сообщение с необходимыми FTP-командами по адресу BITFTP@PUCC. Если вы хотите получить полный справочный файл, пошлите в теле сообщения слово HELP. Ниже приведено тело сообщения, которое следует послать на BITFTP:

FTP ftp.ora .corn NETDATA USER anonymous PASS ваш электронно-почтовый Internet-адрес (а не BITNET-адрес) CD /published/oreilly/nutshell/perl/learning_perl2 DIR GET README GET examples.tar.gz QUIT

    Вопросы, касающиеся самого BITFTP, следует направлять на узел BIT-NET MAINT@PUCC.



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


    Во-первых, я от всего сердца благодарю Чика Уэбба и фирму Taos Mountain Software (Кремниевая долина). Ребята из TMS предоставили мне возможность написать для них (при значительном содействии Чика) ввод-ный курс по языку Perl и прочитать этот курс несколько раз. Этот опыт дал мне мотивы и ресурсы для написания и многократного прочтения нового, моего собственного курса, на базе которого и построена эта книга. Не думаю, что без содействия сотрудников TMS я занимался бы этим, и желаю им всем больших успехов в маркетинге их курса. (А если они ищут хороший текст для переработки своего курса, то у меня как раз есть одно предложение...)
    Спасибо моим рецензентам: "крестному отцу" языка Perl Ларри Уоллу (естественно), Ларри Кистлеру (руководителю службы подготовки кадров фирмы Pyramid), моему коллеге по преподаванию Per] Тому Кристиансену и слушателям курсов по Perl из фирм Intel и Pyramid, а также сотрудникам издательства O'Reilly & Associates Тане Херлик, Лару Кауфману, Пенни Мюльнеру, Линде Мюи и Энди Ораму.
    Эта книга была полностью написана и отредактирована на моем персо-нальном компьютере Apple Macintosh Powerbook (сначала модели 140, теперь 160). В процессе работы я чаще всего находился вне своего кабинета — иногда в парке, иногда в гостинице, иногда в горах, ожидая хорошей погоды для лыжных прогулок, но чаще всего в ресторанах. По сути дела, значительную часть книги я написал в пивном баре Beaverton McMenamin's недалеко от дома. В пабах сети McM's варят и подают самое вкусное пиво, самый лучший творожный пудинг и самые жирные сэндвичи в округе. В этой идеальной для работы обстановке я выпил множество пинт пива и съел гору пудинга, а мой Powerbook поглотил массу киловатт-часов электроэнергии, устраиваясь на тех четырех столах, где есть розетки. Благодарю замечательный персонал бара McM's за эту электроэнергию, а также за гостеприимство, радушие и любезность (и бесплатное место для работы). Кроме того, благодарю ресто-ран Beaverton Chili's, где я начал работать над этой книгой. (Но у них возле стойки не оказалось розеток, поэтому когда я нашел McM's, то перешел туда, чтобы сберечь аккумуляторы.)
    Спасибо всем пользователям Internet (особенно подписчикам телеконфе-ренции сотр.lang.perl) за их постоянную поддержку, оказываемую Ларри и мне, и за бесконечные вопросы на тему о том, как заставить Perl работать. Благодарю также Тима 0'Рейли — за даосизм.
    Наконец, особая, огромная персональная благодарность — моему другу Стиву Тэлботту, который направлял меня на каждом этапе этого пути (в частности, он предложил сделать "прогулку" по стране Perl, впечатления от которой представлены в конце первой главы). Его редакторская критика всегда была справедливой, а его удивительная способность бить меня по голове с исключительной нежностью позволила мне сделать мою книгу произведением искусства, которым я крайне доволен.
    Как всегда, выражаю особую благодарность Лайлу и Джеку за то, что они нашили меня почти всему тому, что я знаю о писательском ремесле.
    И, наконец, безмерная благодарность — моему другу и партнеру Ларри Уоллу за то, что он дал всем нам Perl.
    Одна "Л" — Рэндал — книгу написала, Вторая — лама — на обложку прискакала. Но кто все это изобрел? То целых три "Л" — Ларри Уолл!

Рэндал



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


Я хотел бы поблагодарить Ларри Уолла за создание Perl, членов группы Perl Porters за их постоянные усилия по сопровождению языка и все Perl-сообщество за готовность помогать друг другу.
    Спасибо также Джону Оруонту, Нэйту Торкингтону и Ларри Уоллу за рецензирование главы, посвященной CGI.

Том



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


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

Дескриптор файла в Perl-программе — это имя соединения для ввода-вывода между вашим Perl-процессом и внешним миром. Мы уже видели дескрипторы файлов и пользовались ими, сами того не зная: stdin — это дескриптор, которым именуется соединение между Perl-процессом и стандартным вводом UNIX. Аналогичным образом в Perl существует stdout (для стандартного вывода) и stderr (для стандартного вывода ошибок). Это те же самые имена, что и используемые библиотекой стандартного ввода-вывода в С и C++, которую Perl задействует в большинстве операций ввода-вывода.

Имена дескрипторов файлов похожи на имена помеченных блоков, но они берутся из другого пространства имен (поэтому у вас может быть скаляр $fred, массив Sfred, хеш %fred, подпрограмма sfred, метка fred, а теперь и дескриптор файла fred). Как и метки блоков, дескрипторы файлов используются без специального префиксного символа, поэтому их можно спутать с существующими или возможными в будущем зарезервированными словами (для команд, подпрограмм и др.). Рекомендуем составлять дескрипторы файлов только из прописных букв. Во-первых, они будут хорошо выделяться в тексте программы, и, во-вторых, благодаря этому программа не даст сбой при введении нового зарезервированного слова.

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

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

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

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

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

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


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

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

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

close(LOGFILE) ;

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



Небольшое отступление: функция 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";



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



После того как дескриптор файла открыт для чтения, из него можно читать строки точно так же, как со стандартного ввода 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: $!";



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



Теперь вы знаете, как открыть дескриптор файла для вывода, уничтожив существующий файл с таким же именем. Предположим, вы хотите удостовериться, что файла с таким именем не существует (чтобы избежать случайного уничтожения своей электронной таблицы или очень важного календаря дней рождений). Если бы вы писали сценарий 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; # то же, что и -г $_ >



Функции stat и Istat



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

Операнд функции stat — дескриптор файла или выражение, посредством которого определяется имя файла. Возвращаемое значение — либо undef, если вызов неудачен, либо 13-элементный список**, который легче всего описать с помощью такого списка скалярных переменных:

($dev,$ino,$mode,$nlink,$uid,$gid,$rdev, $size, $atinie, $mtime, $ctime, $blksize,$blocks) = stat (. . .)

* Или операцию int.

** Если вам тяжело запомнить порядок значений, возвращаемых функцией stat, можете обратиться к модулю File: :stat, впервые введенному в выпуске 5.004. Он обеспечивает доступ к этим значениям следующим образом:

$file_owner = stat($filename)->uid

Имена здесь соответствуют частям структуры stat, подробно описанной на man-странице stat(T). Рекомендуем изучить приведенные там подробные пояснения.

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

($uid,,$gid) = (stat("/etc/passwd")) [4,5];

и этого окажется достаточно.

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



Как и в операциях проверки файлов, операнд функций stat и Istat по умолчанию — $_. Это значит, что операция stat будет выполняться над файлом, заданным скалярной переменной $_.



Упражнения



Ответы приведены в приложении А.

1. Напишите программу чтения имени файла из stdin, открытия этого файла и выдачи его содержимого с предварением каждой строки именем файла и двоеточием. Например, если считано имя fred, а файл fred состоял из трех строк, a a a, bbb и с с с, вы должны увидеть fred: а а а, fred: bbb И fred: ссс.

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

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

4. Напишите программу чтения списка имен файлов и поиска среди них самого старого. Выведите на экран имя файла и его возраст в днях.

|     Назад     |     Вперед     |



| Содержание | Предисловие | Введение | Ссылки
| Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10
| Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19
| Приложение А | Приложение Б | Приложение В | Приложение Г |


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


Если у вас есть доступ к World Wide Web, посетите домашнюю страницу Perl по адресу http://www.perl.com/perl/. Здесь вы узнаете, что нового произош-ло в мире Perl, сможете получить исходный код и номера портов, докумен-тацию, модули третьих фирм, базу данных ошибок Perl, информацию о списках рассылки и т.д. На этом узле имеется также сервис мультиплек-сирования CPAN, который описан ниже.



Доступ к каталогам


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

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

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

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

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

print "where do you want to go? ";

chomp($where = <STDIN>) ;

if (chdir $where) f

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

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

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

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

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

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

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

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


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

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

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

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

>

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

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

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

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

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

}

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

@fred_barney_files = <fred* barney*>;

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

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



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

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

$where = "/usr/etc";

} else (

$where = "/etc";

) Sfiles = <$where/*>;

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

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

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

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

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



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



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



Дескриптор каталога представляет собой соединение с конкретным каталогом. Вместо чтения данных (как из дескриптора файла) вы используете дескриптор каталога для чтения списка имен файлов в этом каталоге. Дескрипторы каталогов всегда открываются только для чтения; нельзя использовать дескриптор каталога для изменения имени файла или удаления файла.

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



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



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

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

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

closedir(ETC);

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



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



Открыв дескриптор каталога, мы можем прочитать список имен с помощью функции readdir, которая принимает единственный параметр — дескриптор каталога. Каждый вызов readdir в скалярном контексте возвращает следующее имя файла (только основное имя: в возвращаемом значении никаких косых нет) в порядке, который на первый взгляд кажется случайным*. Если больше имен нет, readdir возвращает undef**. Вызов readdir в списочном контексте возвращает все оставшиеся имена файлов в виде списка с одним именем, приходящимся на каждый элемент. Вот пример, в котором выдается перечень всех имен файлов, содержащихся в каталоге /etc:



opendir(ETC,"/etc") II die "no etc?: $!";

while ($name = readdir(ETC)) f t скалярный контекст, по одному на цикл

print "$name\n"; #выводит ., .., passwd, group и т.д. 1 closedir(ETC) ;

А вот как можно получить все имена в алфавитном порядке с помощью функции sort:

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

foreach $name (sort readdir(ETC)) ( # списочный контекст с сортировкой

print "$name\n"; #выводит ., .., passwd, group и т.д. ) closedir(ETC) ;

В этот список включены имена файлов, которые начинаются с точки. Это не похоже на результат развертывания, выполненного с использованием <*>, при котором имена, начинающиеся с точки, не возвращаются. С другой стороны, это похоже на результат работы команды echo* shell.

* Точнее говоря — это порядок, в котором имена файлов расположены в каталоге, т.е. тот же "беспорядочный порядок", в котором вы получаете файлы в ОС UNIX в результате вызова команды .find или Is -f.

** Это означает, что при работе с опцией-w вам придется использовать цикл while (defined

($name = readdir (...)).



Упражнения



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

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

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

|     Назад     |     Вперед     |



| Содержание | Предисловие | Введение | Ссылки
| Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10
| Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19
| Приложение А | Приложение Б | Приложение В | Приложение Г |


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


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

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

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

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

name

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

passwd

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

uid

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

gid

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

gcos

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

dir

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

shell

Ваш регистрационный shell, как правило, /bin/sh или /Ып/csh (а, может быть, даже /usr/bin/perl, если вы большой оригинал)

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

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

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

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


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

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

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

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

getpwuid(123) getpwnam("fred")

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

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

$idnum = getpwuid("daemon");

$login °= getpwnam (25);

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

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

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

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

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

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

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

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



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

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

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

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

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

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

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

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

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

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

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

}

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

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



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

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

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

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



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



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

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

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



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

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

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

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

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

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

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

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

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



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

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

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

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

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

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

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

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

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



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

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



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



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

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

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

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

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

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

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

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



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

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

print "Slate's address is ",

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

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



Упражнение



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

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

|     Назад     |     Вперед     |



| Содержание | Предисловие | Введение | Ссылки
| Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10
| Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19
| Приложение А | Приложение Б | Приложение В | Приложение Г |


Другие книги


Programming Perl — полный справочник по Perl, тогда как нашу книгу скорее можно назвать пособием. Если вы хотите больше узнать о регулярных выражениях, используемых в Perl, предлагаем вам книгу Mastering Regular Expressions by Jeffrey E.F. Friedl (O'Reilly & Associates).
    Посмотрите также вышедшие в издательстве O'Reilly & Associates книги CGf Programming on the World Wide Web by Shishir Gundavaram; Web Client Programming with Perl by Clinton Wong; HTML: The Definitive Guide by Chuck Musciano and Bill Kennedy.
    Книги The AWK Programming Language by Aho, Kernighan, and Weinberger (Addison-Wesley) и sed &. awk by Dale Dougherty (O'Reilly & Associates) содержат обширный базовый материал по таким вопросам, как ассоциатив-ные массивы, регулярные выражения и общее мировоззрение, благодаря которому появился Perl. В них приведено множество примеров, которые можно перевести на Perl с помощью конвертора awk-to-perl (а2р) или конвертора sed-to-perl (s2p). Конечно, эти конверторы не выдают совершен-ный Perl-код, но если вы не сможете реализовать один из этих примеров в Perl, выходная информация конвертора даст вам хорошую подсказку.
    Для Web-мастеров мы рекомендуем второе издание книги How to Setup and Maintain a Web Site by Lincoln Stein (Addison Wesley). Д-р Штейн, известный как автор Perl-модуля CGI.pm (см. главу 19), профессионально и всесторонне рассматривает все вопросы, связанные с администрированием Web-узла на платформах UNIX, Mac и Windows.
    Мы также рекомендуем удобный и тщательно проработанный краткий справочник Perl 5 Desktop Reference by Johan Vromans (O'Reilly & Associates).



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


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

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

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

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

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

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

$person = "barney";

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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



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



Извлечь фрагмент строки можно путем осторожного применения регулярних виражений, но если зтот фрагмент всегда находится на известной позиции, такой метод незффективен. В зтом случае удобнее использовать функцию 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"

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



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



Функция printf оказывается удобной, когда нужно взять список значений и создать выходную строку, в которой зти значення отображались бы в заданном виде. Функция sprint f использует такие же аргументы, как и функция printf, но возвращает то, что выдала бы printf, в виде одной строки. (Можете считать ее "строковой функцией printf".) Например, чтобы создать строку, состоящую из буквы х и значення переменной $у, дополненного нулями до пяти разрядов, нужно записать:

$result = sprintf("X%05d",$y);

Описание аргументов функции sprintf вы найдете в разделе sprintf главы 3 книги Programming Perl и на man-странице printf(3) (если она у вас єсть).



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



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



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

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

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

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

sub by_number (

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

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

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

) >

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

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



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

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

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

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

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

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

@rightlist = sort by_number Swronglist;

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

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

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

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

sub by_number ( $а <=> $b;

}

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



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

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

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

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

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

sub by mostly_numeric (

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

)

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

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



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

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

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

@sortedkeys = sort_by_name keys (%names);

sub by_names (

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

) foreach (@sortedkeys) {

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

}

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

sub by_names {

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

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



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



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



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

tr ab ba <indata >outdata

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

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

tr/ab/ba;

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

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

$_ = "fred and barney";

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

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

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

$_ = "fred and barney";

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

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

$ = "fred and barney";

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

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



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

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

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

>

$_ = "fred and barney";

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

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

$_ = "fred and barney";

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

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

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

удалены)

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

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

$_ = "aaabbbcccdefghi";

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

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



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

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

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

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

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

$names = "fred and barney";

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



Упражнения



Ответы к упражнениям см. в приложении А.

1. Напишите программу, которая читает список имен файлов и разбивает каждое имя на начальний и конечний компоненти. (Все, что стоит в имени файла до последней косой черты — начальный компонент, а все, что за ней — конечний компонент. Если косой нет, то все имя является конечним компонентом.) Попробуйте вьшолнить зту программу с име-нами вроде /fred, bamey, fred/barney. Имеют ли результати смисл?

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

3. Напишите программу вивода реальних и регистрационннх имен поль-зователей из файла /etc/passwd с сортировкой по фамилиям пользовате-лей. Работоспособно ли ваше решение в случае, если удвух пользователей одинаковне фамилии?

4. Создайте файл, состоящий из предложений, каждое из которих стоит в отдельной строке. Напишите программу, которая переводит первий символ каждого предложения в верхний регистр, а остальную часть предложения — в нижний. (Работает ли зта программа в случае, если первый символ — небуква? Как решить зту задачу, если предложения не стоят в отдельних строках?)

|     Назад     |     Вперед     |



| Содержание | Предисловие | Введение | Ссылки
| Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10
| Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19
| Приложение А | Приложение Б | Приложение В | Приложение Г |


Форматы


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

Помимо всего прочего, Perl — это, как мы уже говорили, "практический язык извлечений и отчетов". Теперь самое время узнать, почему его называют языком отчетов.

В Perl существует понятие шаблона для написания отчета, который называется форматом. В формате определяется постоянная часть (заголовки столбцов, метки, неизменяемый текст и т.д.) и переменная часть (текущие данные, которые вы указываете в отчете). Структура формата близка собственно к структуре вывода и подобна форматированному выводу в Коболе или выводу при помощи клаузы print using в некоторых реализациях Бейсика.

Использование формата предполагает выполнение трех операций:

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

2. Загрузка данных, подлежащих печати, в переменные части формата (поля).

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

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

* Форматы можно создавать и во время выполнения, пользуясь функцией eval (см. книгу Programming Perl) и man-страницу perlform(l).

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

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

format имя_формата =

строка_полей

значение_один, значение_два, значение_три

строка_полей

значение_один, значение_два

строка_полей

значение_один, значение_два, значение_три

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


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

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

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



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



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

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

format ADDRESSLABEL =

$name

$address

$city, $state, $zip

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

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

while (<ADDRESSES>) f

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

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

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

write (ADDRESSLABEL); # send the output }

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



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

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

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

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

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

Stonehenge

4470 SW Hall Suite 107

Beaverton , OR 97005

Fred Flintstone 3737 Hard Rock Lane Bedrock , OZ 999bc



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



Из примеров вы уже поняли, что поледержатель @““ обозначает выровненное по левому краю поле, которое содержит пять символов, а @“““““ — выровненное по левому краю поле, содержащее одиннадцать символов. Как мы и обещали, опишем поледержатели более подробно.



Текстовые поля



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

Если после знака @ стоят левые угловые скобки (““), поле выровнено по левому краю, т.е. в случае, если значение окажется короче, чем отведенное для него поле, оно будет дополняться справа символами пробела. (Если значение слишком длинное, оно автоматически усекается; структура формата всегда сохраняется.)

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



Наконец, если символы, стоящие после знака @, — повторяющаяся несколько раз вертикальная черта (||||), то поле центрировано. Это означает, что если значение слишком короткое, производится дополнение пробелами с обеих сторон, в результате значение будет расположено по центру поля.



Числовые поля



Следующий тип поледержателя — числовое поле с фиксированной десятичной запятой, полезное для больших финансовых отчетов. Это поле также начинается со знака @; за ним следует один или более знаков # с необязательной точкой (она обозначает десятичную запятую). Опять-таки, знак @ считается одним из символов поля. Например:

format MONEY =

Assets: @#W#.*” Liabilities: @#*”##.*# Net: @t”##*.*#

$assets, $liabilities, $assets-$liabilities

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

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

format MONEY =

Assets: @“““<“ Liabilities: @““““ Net: @““<““

&cool($assets,10), scool($liab,9), Scool($assets-$liab,10)

sub pretty (

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

$width -— 2; # учтем отрицательные числа

$n ° sprintf("%.2f",$n); # sprintf описывается в одной из следующих глав if ($n < 0) (

return sprintf ("[t$width.2f]", -$n); # отрицательные числа

# заключаются в квадратные скобки ) else {

return sprintf (" %$width.2f ", $n); # положительные числа выделяются

^

пробелами } }

## body of program:

$assets = 32125.12;

$liab = 45212.15;

write (MONEY) ;



Многостроковые поля



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



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

format STDOUT = Text Before. @*

$long_string Text After.

$long_string = "Fred\nBarney\nBetty\nWilma\n";

write;

позволяет получить такие выходные данные:

Text Before.

Fred

Barney

Betty

Wilma

Text After.



Заполненные поля



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

Во-первых, заполненное поле обозначается путем замены маркера @ в текстовом поледержателе на символ А (например, /'<“). Соответствующее значение для заполненного поля (в следующей строке формата) должно быть скалярной переменной*, содержащей текст, а не выражением, которое возвращает скалярное значение. Это объясняется тем, что при заполнении этого поледержателя Perl изменяет значение переменной, а значение выражения изменить очень трудно.

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

* А также отдельным скалярным элементом массива или хеша, например, $а[3] или $h{"fred").

** Разделители слов задаются переменной $:.

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



format PEOPLE =

Name: @<““““““ Comment: "““““““““““^“““^ $name, $comment

$comment $comment $comment

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

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

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

format PEOPLE = Name: @““““““< Comment: ^“““““““““““““x

$name, $comment

$comment $comment $comment

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

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



format PEOPLE = Name: @<““““““ Comment: ^“““““““““““““^

$name, $comment

$comment

Таким образом, все будет нормально, сколько бы строк ни занимал комментарий — одну, две или двадцать.

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



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



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

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

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



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

format ADDRESSLABEL_TOP = My Addresses -- Page @< $%

Длина страницы по умолчанию — 60 строк. Этот параметр можно изменить, присвоив значение специальной переменной, о которой вы вскоре узнаете.

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



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



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





Упражнения



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

1. Напишите программу, которая открывает файл /etc/passwd по имени и выводит на экран имя пользователя, идентификатор (номер) пользователя и его реальное имя в форматированных столбцах. Используйте функции format И write.

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

3. Добавьте в начало страницы номер страницы, чтобы при их выводе указывалось page I, page 2 и т.д.

|     Назад     |     Вперед     |



| Содержание | Предисловие | Введение | Ссылки
| Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10
| Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19
| Приложение А | Приложение Б | Приложение В | Приложение Г |


FTP


    Чтобы использовать FTP, вам понадобится компьютер, имеющий непо-средственный выход в Internet. Ниже приведен пример сеанса связи.

% ftp ftp.ora .corn Connected to ftp.uu.net
220 ftp.ora.corn FTP server (Version 6.34 Thu Oct 22 14:32:01 EDT 1992)ready. Name (ftp. ora. com:username) : anonymous 331 Guest login ok, send e-mail address as password. Password: username@hostname
(здесь используйте свое пользовательское имя и хост-имя)
230 Guest login ok, access restrictions apply. ftp> cd /published/oreilly/nu-tshell/learn3.ng_Jperl2 250 CWD command successful. ftp> get README


200 PORT command successful.
150 Opening ASCII mode data connection for README (xxxx bytes). 226 Transfer complete. local: README remote: README


xxxx bytes received in xxx seconds (xxx Kbytes/s) ftp> binary 200 Type set to 1. ftp> get examples, tar.gz 200 PORT command successful.
150 Opening BINARY mode data connection for examples.tar.gz (xxxx DycesJ. 226 Transfer complete, local; exercises remote: exercises xxxx bytes received in xxx seconds (xxx Kbytes/s) ftp> quit 221 Goodbye.



FTPMAIL


FTPMAIL — это почтовый сервер, доступный каждому, кто имеет воз-можность посылать электронную почту на узлы Internet и получать ее оттуда. Доступ к FTPMAIL обеспечивают все провайдеры Internet, предоставляющие услуги электронной почты. Вот как это можно сделать.
    Вы посылаете почту HQ.y3enftpmail@online.ora.com. В теле сообщения дайте FTP-команды, которые хотите выполнить. Сервер запустит для вас FTP-сеанс от имени анонимного пользователя и пошлет вам необходимые файлы. Чтобы получить полный справочный файл, пошлите сообщение без указания темы и с телом, состоящим из одного слова — help. Ниже приведен пример сеанса работы с электронной почтой в UNIX, в ходе которого пользователь получает программы-примеры. Эта команда посылает вам перечень файлов, имеющихся в текущем каталоге, и затребованные файлы примеров. Данный перечень полезен, если существуют более поздние версии примеров, которые вас интересуют.

% mail ftpmail@online. ora. corn Subject: reply-to usernaitie@hostname
(на какой компьютер вы хотите принять файлы)
open

cd /published/oreilly/nutahell/learning_perl2 dir get README mode binary uuencode get examples, tar.gz quit

    Подпись в конце сообщения приемлема, если она стоит после команды quit.



Функции


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

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

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



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



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

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

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

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

} t

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

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

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

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



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



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

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

sub sum_of_a and_b ( return $a + $b;

}

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

$а = 3; $b = 4;

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

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

sub Ii st_o f_a_and_b {



return($a,$b) ;

}

$a = 5; $b = 6;

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

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

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

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

returns $a;

) else (

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

returns $b;

} }

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



Аргументы



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

В Perl после вызова подпрограммы следует список, заключенный в круглые скобки, которые обеспечивают автоматическое присваивание элементов данного списка на период выполнения этой подпрограммы специальной переменной с именем @_. Подпрограмма может обратиться к этой переменной и получить число аргументов и их значения. Например:

sub say_hello_to (

print "hello, $_[0]!\n" # первый параметр }

Здесь мы видим ссылку на $_[0] — первый элемент массива @_. Обратите внимание: несмотря на внешнее сходство, значение $_ [ 0 ] (первый элемент массива @_) не имеет ничего общего с переменной $_ (самостоятельной скалярной переменной). Не путайте их! Из этого кода видно, что подпрограмма приветствует того, чье имя мы указываем в качестве первого параметра. Это значит, что ее можно вызвать так:

say_hello_to("world"); # выдает hello, world $х = "somebody";

say_hello_to($x); # выдает hello, somebody say_hello_to("me")+say_hello_to("you"); # а теперь приветствует себя и вас

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



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

sub say (

print "$_[0], $_[!]!\n";

}

say("hello","world"); # опять hello world

say ("goodbye", "cruel world"); # goodbye cruel world - популярная фраза из фильмов

Избыточные параметры игнорируются, т.е. если вы никогда не заглядываете в $_ [ 3 ], языку Perl это абсолютно все равно. Недостающие параметры также игнорируются, и если вы попытаетесь обратиться за пределы массива @_, как и любого другого массива, то просто получите в ответ undef.

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

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

sub add_two {

return $_[0] + $_[!];

1

print add_two(3,4); # выводит значение 7

$с = add_two(5,6); # $с получает значение 11

Давайте обобщим эту подпрограмму. Что, если нам нужно сложить 3, 4 или 100 значений? Это можно было бы сделать с помощью цикла, например:

sub add (

$sum = 0; # инициализировать сумму

foreach $_ (@_) {

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

}

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

$а = add(4,5,6); # складывает 4+5+6=15 и присваивает переменной $а print add(1,2,3,4,5); # выводит 15 print add (1..5); # тоже выводит 15, потому что список 1..5 раскрывается

Что, если бы переменная с именем $sum использовалась у нас раньше? При вызове подпрограммы add мы просто потеряли бы ее значение. В следующем разделе мы увидим, как избежать этого.



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



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



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



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

$values = "original";

tellmeO ;

spoof() ;

tellmeO ;

sub spoof (

local ($value) = "temporary";

tellmeO ;

> sub telime {

print "Current value is $value\n";

}

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

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

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



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

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

local $_;

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

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

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



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



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

use strict;

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

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

use strict;

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



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

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

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

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

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

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

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

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



Упражнения



Ответы к упражнениям даны в приложении А.

1. Напишите подпрограмму, которая будет принимать в качестве аргумента числовое значение от 1 до 9 и возвращать английское название этого числа (т.е. one, two и т.д.). Если значение не принадлежит указанному диапазону, подпрограмма должна возвратить вместо имени исходное число. Проверьте работу подпрограммы, введя какие-нибудь данные. Для вызова этой подпрограммы вам, наверное, придется написать какой-то код. (Совет: эта подпрограмма не должна выполнять никакого ввода-вывода.)

2. Взяв подпрограмму из предыдущего упражнения, напишите программу, которая будет брать два числа и складывать их, выдавая результат в формате Two plus two equals four. (He забудьте начать первое слово с заглавной буквы!)

3. Модернизируйте эту подпрограмму так, чтобы она возвращала названия от negative nine до negative one и zero (т.е. принимала числа из диапазона от -9 до -1 и нуль). Проверьте, как она будет работать в программе из упражнения 2.

* "Обычная переменная" в этом случае — переменная пакета (поэтому $х — это, по сути дела, $main:: х). Переменные, созданные с помощью my (), ни в один пакет не входят.

|     Назад     |     Вперед     |



| Содержание | Предисловие | Введение | Ссылки
| Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10
| Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19
| Приложение А | Приложение Б | Приложение В | Приложение Г |


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


Упражнения, приведенные в этой книге, можно получить в электронном варианте разными способами: по FTP, FTPMAIL, BITFTP и UUCP. Самые дешевые, самые быстрые и самый легкие способы указаны первыми. Если читать сверху вниз, то самый лучший метод — это, вероятно, первый из тех, которые работают. При непосредственной работе в Internet используйте FTP. Если у вас нет прямого соединения с Internet, но вы можете посылать электронную почту на узлы Internet и получать ее с этих узлов, используйте FTPMAIL. Если вы посылаете электронную почту по BITNET, используйте BITFTP. Если ни один из этих способов не действует, используйте UUCP.
    Примечание: упражнения разрабатывались с помощью UNIX-системы. Если вы работаете в среде UNIX, можете использовать их, не корректируя. При работе на другой платформе эти упражнения, возможно, придется слегка модифицировать. Например, при работе с UNIX каждая строка завершается символом новой строки (возврат каретки подразумевается), тогда как в DOS каждая строка должна завершаться явно указанными символами и новой строки, и возврата каретки. В зависимости от конфигурации вашей системы и используемого метода пересылки упражнений по сети вам, возможно, придется добавить символы возврата каретки. Дополнительную информацию по этому вопросу можно найти в файле README, который прилагается к упражнениям.



Хеши


Что такое хеш

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

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

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

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

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

** Модули типа 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 (как и при обращении к отсутствующему элементу массива или к неопределенной скалярной переменной).



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



У вас может возникнуть необходимость обратиться к хешу целиком — например, чтобы инициализировать его или скопировать в другой хеш. Фактически в 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 они превратятся в один элемент, поэтому данную операцию лучше всего выполнять только над хешами с уникальными ключами и значениями.



Хеш-функции



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



Функция 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 только одна пара ключ-значение



Срезы хешей



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

$score("fred"} - 205;

$score("barney"} = 195;

$scoref"dino"} = 30;

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

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

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

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

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

@players = qwffred barney dino);

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

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

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

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

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



Упражнения



Ответы к упражнениям даны в приложении А.

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

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

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


|     Назад     |     Вперед     |

| Содержание | Предисловие | Введение | Ссылки
| Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10
| Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19
| Приложение А | Приложение Б | Приложение В | Приложение Г |