Базовые средства вводавывода^ 1 Вот один из способов решения этой задачи
print reverse о;
Вас, может быть, удивит краткость этого ответа, но он, тем не менее, верен. Вот как работает этот механизм:
а) Сначала функция reverse ищет список своих аргументов. Это значит, что операция "ромб" (О) выполняется в списочном контексте. Следовательно, все строки файлов, указанных как аргументы командной строки (или данные, поступающие со стандартного ввода, если аргументов нет), считываются и преобразуются в список, каждый элемент которого состоит из одной строки.
б) Затем функция reverse меняет порядок следования элементов списка на обратный.
в) Наконец, функция print получает список-результат и выводит его. И. Вот один из способов решения этой задачи:
print "List of strings:\n";
chomp(@strings = <STDIN>) ;
foreach (@strings) (
printf "%20s\n", $_;
)
Первая строка приглашает ввести список строк.
Следующая строка считывает все строки в один массив и избавляется от символов новой строки.
В цикле foreach осуществляется проход по этому массиву с присвоением переменной $_ значения каждой строки.
Функция printf получает два аргумента. Первый аргумент определяет формат "%20s\n", который означает 20-символьный столбец с выравниванием справа и символ новой строки.
3. Вот один из способов решения этой задачи:
print "Field width: ";
chomp($width = <STDIN>) ;
print "List of strings:\n";
chomp(@strings = <STDIN>);
foreach (@strings) (
printf "%$(width}s\n", $_;
}
В решение, данное к предыдущей задаче, мы добавили приглашение ввести ширину поля и ответ на него.
Есть еще одно изменение: строка формата printf теперь содержит ссылку на переменную. Значение переменной $width включается в эту строку до того, как printf использует данный формат. Отметим, что мы не можем записать эту строку как
printf "%$widths\n", $_; #WRONG
потому что тогда Perl искал бы переменную с именем $ widths, а не переменную с именем $width, к которой мы прибавляем букву s. По-другому это можно записать так:
printf "%$width"."s\n", $_; * RIGHT
потому что символ конца строки завершает также имя переменной, защищая следующий символ от присоединения к имени.
Изучаем Perl
Глава 7
В этой главе:
Основные понятия
Основные направления использования регулярных выражений
Образцы
Еще об операции сопоставления
Операция замены
Функции split и join
Упражнения
Регулярные выражения"
Глава 7 "Регулярные выражения"
1. Вот несколько возможных ответов:
а) /а+ь*/
б) /\\*\**/ (Вспомним, что обратная косая черта отменяет значение следующего за ней специального символа.)
в) / ($whatever) {3} / (Не забудьте про круглые скобки, иначе множитель будет действовать только на последний символ $whatever; этот вариант не проходит также в том случае, если $whatever содержит специальные символы.)
г) /[\000-\377] (5}/ или /(. |\п) (5)/ (Использовать точку без дополнительных знаков здесь нельзя, потому что она не соответствует символу новой строки.)
д) / (л l \s) (\s+) (\s+\2)+ (\s | $) / (\s — это не пробельный символ, а \2 — ссылка на все, что есть "слово"; знак " или альтернативный пробельный символ гарантирует, что \s+ начинается на границе пробельного символа.)
2. а) Вот один из способов решения этой задачи:
while (<STDIN>) {
if (/a/i && /e/i &S /i/i &S /o/i && /u/i) ( print;
)
Здесь у нас приведено выражение, состоящее из пяти операций сопоставления. Все эти операции проверяют содержимое переменной $_, куда управляющее выражение цикла while помещает каждую строку. Выражение даст значение "истина" лишь в том случае, если будут найдены все пять гласных.
Обратите внимание: если любая из пяти гласных не обнаруживается, остальная часть выражения пропускается, потому что операция && не вычисляет свой правый аргумент, если значение левого аргумента — "ложь".
б) Еще один способ:
while (<STDIN>) (
if (/a.*e.*i.*o.*u/i) ( print;
} )
Этот ответ, как оказывается, проще. Здесь у нас в операторе if используется более простое регулярное выражение, которое ищет пять гласных в указанной последовательности, разделенных любым количеством символов.
в) Вот один из способов решения этой задачи:
while “STDIN” (
if (/"[eiou]*a[лiou]*e[лaou]*i[^aeu]*o[лaei]*u["aeio]*$/i) ( print;
> )
Выглядит уродливо, но работает. Чтобы написать такое, просто подумайте: "Что может стоять между началом строки и первой буквой а?", а затем "Что может стоять между первой буквой а и первой буквой е?". В конце концов все решится само, от вас потребуется минимум усилий.
3. Вот один из способов решения этой задачи:
while (<STDIN>) {
chomp;
($user, $gcos) = (split /:/)[0,4];
($real) = split (/,/, $gcos) ;
print "$user is $real\n";
}
Во внешнем цикле while производится считывание по одной строке из файла паролей в переменную $_. По достижении последней строки цикл завершается.
Вторая строка тела цикла while означает разбиение строки на отдельные переменные с использованием в качестве разделителя двоеточия. Два из этих семи значений заносятся в отдельные скалярные переменные с имеющими смысл (мы надеемся) именами.
Поле GCOS (пятое поле) затем разбивается на части с использованием в качестве разделителя символа запятой, и список-результат присваивается одной скалярной переменной, заключенной в круглые скобки. Эти скобки играют важную роль — они указывают, что операция присваивания должна быть не скалярной, а для массива. Скалярная переменная $геа1 получает первый элемент списка-результата, а остальные элементы отбрасываются.
Оператор print затем выводит результаты на экран.
4. Вот один из способов решения этой задачи:
while (<STDIM>) (
chomp;
($gcos) = (split /:/)[4];
($real) =split(/,/, $gcos);
($first) ° split(/\s+/, $real);
$seen($first>++;
} foreach (keys %seen) (
if ($seen($_) > 1) {
print "$_ was seen $seen($_) times\n";
) }
Цикл while работает во многом так же, как цикл while из предыдущего упражнения. Помимо разбивки строки на поля и поля GCOS на реальное имя (и другие компоненты), в этом цикле осуществляется также разбиение реального имени на собственно имя и остальную часть. После определения имени элемент хеша в %seen инкрементируется, отмечая тот факт, что мы нашли конкретное имя. Обратите внимание: оператор print в этом цикле не используется.
В цикле foreach осуществляется проход по всем ключам хеша %seen (именам из файла паролей) с присваиванием каждого из них по очереди переменной $_. Если значение, записанное в %seen по данному ключу, больше 1, значит, это имя уже встречалось. Оператор if проверяет, так ли это, и при необходимости выводит соответствующее сообщение.
5. Вот один из способов решения этой задачи:
while (<STDIN>) (
chomp;
($user, $gcos) = (split /:/)[0,4];
($real) = split /,/, $gcos;
($first) = split (/\s+/, $real);
$seen($first) .= " $user";
}
foreach (keys %names) (
$this == $names{$_);
if ($this =~ /. /) {
print "$_ is used by:?this\n";
} }
Эта программа похожа на ответ к предыдущему упражнению, но вместо того чтобы просто подсчитывать, сколько раз у нас встречалось определенное имя, мы присоединяем регистрационное имя пользователя к элементу хеша % names, указывая имя в качестве ключа. Так, для Фреда Роджерса (регистрационное имя mrrogers) $names {"Fred"} становится равным " mrrogers", а когда появляется Фред Флинтстоун (регистрационное имя fred), $names ( "Fred" } становится равным " mrrogers fred". После завершения цикла у нас имеется список имен и список регистрационных имен всех имеющих данное имя пользователей.
В цикле foreach, как и в ответе к предыдущему упражнению, осуществляется проход по полученному в результате хешу, но вместо того, чтобы проверять, больше ли единицы значение элемента хеша, мы должны проверить, есть ли в этом значении более одного регистрационного имени. Для этого нужно занести значение в скалярную переменную $this и посмотреть, есть ли в нем после какого-нибудь символа пробел. Если есть, то одному реальному имени соответствуют несколько регистрационных имен, которые и указываются в выдаваемом в результате сообщении.
Изучаем Perl
Глава 8
В этой главе:
Определение пользовательской функции
Вызов пользовательской функции
Возвращаемые значения
Аргументы
Локальные переменные в функциях
Полулокальные переменные, созданные при помощи функции local
Создаваемые операцией my() переменные файлового уровня
Упражнения
Функции" 1 Вот один из способов решения этой задачи
sub card {
my %card_map;
@card_map(l..9} = qw (
one two three four five six seven eight nine );
my($num) = @_;
if ($card_map($num}) {
return $card_map($num};
) else (
return $num;
) } # driver routine:
while (0) {
chomp;
print "card of $_ is ", &card($ ), "\n";
)
Подпрограмма scard (названная так потому, что она возвращает название на английском языке для данного числа) начинается с инициализации хеша-константы, который называется %card_map. Значения ему присваиваются так, что, например, $card_map {6} равно six; это делает преобразование достаточно простым.
С помощью оператора if мы определяем, принадлежит ли значение заданному диапазону, отыскивая это число в хеше: если в хеше имеется соответствующий элемент, проверка дает значение "истина", и данный элемент, являющийся соответствующим именем числительным, возвращается. Если соответствующего элемента нет (например, когда $num равно 11 или -4), то поиск в хеше возвращает значение undef и выполняется ветвь else оператора if, возвращая исходное число. Весь цикл, задаваемый оператором if, можно заменить одним выражением:
$card map($num) || $num;
Если значение слева от | | истинно, то это — значение всего выражения, которое затем и возвращается. Если оно ложно (например, когда значение переменной $num выпадает из диапазона), то вычисляется правая часть операции | |, возвращая значение $num.
Подпрограмма-драйвер последовательно получает строки, отсекает символы новой строки и передает их по одной в программу &card, выводя результат.
2. Вот один из способов решения этой задачи:
sub card ( ...; } # из предыдущего ответа print "Enter first number: ";
chomp($first = <STDIN>) ;
print "Enter second number: "; , chomp($second = <STDIN>) ;
$message = card($first) . " plus " .
card($second) . " equals " .
card($first+$second) . ".\n";
print "\u$message";
Первые два оператора print приглашают ввести два числа, а операторы, следующие сразу же за ними, считывают эти значения в $first и $second.
Затем путем троекратного вызова &card — по одному разу для каждого значения и один раз для суммы — формируется строка $message.
После формирования сообщения его первый символ с помощью операции \и переводится в верхний регистр. Затем сообщение выводится на экран.
3. Вот один из способов решения этой задачи:
sub card f
my %card_map;
@card_map(0..9} = qw (
zero one two three four five six seven eight nine );
my($num) = @_;
my($negative) ;
if ($num < 0) {
$negative = "negative ";
$num = - $num;
) if ($card_map($num)) (
return $negative . $card_map($num};
} else (
return $negative . $num;
)
Здесь мы объявили массив %card_map, чтобы обнулять его значения.
Первый оператор if инвертирует знак переменной $num и присваивает переменной $negative в качестве значения слово negative, если задаваемое в качестве аргумента число меньше нуля. После действия оператора if значение $num всегда неотрицательное, но при этом в переменную $negative записывается строка negative, которая в дальнейшем используется как префикс.
Второй оператор if определяет, находится ли значение переменной $num (теперь положительное) в хеше. Если да, то полученное в результате значение хеша присоединяется к префиксу, который хранится в $ negative, и возвращается. Если нет, то значение, содержащееся в $negative, присоединяется к исходному числу.
Последний оператор if можно заменить выражением:
$negative . ($card_map{$num) || $num) ;
Разнообразные управляющие структуры "
Глава 9 "Разнообразные управляющие структуры "
1. Вот один из способов решения этой задачи:
sub card (} # из предыдущего упражнения
while О ( ## НОВОЕ ##
print "Enter first number: ";
chomp($first = <STDIN>) ;
last if $first eq "end"; ## НОВОЕ ##
print "Enter second number: ";
chomp($second = <STDIN>) ;
last if $second eq "end"; ## НОВОЕ ##
$message = Scard ($first) . " plus " .
card($second) . " equals " .
card($first+$second) . ".\n";
print "\u$message";
} ## НОВОЕ ##
Обратите внимание на появление цикла while и двух операций last. Вот так-то!
Изучаем Perl
Глава 10
В этой главе:
Что такое дескриптор файла
Открытие и закрытие дескриптора файла
Небольшое отступление: функция die
Использование дескрипторов файлов
Операции для проверки файлов
Функции stat и Istat
Упражнения
Дескрипторы файлов и проверка файлов"
Глава 10 "Дескрипторы файлов и проверка файлов"
1. Вот один из способов решения этой задачи:
print "What file? ";
chomp($filename = <STDIN>);
open(THATFILE, "$filename") ||
die "cannot open Sfilename: $!";
while (<THATFILE>) (
print "$filename: $_"; # предполагается, что $ заканчивается \п }
В первых двух строках дается приглашение ввести имя файла, который затем открывается с дескриптором т hat file. Содержимое этого файла считывается с помощью дескриптора и выводится в stdout.
2. Вот один из способов решения этой задачи:
print "Input file name: ";
chomp($infilename = <STDIN>);
print "Output file name: ";
chomp($outfilename = <STDIN>);
print "Search string: ";
chomp($search = <STDIN>);
print "Replacement string: ";
chomp($replace = <STDIN>);
open(IN,$infilename) II
die "cannot open $infilename for reading: $!";
## необязательная проверка существования файла
## $outfilename
die "will not overwrite $outfilename" if -e $outfilename;
open (OUT,"$outfilename") ||
die "cannot create $outfilename: $!";
while (<IN>) { # читать строку из файла IN в $_
s/$search/$replace/g; # change the lines
print OUT $_; # вывести эту строку в файл OUT ) close (IN);
close (OUT) ;
Эта программа основана на программе копирования файлов, описанной выше в этой главе. К новым особенностям здесь относятся приглашение вводить строки и команда подстановки в середине цикла while, а также проверка возможности уничтожения уже существующего файла.
Обратите внимание на то, что обратные ссылки в регулярном выражении работают, а вот обращение к памяти в заменяющей строке — нет.
3. Вот один из способов решения этой задачи:
while (о) (
chomp; t удалить символ новой строки
print "$_ is readable\n" if -r;
print "$_ is writable\n" if -w;
print "$_ is executable\n" if -x;
print "$_ does not exist\n" unless -e;
}
При каждом выполнении цикла while читается имя файла. После удаления символа новой строки файл проверяется (с помощью остальных операторов) на наличие различных прав доступа.
4. Вот один из способов решения этой задачи:
while (<>) (
chomp;
$аде = -М;
if ($oldest_age < $аде) ( $oldest_name = $_;
$oldest_age = $аде;
} > print "The oldest file is $oldest_name ",
"and is $oldest age days old.\n";
Сначала мы выполняем цикл для каждого считываемого имени файла. Символ новой строки отбрасывается, а затем с помощью операции -м вычисляется возраст файла в днях. Если возраст превышает возраст самого старого из файлов, которые мы до сих пор видели, мы запоминаем имя файла и его возраст. Первоначально $oldest_age = 0, поэтому мы рассчитываем на то, что имеется хотя бы один файл, возраст которого больше 0 дней.
По завершении цикла оператор print выдает отчет.
Изучаем Perl
Глава 11
В этой главе:
Что такое формат
Определение формата
Вызов формата
Еще о поледержателях
Формат начала страницы
Изменение в форматах установок по умолчанию
Упражнения
Форматы"
Глава 11 "Форматы"
1. Вот один из способов решения этой задачи:
open(PW,"/etc/passwd") II die "How did you get logged in?";
while (<PW>) (
($user,$uid,$gcos) = (split /:/)[0,2,4];
($real) ° split /,/,$gcos;
write;
(
format STDOUT =
@“<““ @>””> @“““““““““““““““
$user, $uid, $real
Первая строка открывает файл паролей. В цикле while этот файл обрабатывается построчно. Для того чтобы можно было загрузить скалярные переменные, каждая строка разбивается на части; в качестве разделителя используется двоеточие. Реальное имя пользователя выбирается из поля GCOS. Последний оператор цикла while вызывает функцию write для вывода всех данных.
Формат дескриптора файла stdout определяет простую строку с тремя полями. Их значения берутся из трех скалярных переменных, значения которым присваиваются в цикле while.
2. Вот один из способов решения этой задачи:
# прибавить к программе из первой задачи... format STDOOT_TOP = Username User ID Real Name
Все, что нужно для добавления к предыдущей программе заголовков страниц,— это добавить формат начала страницы. Указанным выражением мы помещаем заголовки в столбцы.
Чтобы выровнять столбцы, мы скопировали текст формата stdout и, используя в нашем текстовом редакторе режим замены, заменили поля @<“ линиями ====.Это можно сделать благодаря существованию посимвольного соответствия между форматом и получаемым результатом.
3. Вот один из способов решения этой задачи:
# прибавить к программе из первой задачи.. . format STDOUT_TOP = Page @<“ $%
Username User ID Real Name
Здесь для получения заголовков страниц мы опять-таки ввели формат начала страницы. Этот формат содержит также ссылку на переменную $%, которая автоматически присваивает странице номер.
Изучаем Perl
Глава 12
В этой главе:
Перемещение по дереву каталогов
Развертывание
Дескрипторы каталогов
Открытие и закрытие дескриптора каталога
Чтение дескриптора каталога
Упражнения
Доступ к каталогам"
Глава 12 "Доступ к каталогам"
1. Вот один из способов решения этой задачи:
print "Where to? ";
chomp($newdir = <STDIN>) ;
chdir($newdir) II die "Cannot chdir to $newdir: $!";
foreach (<*>) { print "$_\n";
}
В первых двух строках запрашивается и считывается имя каталога.
С помощью третьей строки мы пытаемся перейти в каталог с указанным именем. В случае неудачи программа аварийно завершается.
В цикле foreach осуществляется проход по списку. Но что это за список? Это развертывание в списочном контексте, в результате которого мы получим список всех имен файлов, совпадающих с образцом (в данном случае он задан как *).
2. Вот один из способов решения этой задачи — с помощью дескриптора каталога:
print "Where to? ";
chomp ($newdir = <STDIN>) ;
chdir($newdir) ||
die "Cannot chdir to $newdir: $!";
opendir(DOT,".") |I
die "Cannot opendir . (serious dainbramage): $!";
foreach (sort readdir(DOT)) { print "$_\n";
) closedir(DOT) ;
Так же, как в предыдущей программе, мы запрашиваем новый каталог. Перейдя в него посредством команды chdir, мы открываем каталог, создавая дескриптор каталога dot. В цикле foreach список, возвращенный функцией readdir (в списочном контексте) сортируется, а затем просматривается с присваиванием переменной $_ значения каждого элемента.
А вот как сделать это с помощью развертывания:
print "Where to? ";
chomp($newdir = <STDIN>) ;
chdir($newdir) || die "Cannot chdir to $newdir: $!";
foreach (sort <* .*>) ( print "$_\n";
)
Да, это, по сути дела, еще одна программа из предыдущего упражнения, но мы вставили перед операцией развертывания операцию sort и дополнили образец символами .*, чтобы найти файлы, имена которых начинаются с точки. Операция sort нам нужна по той причине, что файл !fred должен стоять перед точечными файлами, a barney —после них, но простого способа, позволяющего расставить их в таком порядке при помощи shell, нет.
Изучаем Perl
Глава 13
В этой главе:
Удаление файла
Переименование файла
Создание для файла альтернативных имен: связывание ссылками
Создание и удаление каталогов
Изменение прав доступа
Изменение принадлежности
Изменение меток времени
Упражнения
Манипулирование файлами и каталогамиff 1 Вот один из способов решения этой задачи
unlink @ARGV;
Да, именно так. Массив @argv — это список имен, подлежащих удалению. Операция unlink получает список имен, поэтому нам нужно лишь соединить два этих компонента, и дело сделано.
Конечно, здесь не реализован ни механизм уведомления об ошибках, ни опции -f и -i, ни другие подобные вещи, но это было бы уже слишком серьезно. Если вы это сделали — отлично!
2. Вот один из способов решения этой задачи:
($old, $new) ” @ARGV; # назвать их
if (-d $new) ( # новое имя — каталог, его нужно откорректировать ($basename = $old) =~ s#.*/##s; # получить название собственно
# каталога $old $new .= "/$basename"; # и добавить его к новому имени > rename($old,$new) [| die "Cannot rename $old to $new: $!";
Рабочая лошадка в этой программе — последняя строка, но все остальное тоже нужно на тот случай, если новое имя принадлежит каталогу.
Сначала мы даем наглядные имена двум элементам массива oargv. Затем, если имя $new — каталог, нам нужно откорректировать его, добавив в конец нового имени собственно имя каталога $old. Это значит, что переименование /usr/src/fred в /etc фактически приводит к переименованию /usr/src/fred в /etc/fred.
Наконец, после добавления собственно имени каталога мы завершаем задачу вызовом rename.
3. Вот один из способов решения этой задачи:
($old, $new) = 3ARGV; # назвать их
if (-d $new) ( # новое имя — каталог, его нужно откорректировать ($basename = $old) =~ s#.*/##s; # получить название собственно
# каталога $old $new .= "/$basenaroe"; # и добавить его к новому имени } link($old,$new) || die "Cannot link $old to $new: $!";
Эта программа идентична предыдущей программе за исключением самой последней строки, потому что мы создаем ссылку, а не выполняем переименование.
4. Вот один из способов решения этой задачи:
if ($ARGV[0] eq "-s") ( # нужна символическая ссылка ;
$symlink++; # запомнить shift(@ARGV); # и отбросить флаг -s
> • • ! • .. , . ' .' . ($old, $new) = @ARGV; * назвать их
if (-d $new) ( # новое имя — каталог, его нужно откорректировать
($basename = $old) =~ s#.*/##s; # получить название собственно # каталога $old
$new .= "/$basename"; # и добавить его к новому имени > if ($symlink) ( # wants a symlink
symlink($old,$new) ;
) else ( # нужна жесткая ссылка
link($old,$new); , )
Средняя часть этой программы — такая же, как в предыдущих двух упражнениях. Новые здесь — несколько первых и несколько последних строк.
В первых строках осуществляется проверка первого аргумента программы. Если этот аргумент равен -s, то скалярная переменная $ symlink инкрементируется, получая в результате значение 1. Затем выполняется сдвиг массива @argv, в результате чего удаляется флаг -s. Если флаг -s отсутствует, то ничего не делается и $symlink остается равной undef. Сдвиг массива @argv выполняется достаточно часто, поэтому имя массива @argv является аргументом функции shift по умолчанию; то есть вместо
shift(@ARGV) ;
мы могли бы написать
shift;
Последние несколько строк проверяют значение $symlink. Оно будет равно либо 1, либо undef, и на основании этого для файлов выполняется либо операция symlink, либо link.
5. Вот один из способов решения этой задачи:
foreach $f (<*>) {
print "$f -> $where\n" if defined($where =• readlink($f));
}
Скалярной переменной $f присваивается по очереди каждое из имен файлов текущего каталога. Для каждого имени переменной $where присваивается значение, полученное в результате выполнения функции readlink () для данного имени. Если имя — не символическая ссылка, то операция readlink возвращает undef, давая значение "ложь" в
проверке if, a print пропускается. Если же операция readlink возвращает какое-то значение, то print выводит название символической ссылки и имя файла или директории, на которую она указывает.
Изучаем Perl
Глава 14
В этой главе:
Использование функций system и ехес
Использование обратных кавычек
Использование процессов как дескрипторов файлов
Использование функции fork
Сводка операций, проводимых над процессами
Передача и прием сигналов
Упражнения
Управление процессами"
Глава 14 "Управление процессами"
1. Вот один из способов решения этой задачи:
if ('date' =~ /"S/) (
print "Go play!\n";
} else (
print "Get to work!\n";
}
Оказывается, команда date выводит букву s в качестве первого символа только по выходным (Sat или sun), что делает задачу тривиальной. Мы вызываем date, затем с помощью регулярного выражения смотрим, является ли первый символ буквой s. На основании результата мы выводим одно сообщение или другое.
2. Вот один из способов решения этой задачи:
open(PW,"/etc/passwd") ;
while (<PW>) {
chomp;
($user,$gcos) = (split /:/)[0,4];
($real) = split (/,/, $gcos); '
$real($user) = $real;
)• close(PW);
open(WHO,"who I") || die "cannot open who pipe";
while (<WHO>) (
($login, $rest) =/" (\S+) \s+(.*)/;
$login = $real($login) if $real($login);
printf "%-30s ts\n",$login,$rest;
}
В первом цикле создается хеш %real, ключи которого — регистрационные имена, а значения — соответствующие реальные имена. Этот хеш используется в следующем цикле для замены регистрационного имени на реальное.
Во втором цикле осуществляется просмотр результата выполнения команды who, которая вызвана при помощи дескриптора файла. Каждая строка полученного в результате выражения разбивается путем сопоставления с регулярным выражением в списочном контексте. Первое слово строки (регистрационное имя) заменяется реальным именем из хеша, но только если оно существует. После всего этого с помощью функции printf результат помещается в stdout.
Операции открытия дескриптора файла и начала цикла можно заменить строкой
foreach $_ ('who') (
с тем же результатом. Единственное различие состоит в том, что вариант программы с использованием дескриптора файла может приступить к работе, как только who начнет выдавать символы, тогда как в варианте с функцией who в обратных кавычках придется ждать завершения выполнения функции who.
3. Вот один из способов решения этой задачи:
open(PW,"/etc/passwd");
while (<PW>) (
chomp;
($user,$gcos) = (split /:/)[0,4];
($real) = split(/,/, $gcos);
$real($user} •= $real;
} close(PW) ;
open(LPR,"1Ipr") I I die "cannot open LPR pipe";
open (WHO,"who]") || die "cannot open who pipe";
while (<WHO>) {
# или заменить предыдущие две строки на foreach $_ ('who') (
($login, $rest) = /л (\S+) \s+(.*)/;
$login = $real($loginl if $real($login} ;
printf LPR "%-30s %s\n",$login,$rest;
}
Разница между этой программой и программой из предыдущего упражнения состоит в том, что мы добавили дескриптор файла lpr, открытый для процесса Ipr, и модифицировали оператор printf так, чтобы он посылал данные не в stdout, а в этот дескриптор.
4. Вот один из способов решения этой задачи:
sub mkdir f
'system "/bin/mkdir", @_;
}
Здесь команда mkdir получает аргументы прямо из аргументов подпрограммы. Однако возвращаемое значение должно подвергнуться операции логического отрицания, потому что ненулевой код выхода из system должен быть преобразован в значение "ложь" для вызывающей Perl-программы.
5. Вот один из способов решения этой задачи:
sub mkdir (
my($dir, $mode) = 8_;
('system "/bin/mkdir", $dir) && chmod($mode, $dir) ;
1
Сначала мы описываем локальные аргументы этой подпрограммы — $dir и $ mode. Затем мы вызываем mkdir для каталога с именем $dir. В случае успеха операция chmod присваивает этому каталогу соответствующие права доступа.
Изучаем Perl
Глава 15
В этой главе:
Поиск подстроки
Извлечение и замена подстроки
Форматирование данных с помощью функции sprintf()
Сортировка по заданным критериям
Транслитерация
Упражнения
Другие операции преобразования данных " 1 Вот один из способов решения этой задачи
while (о) { chomp;
$slash = rindex ($_,"/");
if ($slash > -1) (
$head = substr($_,0,$slash);
$tail = substr($_,$slash+l);
} else (
($head,$tail) = ("", $_) ;
) print "head = '$head', tail = '$tail'\n";
>
Каждая строка, прочитанная операцией "ромб", сначала пропускается через операцию chomp, которая удаляет символ новой строки. Затем с помощью rindex () мы ищем в этой строке крайнюю справа косую черту. Следующие две строки разбивают строку на части, используя substr (). Если косой черты нет, то результат rindex равен -1, и этот вариант мы не рассматриваем. Последняя строка цикла выводит результат.
2. Вот один из способов решения данной задачи:
chomp(@nums = <STDIM>); # обратите внимание на особый случай
# использования chomp @nuros = sort ( $а <=> $b } @nums;
foreach (@nums) (
printf "%30g\n", $_;
}
В первой строке в массив @nums вводятся все числа. Во второй строке этот массив сортируется в числовом порядке, для чего используется встроенный оператор. Цикл foreach обеспечивает вывод результатов.
3. Вот один из способов решения этой задачи:
open(PW,"/etc/passwd") || die "How did you get logged in?";
while “PW” (
chomp;
($user, $gcos) = (split /:/)[0,4];
($real) = split!/,/, $gcos) ;
$real($user} = $real;
($last) = (split /\s+/, $real)[-l];
$last{$user} = "\L$last";
} close(PW) ;
for (sort by_last keys %last) (
printf "%30s %8s\n", $real($_}, $_;
>
sub by_last ( ($last($a} cmp $last($b}) || ($a cmp $b). }
В первом цикле создается хеш %last, ключи которого — регистрационные имена, а соответствующие им значения — фамилии пользователей, и хеш %геа1, содержащий полные реальные имена пользователей. Все символы переводятся в нижний регистр, чтобы, к примеру, FLINT-STONE, Flintstone и flintstone стояли рядом друг с другом.
Во втором цикле выводится %геа1, упорядоченный по значениям %iast. Это делается с использованием определения сортировки, предоставленного подпрограммой by_last.
4. Вот один из способов решения этой задачи:
while (<>) (
substr($_,0,I) =~ tr/a-z/A-Z/;
substr($_,!) — tr/A-Z/a-z/;
print;
1
Для каждой строки, прочитанной операцией "ромб", мы используем две операции tr — по одной для разных частей строки. Первая операция tr переводит первый символ строки в верхний регистр, а вторая переводит все остальные символы в нижний регистр. Результат выводится.
Вот другое решение, с использованием только строковых операций с двойными кавычками:
while (О) {
print "\u\L$_";
}
Если вы самостоятельно нашли это решение, поставьте себе дополнительно пять баллов.
Изучаем Perl
Глава 16
В этой главе:
Получение информации о паролях и группах
Упаковка и распаковка двоичных данных
Получение информации о сети
Упражнение
Доступ к системным базам данныхff 1 Вот один из способов решения этой задачи
while (@pw == getpwent) {
($user, $gid, $gcos) - @pw(0,3,6);
($real) = split /,/, $gcos;
$real($user) = $real;
$members($gid} .= " $user";
($last) = (split /\s+/, $real)(-l);
51ast($user) " "\L$last";
)
while (@gr - getgrent) (
($gnarae, $gid, $meinbers) = @gr[ 0,2,3);
$rnembers( $gid) .=“ " $merabers";
$gname($gid) - $gname;
)
for $gid (sort by_gname keys %gname) (
tall = ();
for (split (/\s+/, $members($gidl)) ( $all{$_)++ if length $_;
1
Omembers = () ;
foreach (sort by_last keys %all) (
push(@members, "$real($_} ($_)");
}
$meinberlist = join(", ", @members);
write;
)
sub by_gname ( $gname($al cmp $gname($bl; ) sub by_last ( ($last(a) cmp $last($b)) || ($a cmp $b); )
format STDOUT = @<<<< @<<<< A<<<<<<<<<<<<<<<<<<<
$gname($gid), "($gid)", $memberlist
^<<<<<<<<<<<<<<<<<<<
$memberlist
Да, в этом не так просто разобраться, но вам это придется сделать самостоятельно, чтобы проверить усвоенные знания.
Изучаем Perl
Глава 17
В этой главе:
DBM-базы данных и DBM-хеши
Открытие и закрытие DBM-хешей
Использование DBM-хеша
Базы данных произвольного доступа с записями фиксированной длины
Базы данных с записями переменной длины (текстовые)
Упражнения
Работа с пользовательскими базами данных" 1 Вот один из способов решения этой задачи
dbmopen(%ALIAS, "/etc/aliases", undef) II
die "No aliases!: $!";
while (($key,$value) - each(tALIAS)) (
chop($key,$value) ;
print "$key $value\n";
1
Первая строка открывает DBM псевдонимов. (В вашей системе DBM псевдонимов может находиться в каталоге /usr/lib/aliases — попробуйте этот вариант, если наш не сработает.) В цикле while осуществляется проход по DBM-массиву. Первая строка цикла служит для удаления символа NUL, которым завершались ключ и значение. Последняя строка цикла обеспечивает вывод результата.
2. Вот один из способов решения этой задачи:
# program 1:
dbmopen(%WORDS,"words",0644) ;
while (О) {
foreach $word (split(/\W+/)) ( $WORDS($word)++;
> } dbmclose(%WORDS) ;
Первая программа (записывающая) открывает DBM words в текущем каталоге, создавая файлы words, dir и words.pag. Цикл while получает каждую строку, используя операцию "ромб". Эта строка разбивается с помощью операции split и разделителя /\w+/, который обозначает нетекстовые символы. Затем производится подсчет всех слов, имеющихся в DBM-массиве, причем для осуществления прохода в цикле по всем словам используется оператор foreach.
# program 2:
dbmopen(%WORDS,"words",undef) ;
foreach $word (sort { SWORDS)$b} <=> SWORDS($a[ } keys %WORDS) ( print "Sword SWORDS(Sword)\n";
1 dbmclose(%WORDS) ;
Вторая программа также открывает DBM words в текущем каталоге. Сложная на первый взгляд строка foreach делает почти всю "грязную" работу. При каждом выполнении цикла переменной $word в качестве значения будет присваиваться следующий элемент списка. Этот список состоит из ключей хеша %words, рассортированных по их значениям (т.е. количеству повторений) в убывающем порядке. Для каждого элемента списка мы выводим слово и количество его повторений.
Изучаем Perl
Глава 18
В этой главе:
Преобразование awk-программ в Perl-программы
Преобразование sed-программ в Perl-программы
Преобразование shell-сценариев в Perl-программы
Упражнение
Преобразование других
for (;;) (
($user,$home) = (getpwent)[0,7];
last unless $user;
next unless open(N,"$home/.newsrc");
next unless -M N < 30; ## added value :-) while (<N>) f
if (/^comp\ . lang\ .perl\ .announce: /) { print "$user is a good person, ", "and reads comp.lang.peri.announce!\n");
last;
} } }
Самый внешний цикл — это цикл for, который выполняется "вечно";
выход из этого цикла осуществляется посредством операции last, стоящей внутри него. При каждом выполнении цикла операция getpwent выбирает новое значение переменной $user (имя пользователя) и переменной $home (его начальный каталог).
Если значение $user пусто, осуществляется выход из цикла for. Следующие две строки ищут последний файл .newsrc в начальном каталоге пользователя. Если этот файл открыть нельзя или если он изменялся слишком давно, запускается следующая итерация цикла for.
В цикле while осуществляется чтение по одной строке из файла .newsrc. Если строка начинается с comp.lang.perl.announce:, то оператор print сообщает об этом, и осуществляется преждевременный выход из цикла
while.
Изучаем Perl
Глава 19
В этой главе:
Модуль CGI.pm
Ваша CGI-программа в контексте
Простейшая CGI-программа
Передача параметров через CGI
Как сократить объем вводимого текста
Генерирование формы
Другие компоненты формы
Создание CGI-программы гостевой книги
Поиск и устранение ошибок в CGI-программах
Perl и Web: не только CGI-программирование
Дополнительная литература
Упражнения
CG1программирование" 1 Вот один из способов решения этой задачи
use strict;
use CGI qw (:standard);
print header(), start_html("Add Me"It-print hi("Add Me") ;
if (paramO) {
my $nl = param('fieldl');
my $n2 = param('field2');
my $n3 = $n2 + $nl;
print p("$nl + $n2 = <strong>$n3</strong>\n") ;
} else (
print hr(), start_form();
print pC'First Number:", textfield("fieldl"));
print p("Second Number:", textfield("field2"));
print p(submit("add"), reset ("clear"));
print end_form(), hr () ;
} print end_html();
Если входных данных нет, то просто создается форма с двумя текстовыми полями (при помощи метода textfield). При наличии входной информации мы объединяем эти поля и выводим результат.
2. Вот один из способов решения этой задачи:
use strict;
use CGI qw(:standard);
print header(), start_html("Browser Detective");
print hi("Browser Detective"), hr();
my $browser = $ENV('HTTP_USER_AGENT'};
$_ '" $browser;
BROWSER:(
if (/msie/i) (
msie($_) ;
) elsif (/mozilla/i) (
netscape($_) ;
) elsif (/lynx/i) (
lynx($_);
1 else (
default($_) ;
> >
print end_html() ;
sub msie)
print pC'Internet Explorer: @_. Good Choice\n") ;
}
sub netscape (
print pC'Netscape: @_. Good Choice\n") ;
t
sub lynx {
print p("Lynx: @_. Shudder...");
(
sub default (
print pC'What the heck is a @_?");
}
Главный момент здесь — проверка переменной среды HTTP_USER_ AGENT на предмет наличия одного из значений, определяющих известный тип броузера (MS Internet Explorer, Netscape Navigator, Lynx). Такая операция проводится не на всех серверах, но на большинстве из них. Это хороший способ генерировать содержимое возвращаемого документа, ориентированное на возможности конкретного броузера. Обратите внимание: чтобы посмотреть, какой именно броузер применяется пользователем, мы выполняем только тривиальное строковое сопоставление (без учета регистра).
Хешфункции В этом разделе перечислены некоторые функции предназначенные для обработки хешей
Функция keys
Функция keys( %имя_хеша) выдает список всех текущих ключей, имеющихся в хеше %имя_хеша. Другими словами, применение этой функции эквивалентно возвращению всех элементов списка с нечетными номерами (первый, третий, пятый и т.д.) путем развертывания хеша %имя_хеша в списочном контексте, причем функция keys возвращает их именно в этом порядке. Если элементы в хеше отсутствуют, функция keys возвращает пустой список.
Применим эту функцию к хешу из предыдущих примеров:
$fred("aaa"} = "bbb";
$fred{234.5) = 456.7;
Olist = keys(%fred); # @list получает значение ("ааа",234.5) # или (234.5,"ааа")
Как и во всех остальных встроенных функциях, круглые скобки не обязательны: функция keys %fred полностью идентична keys (%fred).
foreach $key (keys (%fred)) ( # однократно для каждого значения хеша %fred
print "at $key we have $fred($key}\n"; # показать ключ и значение }
В этом примере показано также, что отдельные элементы хеша могут интерполироваться в строки в двойных кавычках. Весь хеш, однако, интерполировать таким образом нельзя*.
В скалярном контексте функция keys выдает число элементов (пар ключ-значение), содержащихся в хеше. Например, вы можете выяснить, пуст ли хеш, так:
if (keys(%xeni)) { # если keys() не равно 0:
...; # массив не пустой )
# ... или ...
while (keys(%xem) < 10) {
. . .; # продолжать цикл, пока меньше 10 элементов }
Для того чтобы узнать, пуст хеш или нет, нужно просто использовать функцию %хеш в скалярном контексте:
if (%хеш) ( # если "истина", в нем что-то есть
# что-то сделать )
Функция values
Функция values (%имя_массива) возвращает список всех текущих значений указанного массива в том же порядке, в каком функция keys ( %имя_массива} возвращает ключи. Как всегда, круглые скобки не обязательны. Например:
%lastname =0; # сделать %lastname пустым $lastname("fred"} = "flintstone";
$lastname("barney"} = "rubble";
Olastnames = values(tiastname); # получить значения
Массив
@lastnames будет содержать либо значения ("flintstone", "rubble"), либо ("rubble", "flintstone").Функция each
Для выполнения цикла над всем хешем (т.е. для проверки каждого его элемента) можно использовать функцию keys и получать значения по возвращаемым ею ключам. Действительно, этот метод широко используется, но есть и более эффективный способ — функция each ( %имя_хеша), которая возвращает пару ключ-значение как двухэлементный список. При каждом вычислении этой функции для одного хеша возвращается очередная пара ключ-значение, пока не будут проверены все элементы. Если пар больше нет, each возвращает пустой список.
* Можно, в принципе, с помощью среза, но здесь о срезах мы не говорим.
Например, чтобы пройти по хешу %lastname из предыдущего примера, нужно использовать нечто такое:
while ( ($first,$last) = each(%lastname)) {
print "The last name of $first is $last\n";
}
Присваивание нового значения всему хешу заставляет функцию each перейти в его начало. Добавление элементов в хеш и удаление из него элементов в ходе выполнения цикла вполне может "запутать" функцию each (и вас, наверное, тоже).
Функция delete
Итак, вы можете добавлять элементы в хеш, но пока не можете удалять их (кроме как путем присваивания нового значения всему хешу). Для удаления элементов хеша в Perl используется функция delete. Операнд этой функции — хеш-ссылка, аналогичная той, которая используется для извлечения конкретного значения из хеша. Perl удаляет из хеша соответствующую ссылке пару ключ-значение. Например:
%fred = ("aaa","bbb",234.5,34.56); # добавить в %fred два элемента delete $fred("aaa"}; # теперь в хеше %fred только одна пара ключ-значение
Хеши
Хеши
Хешпеременные Имя хешпеременной
* В старой документации хеши назывались ассоциативными массивами, но мы настолько устали применять к столь распространенному понятию такой многосложный термин, что решили заменить его гораздо более удачным односложным словом.
** Модули типа IxHash и DB_fiIe обеспечивают некоторую степень упорядочения, но ценой существенного снижения производительности.
соответствующую часть имен скалярных переменных и массивов. Кроме того, точно так же, как нет никакой связи между $fred и @fred, хеш-переменная %fred не имеет ничего общего с названными объектами.
Чаще всего хеш создается и используется путем обращения к его элементам, а не ко всему хешу. Каждый элемент хеша — отдельная скалярная переменная, доступная через индекс, представляющий собой строковое значение и называемый ключом. Так, обращение к элементам хеша %fred производится путем указания $fred{ $ключ}, где $ключ — любое скалярное выражение. Вновь подчеркнем, что обращение к элементу хеша требует иного синтаксиса, нежели обращение ко всему хешу целиком.
Как и в случае с массивами, новые элементы хеша создаются путем присваивания значения:
$fred{"ааа"} == "bbb"; # создает ключ "ааа", значение "bbb" $fred(234.5} = 456.7; # создает ключ "234.5", значение 456.7
С помощью этих операторов в хеше создаются два элемента. При последующем обращении к элементам (по указанным ключам) возвращаются ранее записанные значения:
print $fred("ааа"); # выводит на экран "bbb" $fred{234.5) += 3; # делает значение равным 459.7
При обращении к несуществующему элементу возвращается значение undef (как и при обращении к отсутствующему элементу массива или к неопределенной скалярной переменной).
И прочее
И прочее
Perl с каждым днем становится все более мощным и полезным, поэтому оперативное обновление посвященной ему документации — довольно сложная задача. (Кто знает, может быть, к дню появления этой книги на полках магазинов уже будет создан Visual Perl?) В любом случае — спасибо, Ларри!
Интерактивный клиент
Интерактивный клиент
Создать программу-клиент, которая просто читает все с сервера или посылает одну команду, получает один ответ, а затем завершает свою работу, очень легко. А как насчет создания чего-нибудь полностью интерактивного, вроде telnefi Мы имеем в виду приложение, которое позволяло бы вам набрать строку, получить ответ, набрать еще одну строку, вновь получить ответ и т.д. (В принципе, telnet обычно работает в символьном, а не в строковом режиме, но идею вы поняли.)
Этот клиент — более сложный, чем те два, с которыми мы имели дело до сих пор, но если вы работаете в системе, которая поддерживает мощный вызов fork, решение получится не слишком сложным. Установив соединение с тем сервисом, с которым вы хотите пообщаться, клонируйте свой процесс вызовом fork. Каждый из созданных идентичных процессов должен выполнить очень простое задание: родительский копирует все из гнезда на стандартный вывод, а порожденный одновременно копирует все со стандартного ввода в гнездо. Реализовать это с помощью только одного процесса было бы гораздо труднее, потому что легче написать два процесса для выполнения одной задачи, чем один процесс — для выполнения двух задач*.
Вот наш код:
#!/usr/bin/peri -w use strict;
use 10::Socket;
my ($host, $port, $kidpid, $handle, $line);
unless (8ARGV == 2 ) ( die "usage: $0 host port" ) ($host, $port) = 8ARGV;
# создать tcp-соединение с указанным хостом и портом $handle - 10::Socket::INET->new(Proto => "tcp",
PeerAddr => $host,
PeerPort => $port)
or die "can't connect to port $port on $host: $!";
$handle->autoflush(l); # и результат сразу же попадает туда print STDERR "[Connected to $host:$port]\n";
# разбить программу на два процесса-близнеца
die "can't fork: $!" unless defined ($kidpid = fork());
# блок if{( выполняется только в родительском процессе if($kidpid) (
# копировать данные из гнезда на стандартный вывод while (defined ($line = <$handle> )) f print STDOUT $line;
1
kill ("TERM",$kidpid); # послать в порожденный процесс сигнал SIGTERM >
# блок else(} выполняется только в порожденном процессе else 1
# копировать данные со стандартного ввода в гнездо while (defined ($line = <STDIN>)) ( print $handle $line;
} 1
* Принцип сохранения простоты — один из краеугольных камней не только философии UNIX, но и высококачественного проектирования программного обеспечения. Наверное, именно поэтому он распространился и на другие системы.
Функция kill в блоке if родительского процесса пошлет сигнал в наш порожденный процесс (в текущий момент работающий в блоке else), как только удаленный сервер закроет свою сторону соединения.
Интерполяция массивов Как и скаляры
Ofred = ("hello","dolly");
$у = 2;
$х = "This is $fred[l]'s place"; # "This is dolly's place" $x = "This is $fred($y-l]'s place"; # To же самое
Отметим, что индексное выражение вычисляется как обычное, как будто оно находится вне строки, т.е. оно предварительно не интерполируется.
Если вы хотите поставить после простой ссылки на скалярную переменную литеральную левую квадратную скобку, нужно выделить эту скобку так, чтобы она не считалась частью массива:
Ofred = ("hello","dolly"); # присвоить массиву @fred значение для проверки $fred = "right";
# мы пытаемся сказать "this is right[1]" $х = "this is $fred[l]"; t неправильно, дает "this is dolly" $x = "this is ${fred}[l]"; t правильно (защищено фигурными скобками) $х = "this is $fred"."[1]"; # правильно (другая строка) $х = "this is $fred\[l]"; t правильно (скобку защищает обратная косая)
Аналогичным образом может интерполироваться список значений перемен-ной-массива. Самая простая интерполяция — интерполяция всего массива, обозначенного именем (с начальным символом @). В этом случае элементы интерполируются по очереди, с разделением их пробелами, например:
@fred = ("а", "bb","ccc",1,2,3) ;
$а11 = "Mow for Sfred here!";
# $all получает значение "Now for a bb ccc 123 here!"
Можно также выбрать часть массива с помощью среза:
@fred = ("а","bb","ccc",1,2,3);
$а11 = "Now for @fred[2,3] here!";
# $all получает значение "Now for ccc 1 here!" $all = "Now for @fred[@fred[4,5]] here!"; ” то же самое
Опять-таки, если вы хотите поставить после ссылки на имя массива литеральную левую квадратную скобку, а не индексное выражение, можете использовать любой из описанных выше механизмов.
Использование анонимного FTP
Если вам никогда не приходилось пользоваться анонимным FTP, разберите приведенный ниже пример сеанса с комментариями. Текст, набранный жир-ным шрифтом — это то, что вы должны вводить с клавиатуры; комментарии набраны курсивом. Символ % — это приглашение, которое вводить не нужно.
% ftp ftp.CPAN.org(на самом деле такого узла нет)
Connected to ftp.CPAN.org 220 CPAN FTP server (Version wu-2.4(l) Fri Dee 1 00:00:00 EST 1995} ready.
Name (ftp.CPAN.orgiCPAN) : anonymous
331 Guest login ok, send your complete e-mail address as password. Password: camal@nutshall.ccm
(здесь введите свое пользовательское имя и имя своего хоста)
230 Guest login ok, access restrictions apply. ftp> cd pub/perl/CPAN/src 250 CWD command successful -
ftp> binary
(для сжатых файлов нужно задать двоичный режим передачи)
ftp> get latest.tar.gz 200 PORT command successful. 150 Opening BINARY mode data connection for FILE. 226 Transfer complete.
{повторите этот шаг для каждого из нужных вам файлов)
ftp> quit 221 Goodbye. %
Получив файлы, распакуйте их, а затем сконфигурируйте, постройте и инсталлируйте Perl:
'"., gunzip < latest.tar.gz I tar xvf -% cd perl5.004 (используйте реальное имя каталога) Теперь любую из следующих двух строк:% sh configure
{строчная буква с — для автоматического конфигурирования)
% sh Configure
{прописная буква С - для конфигурирования вручную)
% make
(построить весь Perl)
'i. make test
(проверить, работает ли он)
% make install
(для этого вы должны быть привилегированным пользователем) * Суффикс .tar.gz. означает, что это стандартный Internet-формат архива, созданного програм-мой tar.
Использование DBMхеша После открытия
$FRED{"fred"} = "bedrock"; # создать (или обновить) злемент delete $FRED("barney"}; # удалить злемент базн данных foreach $key (keys %FRED) ( # пройти по всем значенням print "$key has value of $FRED{$key)\n";
}
Последний цикл должен просмотреть весь файл на диске дважды: один раз для выборки ключей, а второй — для поиска значений, соответствующих зтим ключам. Если вы просматриваете DBM-хеш, то более зффективным способом с точки зрения зксплуатации диска является использование опе-рации each, которая делает всего один проход:
while (($key, $value) = each(%FRED) ) ( print "$key has value of $value\n";
}
Если вы обращаетесь к системним DBM-базам данных, например к базам данных, созданным системами sendmail и NIS, вы должны иметь в виду, что в плохо написанных С-программах в конце строк иногда стоит символ NUL (\0). Программам библиотеки DBM зтот NUL не нужен (они обрабатывают двоичные данные с помощью счетчика байтов, а не строки с символом NUL на конце), позтому он хранится как часть данных. В таком случае вы должны добавлять символ NUL в конец своих ключей и отбрасывать NUL, стоящий в конце возвращаемых значений, иначе данные не будут иметь смысла.
Например, чтобы найти имя merlyn в базе данных псевдонимов, можно сделать так:
dbmopen(%ALI, "/etc/aliases", undef) I I die "no aliases?";
$value °= $ALI {"merlyn\0" 1; # обратите внимание на добавленный NUL
chop ($value) ; # удалить добавленный NUL
print "Randal's mail is headed for: $value\n"; # показать результат
В вашей версии UNIX база данных псевдонимов может храниться не в каталоге /etc, а в каталоге /usr/lib. Чтобы вияснить, где именно она хранится, придется провести маленькое расследование. Новые версии sendmail зтим NUL-дефектом не страдают. ''
Использование дескрипторов файлов
Использование дескрипторов файлов
После того как дескриптор файла открыт для чтения, из него можно читать строки точно так же, как со стандартного ввода stdin. Например, для чтения строк из файла паролей используется такой код:
open (ЕР,"/etc/passwd");
while (<EP>) (
chomp;
print "I saw $_ in the password file!\n";
}
Обратите внимание: вновь открытый дескриптор помещен в угловые скобки, аналогично тому как ранее мы использовали stdin.
Если вы открыли дескриптор файла для записи или добавления и хотите ввести в него что-либо (с помощью функции print), этот дескриптор нужно поставить сразу за ключевым словом print, перед остальными аргументами. Запятой между дескриптором и остальными аргументами быть не должно:
print LOGFILE "Finished item $n of $max\n";
print STDOUT "hi, world!\n"; # как print "hi, world!\n"
В этом случае сообщение, начинающееся со слова Finished, посылается в дескриптор файла logfile, который, предположительно, был открыт в программе ранее. Сообщение hi, world направляется на стандартный вывод, как и раньше, когда вы не указывали дескриптор. Мы говорим, что stdout — это дескриптор файла по умолчанию для оператора print.
Предлагаем вам способ копирования данных из файла, заданного в переменной $а, в файл, указанный в переменной $Ь. Он иллюстрирует почти все, о чем мы рассказывали на последних нескольких страницах:*
open(IN,$a) || die "cannot open $a for reading: $!";
open(OUT,">$b") || die "cannot create $b: $!";
while (<IN>) { # прочитать строку из файла $а в $_
print ОПТ $_; # вывести эту строку в файл $Ь ) close(IN) || die "can't close $a: $!";
close (ОПТ) || die "can't close $b: $!";
Использование функции fork Еще
if (!defined($child_pid = fork()) {
die "cannot fork: $!";
} elsif ($pid) {
4s я —
родительский процесе } else (# я — порожденннй процесе >
Чтоби максимально зффективно использовать зтот клон, нам нужно изучить еще несколько функции, которьге восьма похожи на своих UNIX-тезок: зто функции wait, exit и ехес.
Самая простая из них — функция ехес. Зто почти то же самое, что и функция system, за тем исключением, что вместо запуска нового процесса для виполнения shell-команди Perl заменяет текущий процесе на shell. После успешного внполнения ехес Perl-программа исчезает, поскольку вместо нее виполняется затребованная программа. Например,
ехес "date";
заменяет текущую Perl-программу командой date, направляя результат зтой команди на стандартний вивод Perl-программи. После завершення команди date делать больше нечего, потому что Perl-программа давно исчезла.
Все зто можно рассматривать и по-другому: функция system похожа на комбинацию функции fork c функцией ехес, например:
# МЕТОД І... использование system:
system("date");
t МЕТОД 2... использование fork/exec:
unless (fork) (
# fork видала нуль, позтому я — порожденный процесе и я выполняю:
exec ("date") ; t порожденный процесе становится командой date
}
Использовать fork и exec таким способом — не совсем правильно, потому что команда date и родительский процесе "пыхтят" одновременно, их результати могут переметаться и испортить все дело. Как дать родительскому процессу указание подождать, пока не завершится порожденный процесе? Именно зто и делает функция wait; она ждет завершення данного (да и любого, если быть точним) порожденного процесса. Функция waitpid более разбор-чива: она ждет завершення не любого, а определенного порожденного процесса:
if (!defined($kidpid = fork()) (
# fork возвратила undef, т.е. неудача die "cannot fork: $!";
” elsif ($pid =" 0) {
# fork возвратила О, позтому данная ветвь — порожденный процесе exec("date") ;
# если exec терпит неудачу, перейти к следующему оператору die "can't exec date: $!";
} else {
# fork возвратила не 0 и не undef,
# позтому данная ветвь — родительский процесе waitpid($kidpid, 0) ;
}
Если все зто кажется вам слишком сложным, изучите системные вызовы fork(2) и ехес(2), отыскав материалы о них в каком-нибудь руководстве по ОС UNIX, потому что Perl просто передает вызовы зтих функций прямо в системные вызовы UNIX.
Функция exit обеспечивает немедленньш выход из текущего Perl-про-цесса. Она используется для прерывания Perl-программы где-нибудь посе-редине или — вместе с функцией fork — для вьшолнения Perl-кода в процессе с последующим выходом. Вот пример удаления нескольких файлов из каталога///?у? в фоновом режиме с помощью порожденного Perl-процесса:
unless (defined ($pid = fork)) ( die "cannot fork: $!";
}
unless ($pid) (
unlink </tmp/badrock.*>; # удалить зти файлн exit; # порожденный процесе останавливается здесь
)
# родительский процесе продолжается здесь
waitpid($pid, 0); # после уничтожения порожденного процесса нужно все убрать
Без использования функций exit порожденный процесе продолжал бы вьшолнять Perl-код (со строки "# родительский процесе продолжается здесь") — а как раз зтого нам и не нужно.
Функция exit может иметь необязательный параметр, служащий числовим кодом выхода, который воспринимается родительским процессом. По умолчанию выход производится с нулевым кодом, показывающим, что все прошло нормально.
Использование функций system u
Аналогичным образом Perl-программа в состоянии запускать новые процессы и может делать зто, как и большинство других операций, несколь-кими способами.
Самий простой способ запуска нового процесса — использовать для зтого функцию system. В простейшей форме зта функция передает в совершенно новый shell /bin/sh одну строку, которая будет выполняться как команда. По выполнении команды функция system возвращает код завершення данной команды (если все было нормально — зто, как правило, 0). Вот пример того, как Perl-программа выполняет команду date c помощью shell*:
system("date");
Здесь мы не проверяем возвращаемое значение, но неудачный исход выполнения команд н date вряд ли возможен.
Куда идет результат зтой команди? Откуда поступают исходные данные, если они нужны команде? Хорошие вопросы, и ответы на них позволят узнать, чем отличаются различные способы создания процессов.
* В данном случае shell фактически не используется, поскольку Рсгі сам выполняет операций shell, если командная строка достаточно проста, как в данном случае.
Три стандартних файла для функции system (стандартний ввод, стандартний внвод и стандартний вивод ошибок) наследуются от Perl-процесса. Таким образом, результат выполнения команди dale в приведенном више примере направляется туда же, куда поступает результат выполнения функции print stdout — скореє всего, на дисплей визвавшего ее пользователя. Поскольку ви запускаєте shell, то можете переадресовать стандартний вивод, пользуясь обычными для /Ып/sh операциями переадресации. Например, чтобы направить результати работи команди date в файл right_now, нужно сделать что-то вроде зтого:
system("date >right_now") && die "cannot create right_now";
На зтот раз ми не только посилаєм результат команди date в файл, внполняя переадресацию в shell, но и проверяем статус возврата. Если статус возврата — значение "истина" (не нуль), зто значит, что с командой shell что-то произошло, и функция die внполнит свою миссию. Данное правило обратно обичним правилам выполнения операций в Perl: ненулевое возвра-щаемое значение операций system, как правило, указивает на какую-то ошибку.
Аргументом функции system может бить все, что пригодно для передачи в /Ып/sh, позтому можно задавать сразу несколько команд, разделяя их точками с запятой или символами новой строки. Процесси, после которих указан символ &, запускаются, но программа не ждет их завершення, т.е. в данном случае все происходит аналогично тому, как если би ви ввели в shell строку, которая заканчивается символом &.
Вот пример задания команд date и who в shell с передачей результатов в файл, заданний Perl-переменной. Все зто вьшолняется в фоновом режиме, чтобн для продолжения выполнения Perl-сценария не нужно било ждать завершення данного процесса.
$where = "who_out.".++$і; t получить новое имя файла system "(date; who) >$where &";
В зтом случае функция system возвращает код выхода shell и показывает таким образом, успешно ли бил запущен фоновий процесе, но не сообщает, били ли успешно выполнени команди date и who. В зтой заключенной в двойные кавички строке производится интерполяция переменних, позтому переменная $ where заменяется своим значением (зто делает Perl, а не shell). Если би ви хотели обратиться к переменной shell с именем $where, вам нужно било би поставить перед знаком доллара обратную косую или использовать строку в одинарних кавнчках.
Помимо стандартних дескрипторов файлов, порожденний процесе на-следует от родительского процесса много других вещей. Зто текущее значение, заданное командой umask, текущий каталог й, конечно, идентификатор пользователя.
Кроме того, порожденний процесе наследует все переменнне среды. Зти переменные обычно изменяются командой csh setenv или же соответствующим присваиванием и команд ой export shell (/bin/sh). Переменные среды исполь-зуются многими утилитами, включая сам shell, для изменения порядка работы зтих утилит и управления йми.
В Perl предусмотрена возможность проверки и изменения текущих пере-менных среды посредством специального хеша, который называется %env. Каждый ключ зтого хеша соответствует имени переменной среды, а соответ-ствующее значение — значенню переменной. Содержимое данного хеша отражает параметры среды, переданные Perl родительским shell; изменение хеша изменяет параметри среды, которую использует Perl и порожденные им процессы, но не среды, используемой родительскими процессами.
Вот простая программа, которая работает, как printenv:
foreach $key (sort keys %ENV) ( print "$key = $ENV($key)\n";
> . ,
Обратите внимание: знак равенства здесь — зто не символ операции присваивания, а просто текстовый символ, с помощью которого функция print выдает сообщения вида TERM=xterm или U3ER=merlyn.
Вот фрагмент программы, с помощью которого значение переменной path изменяется таким образом, чтобы поиск команды grep, запущенной функцией system, производился только в "обычных" местах:
$oldPATH = $ENV{ "PATH"); # сохранить предыдущий путь $ENV("PATH") = "/bin:/usr/bin:/usr/ucb"; # ввести известньй путь systemC'grep fred bedrock >output") ; # запустить команду $ENV{"PATH"} = $oldPATH; # восстановить предьшуший путь
Как много текста придется набирать! Гораздо быстрее будет просто установить локальнеє значение для зтого злемента хеша.
Несмотря на наличие некоторых недостатков, операция local может делать одну вещь, которая не под силу операции ту: она способна присваи-вать временное значение одному злементу массива или хеша.
{
local $ENV{"PATH"> " "/bin:/usr/bin:/usr/ucb"; ! systemC'grep fred bedrock >output");
>
Функция system может принимать не один аргумент, а список аргумен-тов. В зтом случае Perl не передает список аргументов в shell, а рассматривает первый аргумент как подлежащую выполнению команду (при необходимости производится ее поиск согласно переменной path), а остальные аргумен-ты — как аргументи команды без обычной для shell интерпретации. Другими словами, вам не нужно заключать в кавычки пробельные символы и беспо-коиться об аргументах, которые содержат угловые скобки, потому что все зто — просто символы, передаваемые в программу. Таким образом, следую-щие две команды зквивалентны:
system "grep 'fred flintstone' buffaloes"; # с использованием shell system "grep","fred flintstone","buffaloes"; # без использования shell
Применение в функции system списка, а не одной строки, зкономит также один процесе shell, позтому поступайте так при любой возможности. (Если форма функции system c одним аргументом достаточно проста, Perl сам оптимизирует код, полностью убирая вызов shell и обращаясь к соответ-ствующей программе непосредственно, как если бы вы использовали вызов функции с несколькими аргументами.)
Вот еще один пример зквивалентных форм:
Ocfiles = ("fred.c","barney.c"); # что компилировать Soptions = ("-DHARD","-DGRANITE"); # опции system "cc -o slate Soptions Bcfiles"; # c shell system "cc","-o","slate",goptions,Scfiles"; # без shell
Использование обратных кавычек
$now = "the time is now".'date'; # получает текст и дату
Значение переменной $now теперь представляет собой текст the time is now и результат выполнения команди date(l) (включая конечний символ новой строки):
the time is now Fri Aug 13 23:59:59 PDT 1996
Если взятая в обратные кавычки команда используется не в скалярном, а в списочном контексте, то возвращается список строкових значений, каждое из которых представляет собой строку (оканчивающуюся символом новой строки*) из результата выполнения команды. В примере с командой date у нас был бы всего один злемент, потому что она видала всего одну строку текста. Результат работы команды who выглядит так:
merlyn tty42 Dec 7 19:41 fred ttylA Aug 31 07:02 barney ttylF Sep 1 09:22
Вот как можно получить зтот результат в списочном контексте:
foreach $_ ('who') ( # один раз для каждой строки текста из who <$who,$where,$when) = /(\S+)\s+(\S+)\s+(.*)/;
print "$who on $where at $when\n";
}
* Или символом, которьш у вас занесен в переменную $/.
При каждом выполнении зтого цикла используется одна строка виход-ных данных команди who, потому что взятая в обратные кавички команда интерпретируется в списочном контексте.
Стандартний ввод и стандартний вывод ошибок команди, взятой в обратные кавички, наследуются от Perl-процесса*. Зто значит, что обычно стандартний вывод таких команд ви можете получить как значение строки, заключенной в обратные кавички. Одна из распространенных операций — обьединение стандартного внвода ошибок со стандартним виводом, чтобы команда в обратннх кавычках "подбирала" их оба. Для зтого используется конструкция shell 2>&1:
die "rm spoke!" if 'rm fred 2>&1';
Здесь Perl-процесе завершается, если rm посылает какое-нибудь сообще-ние — либо на стандартний вивод, либо на стандартний вивод ошибок, потому что результат больше не будет пустой строкой (пустая строка соот-ветствовала би значенню "ложь").
Использование процессов как дескрипторов
open(WHOPROC, "who 1"); # открнть who для чтения
Обратите внимание на вертикальную черту справа от who. Зта черта информирует Perl о том, что данная операция open относится не к имени файла, а к командо, которую необходимо запустить. Поскольку черта стоит справа от команди, данннй дескриптор файла открнвается для чтения. Зто означает, что предполагается прием данных со стандартного внвода команди who. (Стандартний ввод и стандартний вывод ошибок продолжают исполь-зоваться совместно с Perl-процессом.) Для остальной части программы дескриптор whoproc — зто просто дескриптор файла, которий открыт для
* На самом деле все не так просто. См. соответствующий ответ в разделе 8 сборника часто задаваемых вопросов по Perl ("Как перехватить stderr из внешней команды?"). Если у вас Perl версии 5.004, зтот сборник распространяется как обычная man-страница — в данном случас per/faq8( 1).
** Ho не одновременно. Примеры двунаправленной связи приведены в главе 6 книги Programming Perl и на man-странице рег1ірс(1).
чтения, что означает возможность вьшолнения над файлом всех обычных операций ввода-вывода. Вот как можно прочитать данные из команды who в массив:
@whosaid = <WHOPROC>;
Аналогичннм образом для запуска команды, которой необходимы вход-ные данные, мы можем открыть дескриптор файла процесса для записи, поставив вертикальную черту слева от команды, например:
open(LPR,"|Ipr -Psiatewriter") ;
print LPR Srockreport;
close(LPR) ;
В атом случае после открытия lpr мы записываем в него данные и закриваєм данный файл. Открьггие процесса с дескриптором файла позволяет выполнять команду параллельно с Perl-программой. Задание для дескриптора файла команды close заставляет Perl-программу ожидать завершения процесса. Если дескриптор не будет закрыт, процесе может продолжаться даже после завершения Perl-программы.
Открытие процесса для записи предполагает, что стандартний ввод команды будет получен из дескриптора файла. Стандартний вывод и стандартний вивод ошибок используются зтим процессом совместно с Perl. Как и прежде, ви можете использовать переадресацию ввода-вивода в етиле /bin/sh. Вот как в нашем последнем примере можно отбрасивать сообщения об ошибках команди Ipr.
open(LPR,"|Ipr -Psiatewriter >/dev/null 2>&1");
С помощью операций >/dev/null обеспечивается отбраснвание стандартного вивода путем переадресации его на нулевое устройство. Операция 2>&1 обеспечивает передачу стандартного вивода ошибок туда, куда направляется стандартний вывод, позтому сообщения об ошибках также отбрасываются.
Можно даже обьединить все зти фрагменти программи и в результате получить отчет обо всех зарегистрированных пользователях, кроме Фреда:
open (WHO,"who[ ") ;
open (LPR,"|Ipr - Psiatewriter");
while (<WHO>) (
unless (/fred/) { # не показывать имя Фред print LPR $_:
> } close WHO;
close LPR;
Считивая из дескриптора who по одной строке, зтот фрагмент кода выводит в дескриптор lpr все строки, которне не содержат строкового значення fred. В результате на принтер внводятся только те строки, которне не содержат имени fred.
Вовсе не обязательно указывать в команде open только по одной команде за один прием. В ней можно задать сразу весь конвейєр. Например, следую-щая строка запускает процесе ls(l), который передает свои результати по каналу в процесе tail(l), который, в свою очередь, передает свои результати в дескриптор файла who pr:
open(WHOPR, "Is I tail -r I");
Изменение меток времени С кажднм
зто время последнего доступа, время последнего изменения и время послед-него изменения индексного дескриптора. Первым двум моткам времени можно присвоить произвольные значення с помощью функции utime (ко-торая соответствует системному вызову utime в ОС UNIX). При установке двух зтих значений третья метка автоматически устанавливается в текущее время, позтому отдельного способа для ее установки не предусмотрено.
Зти значення устанавливаются во внутреннем формате времени, а имен-но в количестве секунд, прошедших после полуночи 1 января 1970 года по среднегринвичскому времени. Когда мы писали нашу книгу, зта цифра достигла 800 миллионов с небольшим. (Во внутреннем формате она пред-ставляется как 32-разрядное число без знака, и если все мы не перейдем на 64-разрядные (й более) машины, то переполнение наступит где-то в следую-щем столетии. У нас будут гораздо более серьезные проблеми в 2000-м году*.)
Функция utime работает аналогично функциям chmod и unlink. Она получает список имен файлов и возвращает число файлов, параметри времени которых были изменены. Вот что нужно сделать, чтобы файли fred и barney выглядели так, будто они изменялись в недавнем прошлом:
$atime = $mtime = 700_000_000; # некоторое время назад utime($atime,$mtime,"fred","barney")
Никакого "розумного" значення для меток времени нет: можно сделать так, чтобы файл выглядел сколь угодно старым, или чтобы казалось, будто он бил изменен в далеком будущем (зто полезно, если вы пишете научно-фантастические рассказы). Вот как, например с помощью функции time (которая возвращает текущее время как метку времени UNIX) можно сделать так, чтобы казалось, будто файл max_headroom был изменен спустя 20 минут после текущего момента времени:
$when = time() + 20*60; # 20 минут с текущего момента utime($when,$when,"max headroom");
* Perl-функции localtime nqmtime работаюттак, каквС: они возвращают год, изкоторого вычтена цифра 1900. В 2003-м году localtime выдаст год как 103.
Изменение прав доступа Права доступа
chmod(0666,"fred","barney");
Режим 0666 обозначает чтение и запись для владельца, группы и прочих пользователей, т.е. как раз то, что нам нужно.
* В данном случае вы не создаете каталог с самими широкими правами доступа. Определить права доступа вам также поможет текущая маска доступа umask вашего процесса. В UNIX-системах см. описание комавды shell umask или man-страницу umask(2).
Функция chmod возвращает число файлов, для которых были успешно изменены права доступа (даже если в результате фактически ничего не изменилось). Таким образом, в отношении контроля ошибок она работает аналогично функции unlink. Позтому, чтобы изменить права доступа к файлам fred и Ьагпеу и выполнить контроль ошибок в каждом случае, необходимо использовать следующую конструкцию:
foreach $file ("fred","barney") f unless chmod (0666,$file) (
warn "hmm... couldn't chmod $file.\$!";
Изменение принадлежности
Изменение принадлежности
Каждый файл в файловой системе (обычный, каталог, файл устройства и т.д.) имеет владельца и группу. Зги параметры определяют, кому принадлежат права доступа, установленные для файла по категориям "владелец" и "группа" (чтение, запись и (или) выполнение). Владелец и группа определяются в момент создания файла, но при определенных обстоятельствах вы можете изменить их. (Зти обстоятельства зависят от конкретной разновидности UNIX, c которой вы работаете; подробности см. на man-странице chown.)
Функция chown получает идентификатор пользователя (UID), иденти-фикатор группы (GID) и список имен файлов и пытается изменить принад-лежность каждого из перечисленных файлов в соответствии с указанными идентификаторами. Успешному результату соответствует ненулевое значе-ние, равное числу файлов, принадлежность которых изменена (как в функ-циях chmod и unlink). Обратите внимание: вы одновременно меняете и владельца, и группу. Если какой-то из зтих идентификаторов вы менять не хотите, поставьте вместо него -1. Помните, что нужно использовать числовые UID и GID, а не соответствующие символические имена (хотя команда chmod и принимает такие имена). Например, если UID файла fred — 1234, а идентификатор группы stoners, которой зтот файл принадлежит по умолчанию,— 35, то в результате применения следующей команды файлы slate и granite переходят к пользователю fred и его группе:
chown(1234, 35, "slate", "granite"); # то же, что й
# chown fred slate granite
# chgrp stoners slate granite
В главе 16 вы узнаете, как преобразовать
fred в 1234 и stoners в 35.Изменение в форматах установок по умолчанию
Изменение в форматах установок по умолчанию
Мы часто говорим об использовании в тех или иных ситуациях значений "по умолчанию". В Perl имеется способ отмены этих "умолчаний" практически для всех случаев. Давайте поговорим об этом.
Изменение дескриптора файла с помощью функции select()
Когда мы говорили о функции print в главе 6, я упомянул, что print и print stdout идентичны, потому что stdout — это установка по умолчанию для print. Это не совсем так. Настоящая установка по умолчанию для print (а также для write и еще нескольких операций, до которых мы скоро доберемся) — это выбранный в текущий момент дескриптор файла.
Вначале выбранный в текущий момент дескриптор файла — это stdout, благодаря чему упрощается отправка данных на стандартный вывод. Изменить выбранный в текущий момент дескриптор файла можно с помощью функции select. В качестве аргумента эта функция принимает отдельный дескриптор файла (или скалярную переменную, которая содержит имя дескриптора файла). Изменение выбранного в текущий момент дескриптора файла влияет на все последующие операции, которые от него зависят. Например:
print "hello world\n"; # аналогично print STDOUT "hello worldVn"
select (LOGFILE); # выбрать новый дескриптор файла
print "howdy, world\n"; # аналогично print LOGFILE "howdy, world\n"
print "more for the log\n"; # еще в LOGFILE
select (STDOOT); # вновь выбрать STDOUT
print "back to stdout\n"; # это идет на стандартный вывод
Отметим, что операция select — "липкая"; после выбора нового дескриптора он остается "выбранным в текущий момент" до проведения следующей операции select.
Таким образом, более удачное определение stdout по отношению к функциям print и write будет звучать так: stdout — выбранный в текущий момент дескриптор по умолчанию, или просто дескриптор по умолчанию.
В подпрограммах может возникать необходимость смены выбранного в текущий момент дескриптора файла. Представляете, какое неприятное чувство можно испытать, вызвав подпрограмму и обнаружив, что все тщательно проверенные строки текста уходили куда-то "налево", потому что подпрограмма, оказывается, изменила выбранный в текущий момент дескриптор файла и не восстановила его! Что же должна делать "хорошо воспитанная" подпрограмма? Если она "знает", что текущий дескриптор — stdout, она может восстановить выбранный дескриптор с помощью кода, похожего на приведенный выше. А если программа, которая вызвала подпрограмму, уже изменила выбранный дескриптор файла — что тогда?
Оказывается, значение, возвращаемое функцией select, — это строка, которая содержит имя ранее выбранного дескриптора. Получив данное значение, можно впоследствии восстановить этот дескриптор, используя такой код:
$oldhandle = select LOGFILE;
print "this goes to LOGPILEW;
select ($oldhandle); # восстановить предыдущий дескриптор
Действительно, в приведенных выше примерах гораздо проще в качестве дескриптора файла для print явным образом указать logfile, но некоторые операции, как мы вскоре увидим, требуют именно изменения выбранного в текущий момент дескриптора файла.
Изменение имени формата
Имя формата по умолчанию для конкретного дескриптора файла совпадает с именем этого дескриптора. Для выбранного в текущий момент дескриптора файла этот порядок можно изменить, присвоив новое имя формата специальной переменной $~. Можно также проверить значение этой переменной и посмотреть, каков текущий формат для выбранного в текущий момент дескриптора файла.
Например, чтобы использовать формат addresslabel с дескриптором stdout, следует просто записать:
$~ = "ADDRESSLABEL";
А что, если нужно установить для дескриптора report формат summary? Для этого необходимо сделать всего лишь следующее:
$oldhanlde =• select REPORT;
$~ ° "SUMMARY";
select ($oldhandle);
Когда в следующий раз мы напишем
write (REPORT) ;
то
тем самым передадим текст на дескриптор report, но в формате summary*.Обратите внимание на то, что вы сохранили предыдущий дескриптор в скалярной переменной, а затем восстановили его. Этот прием — признак хорошего стиля программирования. В коде реальной программы мы, вероятно, решили бы предыдущий однострочный пример таким же способом, а не предполагали бы, что stdout — дескриптор по умолчанию.
Изменяя текущий формат для конкретного дескриптора файла, вы можете чередовать в одном отчете много разных форматов.
Изменение имени формата начала страницы
Точно так же, как путем установки переменной $~ мы можем изменять имя формата для конкретного дескриптора файла, так путем установки переменной $ л мы можем менять формат начала страницы. Эта переменная содержит имя формата начала страницы для выбранного в текущий момент дескриптора файла и доступна для чтения и записи, т.е. вы можете проверить ее значение и узнать текущее имя формата, а также изменить его, присвоив этой переменной новое значение.
* Объектно-ориентированный модуль FileHandle, входящий в состав стандартного дистрибутива Perl, обеспечивает выполнение этой задачи более простым способом.
Изменение длины страницы
Если определен формат начала страницы, длина страницы приобретает особое значение. По умолчанию длина страницы равна 60 строкам, т.е. если результаты работы функции write не умещаются до конца 60-й строки, то перед дальнейшим выводом текста автоматически вызывается формат начала страницы.
Иногда 60 строк — не то, что нужно. Этот параметр можно изменить, установив переменную $=. Данная переменная содержит текущую длину страницы для выбранного в текущий момент дескриптора файла. Опять-таки, для замены дескриптора stdout (выбранного в текущий момент дескриптора файла по умолчанию) на другой нужно использовать операцию select. Вот как следует изменить дескриптор файла logfile так, чтобы страница содержала 30 строк:
$old = select LOGFILE; # выбрать LOGFILE и сохранить старый дескриптор $= ° 30;
select $old;
Изменение длины страницы вступает в силу только при следующем вызове формата начала страницы. Если вы установили новую длину перед выводом текста в дескриптор файла в каком-то формате, то все будет работать как надо, потому что формат начала страницы вызывается при первом же вызове функции write.
Изменение положения на странице
Если вы выводите свой текст в дескриптор файла с помощью функции print, то значение счетчика строк будет неправильным, потому что Perl проводит подсчет строк только для функции write. Если вы хотите дать Perl знать, что выводите несколько дополнительных строк, можно настроить внутренний счетчик строк Perl, изменив значение переменной $-. Эта переменная содержит число строк, оставшихся на текущей странице для выбранного в текущий момент дескриптора файла. Каждая функция write уменьшает число оставшихся строк на число фактически выведенных строк. Когда значение этого счетчика достигает нуля, вызывается формат начала страницы и из переменной $= (задающей длину страницы) копируется значение $-.
Например, чтобы сообщить Perl, что вы послали в STDOUT дополнительную строку, нужно сделать следующее:
write; # вызвать формат STDOUT для STDOUT
print "An extra line... oops!\n"; # это идет в STDOUT $- --; # декрементировать $-, чтобы показать, что в STDOUT пошла строка не из write
write; # сработает, учтя дополнительную строку
В начале программы $- устанавливается в нуль для каждого дескриптора файла. Это позволяет гарантировать, что формат начала страницы будет первым элементом, вызываемым для каждого дескриптора файла при выполнении первой операции write.
Извлечение и замена подстроки
Извлечение и замена подстроки
Извлечь фрагмент строки можно путем осторожного применения регулярних виражений, но если зтот фрагмент всегда находится на известной позиции, такой метод незффективен. В зтом случае удобнее использовать функцию substr. Зта функция принимает три аргумента: строковое значение, начальную позицию (определяемую так же, как в функции index) и длину, т.е.
$s = substr ($строка, $нач<зло, $длина) ;
Начальная позиция определяется так же, как в функции index: первый символ — нуль, второй символ — единица и т.д. Длина — зто число символов, которые необходимо извлечь, начиная от данной позиции: нулевая длина означает, что символы не извлекаются, единица означает получение первого символа, двойка — двух символов и т.д. (Больше символов, чем имеется в строке, извлечь нельзя, позтому если вы запросите слишком много, ничего страшного не произойдет.) Выглядит зто так:
$hello = "hello, world!";
$grab ” substr($hello, 3, 2); t $grab получает "lo" $grab = substr($hello, 7, 100); # 7 до конца, или "world!"
Можно даже вьшолнять подобным образом операцию "десять в степени п" для небольших целочисленньк степеней, например:
$big = substr("10000000000",0,$power+l); # 10 ** $power
Если количество символов равно нулю, то возвращается пустая строка. Если либо начальная, либо конечная позиция меньше нуля, то такая позиция отсчитывается на соответствующее число символов, начиная с конца строки. Так, начальная позиция -їй длина 1 (или более) дает последний символ. Аналогичным образом начальная позиция -2 отсчитывается от второго символа относительно конца строки:
$stuff = substr("a very long string",-3,3); # последние три символа $stuff = substr("a very long string",-3,1); # буква і
Если начальная позиция указана так, что находится "левее" начала строки (например, задана большим отрицательным числом, превышающим длину строки), то в качестве начальной позиции берется начало строки (как если бы вы указали начальную позицию 0). Если начальная позиция — большое положительное число, то всегда возвращается пустая строка. Другими словами, зта функция всегда возвращает нечто, отличное от сообщения об ошибке.
Отсутствие аргумента "длина" зквивалентно взятию в качестве зтого аргумента большого числа — в зтом случае извлекается все от выбранной позиции до конца строки*.
Если первый аргумент функции substr — скалярная переменная (другими словами, она может стоять в левой части операции присваивания), то сама зта функция может стоять в левой части операции присваивания. Если вы перешли к программированию на Perl из С, вам зто может показаться странным, но для тех, кто когда-нибудь имел дело с некоторыми диалектами Basic, зто вполне нормально.
* В очень старых версиях Perl пропуск третього аргумента не допускался, позтому первые Perl-программистн использовали в качестве зтого аргумента большие числа. Вы, возможно, столкнетесь с зтим в своих археологических исследованиях программ, написанньк Perl.
В результате такого присваивания изменяется та часть строки, которая была бы возвращена, будь substr использована не в левой, а в правой части выражения. Нопример, substr ($var, 3,2) возвращает четвертьш и пятый символы (начиная с 3 в количестве 2), позтому присваивание изменяет указанные два символа в $var подобно тому, как зто приведено ниже:
$hw = "hello world!";
substr($hw, 0, 5) = "howdy"; # $hw теперь равна "howdy world!"
Длина заменяющего текста (который присваивается функции substr) не обязательно должна быть равна длине заменяемого текста, как в зтом примере. Строка автоматически увеличивается или уменьшается в соответ-ствии с длиной текста. Вот пример, в котором строка укорачивается:
substr($hw, 0, 5) = "hi"; # $hw теперь равна "hi world!"
В следующем примере зта строка удлиняется:
substr($hw, -б, 5) = "nationwide news"; # заменяет "world"
Процедуры укорачивания и удлинения заменяемой строки выполняются д остаточно быстро, позтому не бойтесь их использовать — хотя лучше все же заменять строку строкой той же длины.
Как получить Perl
Основной пункт распространения Perl — это Comprehensive Perl Archive Network, или CPAN (Сеть полных Perl-архивов). Эти архивы содержат не только исходный код, но и практически все материалы, которые вам когда-либо понадобятся для работы с Perl. CPAN зеркально копируется десятками узлов по всему свету. Главный узел — ftp.funet.fi (128.214.248.6). Вы можете найти и более близкий к вам узел CPAN, получив файл /pub/languages/perl/CPAN/MIR-RORS с этого узла. Можно также обратиться с помощью Web-броузера к сервису мультиплексирования CPAN по адресу www.perl.com. Если вы запросите с этого Web-сервера файл, имя которого начинается на /CPAN/, он соединит вас с ближайшим узлом CPAN, выбрав его по вашему доменному имени. Вот некоторые популярные адреса (URL) в CPAN:
http://www.perl.corn/CPAN/
http://www.perl.com/CPAN/README.html
http://www.perl.corn/CPAN/modules
http://www.perl.com/CPAN/ports
http://www.perl.corn/CPAN/doc
http://www.perl.com/CPAN/sre/latest.tar.Gz
Сервис мультиплексирования CPAN пробует соединить вас с локальной быстродействующей машиной через высокопроизводительный концентра-тор. Это, однако, получается не всегда, потому что доменные имена могут и не отражать схемы сетевых соединений. Например, ваше хост-имя может заканчиваться на .se, но при этом вам лучше будет соединяться не со Швецией, а с Северной Америкой. В этом случае вы можете самостоятельно выбрать узел по следующему URL:
http://www.perl.com/CPAN
Обратите внимание на отсутствие косой черты в конце этого URL. Если конечная косая опущена, мультиплексор CPAN выдает меню дублирующих серверов CPAN, из который вы можете выбрать подходящий. Если ваш Web-броузер поддерживает встроенные переменные cookies, то мультиплек-сор CPAN автоматически запомнит выбранный вами узел.
Ниже перечислены машины, на которых должен быть исходный код Perl и копии списка дублирующих серверов CPAN. Эти материалы можно полу-чить по анонимному FTP. (Попробуйте использовать не IP-адреса, а имена машин, поскольку IP-адреса могут измениться.)
ftp.perl.corn (199.45.129.30)
ftp.cs.Colorado.edu (131.211.80.17)
ftp.funet.fi (128.214.248.6)
ftp.cs.run.nl (131.211.80.17)
Местонахождение главного каталога зеркального сервера CPAN на этих машинах может быть разным, но скорее всего это нечто вроде /pub/perl/CPAN.
Где находятся файлы В главном каталоге CPAN вы увидите как минимум следующие подкаталоги:
authors
Этот каталог содержит многочисленные подкаталоги, по одному для каждого автора программного обеспечения. Например, если вы захотите найти знаменитый модуль CGI.pm Линкольна Штейна, будучи твердо уверены, что именно он его автор, то можете посмотреть в каталоге authors/Lincoln__Stein. Если бы вы не знали, что Штейн — автор этого модуля, то можно было бы посмотреть в каталоге модулей, который описывается ниже.
doc
Каталог, содержащий всевозможную документацию на Perl. Это вся официальная документация (man-страницы) в нескольких форматах (тек-стовый ASCII, HTML, PostScript и собственный формат Perl POD), а также сборники часто задаваемых вопросов и интересные дополнитель-ные документы.
modules
Каталог содержит отдельные модули, написанные на С, Perl или обоих этих языках. Расширения позволяют вам эмулировать и использовать функцио-нальные возможности других программных продуктов, например, графиче-ских средств Tk, UNIX-библиотеки curses и математических библиотек. Они позволяют также взаимодействовать с базами данных (Oracle, Sybase и др.) и создавать HTML-файлы и CGI-сценарии.
ports
Каталог содержит исходный код и (или) двоичные файлы для Perl-портов к операционным системам, не поддерживаемых непосредственно в стан-дартном дистрибутиве. Эти порты " результат усилий других авторов, и не все они могут функционировать так, как описано в нашей книге.
scripts
Набор разнообразных сценариев, собранных со всего мира. Если вам нужно узнать, как сделать что-либо, или если вы просто хотите посмотреть, как другие пишут программы, просмотрите этот каталог. Подкаталог nutshell содержит примеры, приведенные в нашей книге. (Эти тексты можно также получить на узле издательства O'Reilly & Associates, ftp,ora.com, в каталоге /published/oreMy/nutshell/leammg_perl2/.
src
В этом каталоге вы найдете исходный код стандартного дистрибутива Perl. Его текущая редакция всегда находится в файле src/lutest.tar.gz. Этот боль-шой файл содержит весь исходный код и полную документацию. Конфи-гурирование и инсталляция должны быть относительно простыми как в UNIX- и UNIX-подобных системах, так и в VMS и OS/2. Начиная с версии 5.004, Perl инсталлируется также в 32-разрядных Windows-системах.
Как распространяется Perl
Perl распространяется по одной из двух лицензий (на ваш выбор). Первая — стандартная форма GNU Copyleft. Коротко говоря, это означает, что если вы можете выполнять Perl в своей системе, то должны иметь доступ к полному исходному коду языка без дополнительной платы. Вторая фор-ма — Artistic License, которую некоторые (особенно юристы) находят менее угрожающей, нежели Copyleft.
В каталоге/^дистрибутива Perl вы найдете ряд программ-примеров. Есть и другие лакомые кусочки — можете посвятить их поиску один из дождливых дней. Изучите исходный код Perl (если вы — С-хакер с мазохистскими наклонностями). Взгляните на тестовый комплект. Посмотрите, как Configure определяет наличие системного вызова mkdir(l). Определите, как Perl выпол-няет динамическую загрузку С-модулей. В общем, делайте все, что вам по душе.
Как сократить объем вводимого
Мы могли бы перечислить все эти функции в списке, прилагаемом к оператору use, но такой список разросся бы до небывалых размеров. В CGI.pm, как и во многих других модулях, имеются так называемые директивы импорта — метки, которые обозначают группы импортируемых функций. Вам нужно лишь поставить желаемые директивы (каждая из которых начинается двоеточием) в начале своего списка импорта. В модуле CGI.pm имеются такие директивы:
: cgi
Импортировать все методы обработки аргументов, например param ().
: form
Импортировать все методы создания заполняемых форм, например text-field().
:html2
Импортировать все методы, которые генерируют стандартные элементы HTML 2.0.
:htmi3
Импортировать все методы, которые генерируют элементы, предложенные в HTML 3.0 (такие как <table>, <super> и <sub>).
:netscape
Импортировать все методы, которые генерируют расширения HTML, характерные для Netscape.
:shortcuts
Импортировать все сокращения, генерируемые HTML (т.е. "html2" + "html3" + "netscape").
:standard
Импортировать "стандартные" возможности: "html2", "form" и "cgi".
:all
Импортировать все имеющиеся методы. Полный список приведен в модуле CGI.pm, где определяется переменная %tags.
Мы будем использовать только директиву : standard. (Подробная информация об импортировании функций и переменных из модулей приведена в главе 7 книги Programming Perl, а также на man-странице Exporter 3).}
Вот как выглядит наша программа со всеми сокращениями, которые используются в CGI.pm:
#!/usr/local/bin/perlS -w
# cgi-bin/ice_cream # программа ответа на форму о любимом
t
сорте мороженого (версия 2) use CGI qw(:standard);print header() ;
print start_html("Hello World"), hi ("Hello World");
my $favorite = param("flavor");
print p("Your favorite flavor is $favorite.");
print end_html();
Видите, насколько это проще? Вам не нужно беспокоиться о декодировании данных формы, о заголовках и HTML-тексте, если вы этого не хотите.
Как выбирать модули
Процесс выборки и построения отдельных модулей Perl протекает немного по-другому. Скажем, вы хотите построить и инсталлировать модуль CoolMod. Сначала нужно выбрать его, воспользовавшись для этого командой ^(1) или с помощью Web-броузера обратиться к сервису модулей из hffp://www.perLcom/, который всегда выбирает самую последнюю версию конкретного зарегистри-рованного модуля. Адрес, который нужно ввести в броузер, выглядит так: http://www.perl.com/cgi-bin/cpanmod?module=CoolMod
Получив этот файл, сделайте следующее:
% gunzip < CoolMod-2.34. tar .gz I tar xvf -% cd CoolMod-2 .34'ъ perl Makefile.PL
(создает реальный Makefile)
^ make
(построить весь модуль)
^ make test
(проверить, работает ли он)
°J make install
(для этого вы, наверное, должны быть привилегированным пользователем)
После успешной инсталляции модуля CoolMod (он автоматически поме-щается в директорию Perl-библиотеки вашей системы) в ваших программах можно использовать use CooiMod; а вы сможете читать документацию этого модуля с помощью команды man CoolMod (или perldoc CoolMod).
Клиент webget
Клиент webget
Вот простой клиент, который устанавливает соединение с удаленным сервером и получает с него список документов. Этот клиент интереснее предыдущего, потому что перед получением ответа сервера он посылает на него строку данных.
#!/usr/bin/peri -w use 10::Socket;
unless (@ARGV > 1) ( die "usage: $0 host document ..." } $host = shift (OARGV);
foreach $document ( OARGV ) (
$remote == 10::Socket::INET->new( Proto => "tcp",
PeerAddr => $host,
PeerPort => "http (80)",
);
unless ($remote) ( die "cannot connect to http daemon on $host" )
$remote->autoflush(l) ;
print $remote "GET $document HTTP/I.0\n\n";
while ( <$remote> ) ( print )
-close $remote;
)
Подразумевается, что Web-сервер, на котором работает сервис http, использует свой стандартный порт (номер 80). Если сервер, с которым вы пытаетесь установить соединение, использует другой порт (скажем, 8080), то в качестве третьего аргумента конструктора new () нужно указать PeerPort => 8080. При работе с этим гнездом применяется метод autoflush, потому что в противном случае система буферизировала бы выходную информацию, которую мы ей передали. (Если у вас компьютер Macintosh, то нужно заменить все \п в коде, предназначенном для передачи данные по сети, на
\015\012.)
Соединение с сервером — это лишь первый этап процесса: установив соединение, вы должны начать говорить на языке этого сервера. Каждый сервер сети использует свой собственный маленький командный язык, и входные данные, подаваемые на сервер, должны быть сформулированы именно на этом языке. Начинающаяся словом GET строка, которую мы послали серверу, соответствует синтаксису протокола HTTP. В данном случае мы просто запрашиваем каждый из указанных документов. Да, мы действительно создаем новое соединение для каждого документа, несмотря на то, что это тот же самый хост. Именно так функционирует НТТР-серевер. (Последние версии Web-броузеров могут требовать, чтобы удаленный сервер оставлял соединение открытым на некоторое время, но сервер не обязан удовлетворять такой запрос.)
Мы назовем нашу программу webget. Вот как ее можно было бы выполнить:
shell prompt? webget www.peri.com /guanaco.html
HTTP/I.I 404 File Not Found
Date: Thu, 08 May 1997 18:02:32 GMT
Server: Apache/1.2b6
Connection: close
Content-type: text/html
<HEADXTITLE>404 File Not Found</TITLEX/HEAD>
<BODYXHl>File Not Found </H1>
The request URL /guanaco.html was not found on this server. <P>
</BODY>
Это, конечно, не очень интересно, потому что программа не нашла конкретный документ, однако длинный ответ не поместился бы на этой странице.
Чтобы ознакомиться с более развитой версией данной программы, вам нужно найти программу Iwp-request, входящую в состав модулей LWP из CPAN. (LWP мы вкратце рассмотрели в конце главы 19.)
Командная строка
Командная строка
При запуске интерпретатора Perl можно указывать множество ключей командной строки. См. man-страницу perlrun(\').
Литеральное представление хеша
Литеральное представление хеша
У вас может возникнуть необходимость обратиться к хешу целиком — например, чтобы инициализировать его или скопировать в другой хеш. Фактически в 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 они превратятся в один элемент, поэтому данную операцию лучше всего выполнять только над хешами с уникальными ключами и значениями.
Литеральное представление Списочный
(1,2,3) # массив из трех значений 1, 2 и 3 ("fred",4.5) # два значения — "fred" и 4,5
Элементы списка не обязательно должны быть константами. Это могут быть выражения, которые вычисляются при каждом использовании литерала. Например:
($а, 17; # два значения: текущее значение переменной $а и 17 ($b+$c,$d+$e) # два значения
Пустой список (список, не содержащий элементов) представляется пустой парой круглых скобок:
() # пустой список (нуль элементов)
Элемент списочного литерала может включать операцию конструктора списка. Это два скалярных значения, разделенных двумя точками. Данная операция создает список значений, начиная с левого скалярного значения и кончая правым скалярным значением, с шагом 1. Например:
(1 .. 5) # то же самое, что (1, 2, 3, 4, 5)
(1.2 .. 5.2) # то же самое, что (1.2, 2.2, 3.2, 4.2, 5.2)
(2 .. 6,10,12) # тоже самое, что (2,3,4,5,6,10,12)
($а .. $b) # диапазон, заданный текущими значениями переменных $а и $Ь
Если правый скаляр меньше левого, то список будет пустым, так как в обратном направлении отсчет вести нельзя. Если последнее значение не соответствует целому числу шагов, то список заканчивается там, где приращение на единицу привело бы к появлению числа, не принадлежащего заданному диапазону:
(1.3 .. 6.1) # то же самое, что (1.3,2.3,3.3,4.3,5.3)
Списочные литералы с множеством коротких текстовых строк со всеми этими кавычками и запятыми выглядят не очень привлекательно:
@а = ("fred","barney","betty","wilma"); # уф-ф!
Поэтому предусмотрена функция заключения в кавычки, которая создает список из разделенных пробелами слов, заключенных в круглые скобки*:
@а = qw(fred barney betty wilma); # так-то лучше! @а = qw(
fred
barney
betty
wilma ); # то же самое
Одно из возможных применений списочного литерала — в качестве аргумента функции print, с которой мы уже знакомы. Элементы списка выводятся на экран без промежуточных пробельных символов:
print("The answer is ",$a,"\n"); # трехэлементный списочный литерал
Этот оператор выводит на экран слова The answer is, затем пробел, значение переменной $а и символ новой строки. Другие способы использования списочных литералов мы рассмотрим позднее.
* Как и в функциях сопоставления с образцом, о которых мы узнаем позже, в качестве разделителя здесь вместо круглых скобок можно использовать любой символ, не относящийся к числу пробельных символов, букв и цифр.