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

Приложение А

    В этой главе:


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

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

Глава 2 "Скалярные данные"

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

$pi = 3.141592654;

$result = 2 * $pi * 12.5;

print "radius 12,5 is circumference $result\n";

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

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

print "What is the radius: ";

chomp($radius = <STDIN>) ;

$pi = 3.141592654;

$result = 2 * $pi * $radius;

print "radius $radius is circumference $result\n";

Это похоже на предыдущий пример, но здесь мы попросили пользователя, выполняющего программу (применив для выдачи приглашения оператор print), ввести значение. Считывание строки с терминала осуществляется посредством операции <stdin>.

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

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

print "First number: "; chomp($a = <STDIN>) ;

print "Second number: "; chomp($b = <STDIN>) ;

$c = $a * $b; print "Answer is $c.\n";

Первая строка делает три вещи: приглашает вас ввести число, считывает строку со стандартного ввода, а затем избавляется от неизбежного символа новой строки. Поскольку мы используем значение $а строго как число, функцию chomp здесь можно опустить, потому что в числовом контексте 45\n это 45. Однако столь небрежное программирование может впоследствии обернуться неприятностями (например, если нужно будет включить $а в сообщение).

Вторая строка делает то же самое со вторым числом и помещает его в скалярную переменную $Ь.

Третья строка перемножает эти два числа и выводит результат. Отметьте здесь наличие символа новой строки в конце строки (тогда как в первых двух строках он отсутствует). Первые два сообщения это приглашения, в ответ на которые пользователь должен ввести число в той же строке. Последнее сообщение это оператор; если бы мы выбросили символ новой строки, то сразу же за сообщением появилось бы приглашение shell. He очень-то хорошо.

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

print "String: "; = <STDIN>;

print "Number of times: "; chomp($b = <STDIN>) ;

$c = $a x $b; print "The result is:\n$c";

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

Глава 3 "Массивы и списочные данные"

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

print "Enter the list of strings:\n";

@list = <STDIN>;

Sreverselist = reverse @list;

print @reverselist;

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

Последние три строки можно объединить:

print "Enter the list of strings:\n";

print reverse <STDIN>;

Этот код работает аналогично предыдущему, так как операция print ожидает ввода списка, а операция reverse возвращает список. Далее операции reverse нужен список значений для реверсирования, а операция <stdin>, применяемая в списочном контексте, возвращает список строк и они получают необходимое!

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

print "Enter the line number: "; chomp($a = <STDIN>) ;

print "Enter the lines, end with "D:\n"; @b = <STDIN>;

print "Answer: $b[$a-l]";

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

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

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

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

srand;

print "List of strings: "; @b = <STDIN>;

print "Answer: $b[rand (@b)]";

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

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

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

print "What temperature is it? ";

chomp($temperature <STDIN>);

if ($temperature > 72) {

print "Too hot!\n";

} else (

print "Too cold!\n";

>

Первая строка приглашает ввести температуру. Вторая строка принимает введенное значение температуры. Оператор if в последних пяти строках выбирает для вывода одно из двух сообщений в зависимости от значения переменной $temperature.

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

print "What temperature is it? ";

chomp($temperature = <STDIN>) ;

if ($temperature > 75) (

print "Too hot!\n";

} elsif ($temperature < 68) (

print "Too cold!\n";

) else {

print "Just right!\n";

1

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

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

print "Enter a number (999 to quit): ";

chomp($n = <STDIN>) ;

while ($n != 999) f

$sum += $n;

print "Enter another number (999 to quit): ";

chomp($n = <STDIN>);

1 print "the sum is $sum\n";

Первая строка приглашает ввести первое число. Вторая строка считывает это число с терминала. Цикл while продолжает выполняться до тех пор, пока число не станет равным 999.

Операция += накапливает числа в переменной $sum. Обратите внимание:

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

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

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

Если сразу же ввести 999, то значение переменной $sum будет равно не нулю, а пустой строке т.е. значению undef в строковом контексте. Если вы хотите, чтобы программа в этом случае выводила нуль, нужно в начале программы инициализировать значение $s urn операцией $ sum = 0.

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

print "Enter some strings, end with "D:\n";

@strings = <STDIN>;

while (Ostrings) (

print pop @strings;

}

Сначала программа запрашивает строки. Эти строки сохраняются в переменной-массиве @strings (по одной на элемент).

Управляющее выражение цикла while Sstrings. Это управляющее выражение ищет только одно значение ("истина" или "ложь"), поэтому вычисляет выражение в скалярном контексте. Имя массива (такое как @ strings) при использовании в скалярном контексте представляет собой количество элементов, находящихся в массиве в текущий момент. Поскольку массив не пуст, это число не равно нулю и, следовательно, имеет значение "истина". Эта идиома очень широко используется в Perl, она соответствует указанию "делать это, пока массив не пуст".

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

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

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

for ($number = 0; $number <= 32; $number++) {

$square = $number * $number;

printf "%5g %8g\n", $number, $square;

}

А вот как можно решить задачу с помощью списка:

foreach $number (0..32) (

$square = $number * $number;

printf "%5g %8g\n", $number, $square;

}

В обоих решениях применяются циклы с использованием операторов for и foreach. Тела этих циклов идентичны, потому что в обоих решениях значение переменной $ number при каждой итерации изменяется от 0 до 32.

В первом решении использован традиционный С-подобный оператор for. Первое выражение устанавливает переменную $number в 0, второе проверяет, меньше ли $number, чем 32, а третье инкрементирует $number при каждой итерации.

Во втором решении использован оператор foreach, подобный аналогичному оператору C-shell. С помощью конструктора списка создается список из 33 элементов (от 0 до 32). Затем переменной $number поочередно присваиваются значения, равные этим элементам.

Глава 5 "Хеши"

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

%map = qwfred apple green leaves blue ocean);

print "A string please: "; chomp($some_string = <STDIN>);

print "The value for $some_string is $map($some_string(\n";

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

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

$map('red') = 'apple';

$map('green'( = 'leaves';

$map('blue'} = 'осеап',-

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

chomp(@words = <STDIN>); # читать слова минус символы новой строки foreach $word (@words) (

$count{$word} = $count($word} + 1; # или $count{$word}++ t foreach $word (keys %count) {

print "$word was seen $count($word) times\n";

}

Первая строка считывает строки в массив @ words. Вспомните: в результате выполнения этой операции каждая строка становится отдельным элементом массива, причем символ новой строки останется нетронутым.

В следующих четырех строках осуществляется обход массива, при этом $word приравнивается по очереди каждой строке. Функция chomp отсекает символ новой строки, а потом начинается волшебство. Каждое слово используется как ключ хеша. Значение элемента, выбранного по этому ключу (слову), представляет собой значение счетчика повторений данного слова до текущего момента. Сначала в хеше элементов нет, поэтому если слово wild встречается в первой строке, то $count {"wild"} будет содержать undef. Это значение undef плюс единица оказывается равным нулю плюс единица, то есть единице. (Напомним, что при использовании в качестве числа undef означает нуль.) При следующем проходе у нас будет единица плюс единица, или два, и т.д.

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

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

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

Глава 6 "Базовые средства ввода-вывода^

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

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

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

Глава 8 "Функции"

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. Вот так-то!

Глава 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 выдает отчет.

Глава 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

Здесь для получения заголовков страниц мы опять-таки ввели формат начала страницы. Этот формат содержит также ссылку на переменную $%, которая автоматически присваивает странице номер.

Глава 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, нет.

Глава 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 выводит название символической ссылки и имя файла или директории, на которую она указывает.

Глава 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 присваивает этому каталогу соответствующие права доступа.

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

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$_";

}

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

Глава 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

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

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

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, рассортированных по их значениям (т.е. количеству повторений) в убывающем порядке. Для каждого элемента списка мы выводим слово и количество его повторений.

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

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

for (;;) (

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

last unless $user;

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

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

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

last;

} } }

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

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

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

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

while.

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

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

use strict;

use CGI qw (:standard);

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

if (paramO) {

my $nl = param('fieldl');

my $n2 = param('field2');

my $n3 = $n2 + $nl;

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

} else (

print hr(), start_form();

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

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

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

print end_form(), hr () ;

} print end_html();

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

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

use strict;

use CGI qw(:standard);

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

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

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

$_ '" $browser;

BROWSER:(

if (/msie/i) (

msie($_) ;

) elsif (/mozilla/i) (

netscape($_) ;

) elsif (/lynx/i) (

lynx($_);

1 else (

default($_) ;

> >

print end_html() ;

sub msie)

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

}

sub netscape (

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

t

sub lynx {

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

(

sub default (

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

}

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




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


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