«Большая стрелка»

При присваивании списка хешу иногда бывает неочевидно, какие элементы списка являются ключами, а какие – значениями. Например, если мы захотим узнать, чем является 2.5 в следующей команде присваивания (которая уже встречалась нам ранее), нам придется вручную отсчитывать элементы списка – «ключ, значение, ключ, значение»:

%some_hash = ("foo", 35, "bar", 12.4, 2.5, "hello",
"wilma", 1.72e30, "betty", "bye\n");


Разве не удобно было бы иметь механизм определения пар «ключ-значение» в таких списках, чтобы мы могли с первого взгляда определить, чем является тот или иной элемент? Ларри тоже так думал, поэтому он изобрел «большую стрелку» =>. С точки зрения Perl «большая стрелка» является всего лишь альтернативным способом «изображения» запятой, поэтому иногда ее называют «жирной запятой». Иначе говоря, в грамматике Perl во всех ситуациях, где может использоваться запятая (,), ее можно заменить «большой стрелкой»; для Perl эти обозначения эквивалентны. А следовательно, определение хеша с именами и фамилиями может выглядеть так:

my %last_name = ( # Хеш может быть лексической переменной
"fred" => "flintstone",
"dino" => undef,
"barney" => "rubble",
"betty" => "rubble",
);


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

Добавлено: 20 Июля 2018 08:42:43 Добавил: Андрей Ковальчук

Присваивание хешей

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

%new_hash = %old_hash;


В действительности при этом Perl выполняет больше работы, чем видно на первый взгляд. В отличие от языков типа Pascal или C, где такая операция сводилась бы к простому копированию блока памяти, структуры данных Perl более сложные. Следовательно, эта строка кода приказывает Perl раскрутить %old_hash в список пар «ключ/значение», а затем последовательно, пару за парой, присвоить его %new_hash. Впрочем, чаще всего присваивание хешей сопровождается некоторым преобразованием. Например, хеш можно обратить, то есть переставить его элементы в обратном порядке:

%inverse_hash = reverse %any_hash;


Команда берет %any_hash и раскручивает его в список пар «ключ-значение»; образуется список вида (ключ, значение, ключ, значение…). Вызов reverse переставляет элементы списка в обратном порядке: (значение, ключ, значение, ключ…). Теперь ключи стоят там, где раньше были значения, и наоборот. При сохранении результата в %inverse_hash выборка производится по строкам, которые были значениями в %any_hash, – в %inverse_hash они стали ключами. А в результате выборки мы получаем значение, которое принадлежало в числу ключей %any_hash. Таким образом, выборка по «значению» (которое стало ключом) дает «ключ» (который стал значением). Конечно, обращенный хеш будет нормально работать только в том случае, если значения исходного хеша были уникальными, в противном случае в новом хеше появятся дубликаты ключей, а ключи всегда должны быть уникальными. В подобных ситуациях в Perl используется простое правило: последнее добавление элемента побеждает. Иначе говоря, элементы, добавленные в список позднее, заменяют более ранние элементы. Конечно, мы не знаем, в каком порядке пары «ключ-значение» будут храниться в списке, поэтому невозможно заранее предсказать, какая пара «победит». Этот прием следует использовать только в том случае, если вы знаете, что среди исходных значений нет дубликатов. Но в приводившемся ранее примере с IP-адресами и именами хостов дело обстоит именно так:

%ip_address = reverse %host_name;


Теперь можно одинаково легко осуществлять поиск как по IP-адресу, так и по имени хоста.

Добавлено: 20 Июля 2018 08:42:12 Добавил: Андрей Ковальчук

Хеш как единое целое

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

%some_hash = ("foo", 35, "bar", 12.4, 2.5, "hello",
"wilma", 1.72e30, "betty", "bye\n");


Значение хеша (в списочном контексте) представляет собой простой список пар «ключ-значение»:

@any_array = %some_hash;


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

print "@any_array\n";
# Примерный результат:
# betty bye (новая строка) wilma 1.72e+30 foo 35 2.5 hello bar 12.4


Порядок изменился, потому что Perl хранит пары «ключ-значение» в удобном для себя порядке, обеспечивающем максимальную скорость поиска. Хеш используется в тех случаях, когда порядок элементов неважен или имеется простой способ приведения их к желаемому порядку. Конечно, несмотря на изменение порядка пар «ключ-значение», каждый ключ остается «прикрепленным» к соответствующему значению в итоговом списке. Таким образом, хотя мы не знаем, в какой позиции списка будет находиться ключ foo, можно быть уверенным в том, что соответствующее значение (38) будет находиться сразу же за ним.

Добавлено: 20 Июля 2018 08:23:54 Добавил: Андрей Ковальчук

Обращение к элементам хеша

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

$hash{$some_key}


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

$family_name{"fred"} = "flintstone";
$family_name{"barney"} = "rubble";


Это позволяет применять конструкции следующего вида:

foreach $person (qw< barney fred >) {
print "I've heard of $person $family_name{$person}.\n";
}


Имена хешей подчиняются тем же правилам, что и остальные идентификаторы Perl (последовательность букв, цифр и символов подчеркивания, которая не может начинаться с цифры). Кроме того, они существуют в отдельном пространстве имен, т. е. элемент хеша $family_name{"fred"} никак не связан, например, с пользовательской функцией &family_name. Конечно, не стоит запутывать программу, присваивая разным сущностям одинаковые имена. Но Perl не станет возражать, если вы создадите скаляр с именем $family_name и элементы массива $family_name. Мы, люди, должны поступать так же, как поступает Perl, – смотреть на знаки до и после идентификатора, чтобы понять, что он означает. Если перед именем стоит знак $, а после него – фигурные скобки, значит, происходит обращение к элементу хеша. Разумеется, ключ хеша может задаваться любым выражением, не только строковыми литералами и простыми скалярными переменными:

$foo = "bar";
print $family_name{ $foo . "ney" }; # Выводит "rubble"


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

$family_name{"fred"} = "astaire"; # Задает новое значение
# для существующего элемента
$bedrock = $family_name{"fred"}; # Возвращает "astaire"; старое
# значение теряется


По сути происходит то же самое, что при использовании массивов и скаляров: если вы сохраняете новое значение в $pebbles или $dino, старое значение при этом заменяется. Если сохранить новое значение в $family_name{"fred"}, старое значение также будет заменено. Элементы хешей создаются присваиванием:

$family_name{"wilma"} = "flintstone"; # Добавляет новый ключ
# (и значение)
$family_name{"betty"} .= $family_name{"barney"}; # Создает новый элемент
# в случае необходимости


И здесь происходит то же самое, что с массивами и скалярами: если ранее элемент $pebbles или переменная $dino не существовали, они автоматически создаются присваиванием. Если ранее элемент $family_name{"betty"} не существовал, теперь он существует. При обращении к несуществующему элементу хеша возвращается
undef:

$granite = $family_name{"larry"}; # Ключа larry нет: undef


И снова здесь происходит то же, что с массивами и скалярами: если элемент $pebbles или переменная $dino не были инициализированы, обращение к ним возвращает undef. Если элемент хеша $family_name{"larry"} не был инициализирован, обращение к нему также возвращает undef.

Добавлено: 20 Июля 2018 08:23:23 Добавил: Андрей Ковальчук

Зачем использовать хеш?

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

Имя, фамилия

Имя (ключ) используется для выборки фамилии (значение). Например, по ключу tom хеш вернет значение phoenix. Конечно, для этого имена должны быть уникальными; если в данных присутствуют два человека с именем randal, хеш работать не будет.

Имя хоста, IP-адрес
Вероятно, вы знаете, что каждый компьютер в Интернете обладает именем хоста (вида http://www.1111.com) и числовым IP-адресом вида 123.45.67.89. Компьютерам удобнее работать с числами, а люди предпочитают запоминать имена. Имена хостов являются уникальными строками и могут использоваться в качестве ключей хеша. По имени хоста программа находит соответствующий IP-адрес.

IP-адрес, имя хоста
Возможно и обратное преобразование. Обычно мы рассматриваем IP-адрес как серию чисел, но его также можно преобразовать в уникальную строку, поэтому IP-адреса могут использоваться в качестве ключей хеша для выборки соответствующих имен хостов. Обратите внимание: этот хеш не эквивалентен описанному в предыдущем примере; выборка в хешах производится только в одном направлении – от ключа к значению. Невозможно передать значение и получить соответствующий ему ключ! Таким образом, эти два хеша составляют пару: в одном хранятся IP-адреса, а в другом имен хостов. Впрочем, как вы вскоре увидите, при наличии одного хеша можно легко построить другой.

Слово, количество экземпляров
Очень типичное использование хеша. Оно настолько типично, что даже войдет в одно из упражнений в конце главы! Идея проста: вы хотите знать, сколько раз некоторое слово встречается в документе. Представьте, что вы индексируете группу документов, чтобы при поиске по строке fred узнать, что в одном документе строка fred встречается пять раз, в другом – семь, а в третьем не упоминается вовсе. Это позволит ранжировать документы по количеству вхождений. Когда программа индексации читает документ, при каждом обнаружении строки fred она увеличивает значение, связанное с ключом fred, на 1. Таким образом, если ранее строка fred дважды встречалась в документе, значение в хеше будет равно 2, а теперь оно увеличивается до 3. Если строка fred ранее не встречалась, значение изменяется с undef (значение по умолчанию) на 1.

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

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

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

Добавлено: 20 Июля 2018 08:22:38 Добавил: Андрей Ковальчук

Что такое хеш?

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

Ключи являются произвольными строками – любое строковое выражение может использоваться в качестве ключа хеша. К тому же они уникальны: подобно тому как в массиве имеется только один элемент с номером, в хеше существует только один элемент с ключом wilma. Хеш также можно представить как «бочку с данными», в которой к каждому элементу прикреплен ярлык. Вы можете запустить руку в бочку, вытащить любой ярлык и посмотреть, какие данные на нем «висят». Однако в бочке не существует «первого» элемента, все элементы лежат вперемежку. В массиве перебор начинается с элемента 0, затем следует элемент 1, затем элемент 2 и т. д. В хеше нет ни фиксированного порядка, ни первого элемента. Его содержимое представляет собой множество пар «имя-значение».

Ключи и значения являются произвольными скалярными значениями, но ключи всегда преобразуются в строки. Следовательно, если использовать числовое выражение 50/20 в качестве ключа1, оно преобразуется в строку из трех символов "2.5", соответствующую одному из ключей. Как обычно, действует принятая в Perl философия «отсутствия искусственных ограничений»: хеш может иметь произвольный размер – от пустого хеша с нулем пар «ключ-значение» до заполнения всей свободной памяти. Некоторые реализации хешей (например, в языке awk, из которого Ларри позаимствовал идею) с увеличением хеша работают все медленнее и медленнее. В Perl это не так – в нем используется хороший эффективный масштабируемый алгоритм. Таким образом, если хеш состоит всего из трех пар «ключ-значение», Perl очень быстро «запускает руку в бочку» и извлекает нужный элемент. Если хеш состоит из трех миллионов пар, выборка пройдет практически с такой же скоростью. Не бойтесь больших хешей. Также стоит снова напомнить, что ключи всегда уникальны, тогда как значения могут повторяться. В хеше могут храниться числа, строки, значения undef в любых комбинациях. При этом ключи должны быть произвольными, но уникальными строками.

Добавлено: 20 Июля 2018 08:22:08 Добавил: Андрей Ковальчук

Вывод функцией say

Perl 5.10 позаимствовал встроенную функцию say из текущей разработки Perl 6 (которая, возможно, взяла за образец функцию println из Pascal). Функция say делает то же, что print, но завершает вывод символом новой строки. Все следующие команды выводят одинаковые строки:

use 5.010;
print "Hello!\n";
print "Hello!", "\n";
say "Hello!";

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

use 5.010;
my $name = 'Fred';
print "$name\n";
print $name, "\n";
say $name;


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

use 5.010;
my @array = qw( a b c d );
say @array; # "abcd\n"
say "@array"; # "a b c d\n";


По аналогии с print при вызове say можно задать файловый дескриптор:

use 5.010;
say BEDROCK "Hello!";


Так как функция относится к числу нововведений Perl 5.10, мы используем ее только в том случае, если в программе задействованы другие возможности Perl 5.10. Старая верная команда print работает ничуть не хуже, но мы подозреваем, что некоторые программисты Perl с радостью избавятся от ввода четырех лишних символов (два в имени и \n).

Добавлено: 20 Июля 2018 08:21:44 Добавил: Андрей Ковальчук

Повторное открытие стандартного файлового дескриптора

Ранее мы уже упоминали о том, что при повторном открытии файлового дескриптора (то есть если вы открываете файловый дескриптор FRED, хотя в программе уже имеется открытый файловый дескриптор FRED) старый дескриптор закрывается автоматически. И мы сказали, что использовать имена шести стандартных файловых дескрипторов нежелательно, если только вы не собираетесь наделить их специальными возможностями. Также мы говорили, что сообщения die и warn вместе с внутренними сообщениями Perl автоматически направляются в STDERR. Если объединить все сказанное, становится ясно, как направить сообщения об ошибках в файл вместо стандартного потока ошибок вашей программы:

# Отправлять ошибки в личный журнал
if ( ! open STDERR, ">>/home/barney/.error_log") {
die "Can't open error log for append: $!";
}


После повторного открытия STDERR все сообщения об ошибках, поступающие от Perl, заносятся в новый файл. Но что произойдет при выполнении die – куда попадет это сообщение, если новый файл не удалось открыть для приема сообщений? Если при повторном открытии одного из трех системных файловых дескрипторов – STDIN, STDOUT или STDERR – произойдет ошибка, Perl любезно восстанавливает исходное значение. То есть Perl закрывает исходный дескриптор (любой из этих трех) только тогда, когда он видит, что открытие нового канала прошло успешно. Таким образом, этот прием может использоваться для перенаправления любых из трех системных файловых дескрипторов внутри вашей программы – почти так же, как если бы программа изначально была запущена в командном процессоре с перенаправлением ввода/вывода.

Добавлено: 20 Июля 2018 08:21:08 Добавил: Андрей Ковальчук

Изменение файлового дескриптора вывода по умолчанию

По умолчанию, если при вызове print (или printf – все, что мы здесь говорим об одной функции, в равной степени относится к другой) не указан файловый дескриптор, данные направляются в STDOUT. Однако этот файловый дескриптор по умолчанию можно сменить оператором select. В следующем фрагменте несколько выходных строк отправляются в BEDROCK:

select BEDROCK;
print "I hope Mr. Slate doesn't find out about this.\n";
print "Wilma!\n";


Файловый дескриптор, выбранный по умолчанию для вывода, остается выбранным на будущее. Чтобы не создавать путаницу в программе, обычно рекомендуется возвращать STDOUT на место сразу же после завершения.1 По умолчанию вывод в каждый файловый дескриптор буферизуется. Если задать специальной переменной $| значение 1, текущий файловый дескриптор (то есть выбранный на момент изменения переменной) всегда сбрасывает буфер после каждой операции вывода. Таким образом, если вы хотите быть уверены в том, что все записи сохраняются в журнале одновременно (например, если вы читаете данные из журнала, отслеживая ход продолжительного выполнения программы), используйте конструкцию следующего вида:

select LOG;
$| = 1; # Не оставлять данные LOG в буфере
select STDOUT;
# ... Проходят годы, сдвигаются тектонические плиты, а потом...
print LOG "This gets written to the LOG at once!\n";

Добавлено: 20 Июля 2018 08:20:44 Добавил: Андрей Ковальчук

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

Когда файловый дескриптор будет открыт для чтения, чтение строк из него осуществляется практически так же, как чтение из стандартного ввода с дескриптором STDIN. Например, чтение строк из файла паролей UNIX происходит так:

if ( ! open PASSWD, "/etc/passwd") {
die "How did you get logged in? ($!)";
}
while (<PASSWD>) {
chomp;
...
}


В этом примере в сообщении die переменная $! заключена в круглые скобки. Это всего лишь литеральные символы, в которых выводится сообщение в выходных данных. (Иногда в Perl знак препинания – это просто знак препинания.) Как видите, то, что мы называли «оператором построчного вывода», в действительности состоит из двух компонентов: угловых скобок (собственно оператор построчного ввода) и заключенного в них файлового дескриптора. Файловый дескриптор, открытый для записи или присоединения, может использоваться с функцией print или printf. Он указывается сразу же после ключевого слова, но перед списком аргументов:

print LOG "Captain's log, stardate 3.14159\n"; # Ввыод направляется в LOG
printf STDERR "%d percent complete.\n", $done/$total * 100;


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

printf (STDERR "%d percent complete.\n", $done/$total * 100);
printf STDERR ("%d percent complete.\n", $done/$total * 100);

Добавлено: 20 Июля 2018 08:20:18 Добавил: Андрей Ковальчук

Предупреждающие сообщения и функция warn

Подобно тому как функция die выдает фатальную ошибку, сходную со встроенными ошибками Perl (например, делением на нуль), функция warn выдает предупреждение, сходное с встроенными предупреждениями Perl (например, если undef используется так, как если бы это было определенное значение, при включенном режиме предупреждений). Функция warn работает так же, как die, кроме последнего шага – она не прерывает работу программы. Но она, так же как die, выводит имя программы и номер строки и отправляет сообщение в стандартный вывод. Итак, от разговоров о «смерти» программ и суровых предупреждениях мы возвращаемся к обычному учебному материалу о вводе/выводе. Продолжайте читать.

Добавлено: 20 Июля 2018 08:19:47 Добавил: Андрей Ковальчук

Фатальные ошибки и функция die

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

Функция die выводит сообщение, переданное при вызове (в стандартный поток ошибок, куда должны поступать такие сообщения), и обеспечивает выход из программы с ненулевым кодом завершения. Возможно, вы и не знали об этом, но каждая программа, выполняемая в UNIX (и многих других современных операционных системах), обладает числовым кодом завершения, который сообщает, успешно была выполнена программа или нет. Программы, запускающие другие программы (например, утилита make), по коду завершения проверяют, правильно ли прошло выполнение. Код завершения состоит всего из одного байта, и в него трудно уместить сколько-нибудь подробное описание. Традиционно значение 0 считалось признаком успешного выполнения, а любое ненулевое значение – признаком ошибки. Допустим, код 1 может означать синтаксическую ошибку в аргументах команды, 2 – ошибку на стадии обработки, 3 – ошибку при поиске конфигурационного файла; конкретные значения определяются для каждой команды. Но значение 0 всегда означает, что выполнение прошло нормально. Если код завершения указывает на возникшую ошибку, программа (например, make) знает, что к следующему этапу переходить не следует. Таким образом, предыдущий пример можно переписать в следующем виде:

if ( ! open LOG, ">>logfile") {
die "Cannot create logfile: $!";
}


Если вызов open завершается неудачей, die завершает программу и сообщает, что создать файл журнала не удалось. Но что делает $! в сообщении? Это системное описание ошибки в форме, понятной для человека. В общем случае, когда система отказывается выполнить запрос программы (например, открыть файл), в переменной $! указывается причина (например, «отказано в доступе» или «файл не найден»). Эту же строку можно получить при помощи perror в языке C или других языках. В Perl это сообщение содержится в специальной переменной $!. Желательно включать эту переменную в сообщение; она поможет пользователю разобраться в том, что же было сделано неправильно. Но если die применяется для обозначения ошибок, происходящих не в результате ошибки по системному запросу, не включайте переменную $! в сообщение; обычно в ней будет содержаться постороннее сообщение, оставшееся от внутренних операций Perl. Полезная информация останется только после ошибок, происходящих при выполнении системных запросов. При удачном завершении запроса никакой полезной информации здесь не останется.

Есть еще одна операция, которую die автоматически выполнит за вас: к сообщению присоединяется имя программы Perl и номер строки, что позволит вам легко определить, какая команда die в программе ответственна за преждевременный выход. Сообщение об ошибке из предыдущего кода может выглядеть так (предполагается, что переменная $! содержит сообщение permission denied):

Cannot create logfile: permission denied at your_program line 1234.


Весьма полезные сведения – обычно нам хочется получать в сообщениях об ошибках больше информации, чем мы сами в них включаем. Если вы не хотите выдавать номер строки и имя файла, проследите за тем, чтобы «предсмертное сообщение» завершалось символом новой строки:

if (@ARGV < 2) {
die "Not enough arguments\n";
}


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

Добавлено: 20 Июля 2018 08:19:26 Добавил: Андрей Ковальчук

Закрытие файлового дескриптора

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

close BEDROCK;


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

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

Добавлено: 20 Июля 2018 08:18:56 Добавил: Андрей Ковальчук

Недопустимые файловые дескрипторы

В действительности Perl не может открыть файл своими силами. Он, как и другие языки программирования, всего лишь обращается к операционной системе с запросом на открытие файла. Конечно, операционная система может отказать изQза разрешений доступа, неверного имени файла или по другим причинам. Попытавшись прочитать данные из недопустимого файлового дескриптора (т. е. из файлового дескриптора, который не был открыт положенным образом), вы немедленно получите признак конца файла. (Для механизмов ввода/вывода, представленных в этой главе, конец файла обозначается undef в скалярном контексте или пустым списком в списочном контексте.) Если вы попытаетесь записать данные в недопустимый файловый дескриптор, эти данные просто пропадут. К счастью, всех этих неприятных последствий легко избежать. Прежде всего, при включенном режиме предупреждений ключом –w или директивой use warnings Perl подскажет, если мы используем недопустимый дескриптор. Но даже до этого open всегда сообщает, успешно ли завершился вызов, возвращая true в случае успеха или false в случае неудачи. Это позволяет применять конструкции следующего вида:

my $success = open LOG, ">>logfile"; # Сохранение возвращаемого значения
if ( ! $success) {
# Вызов open завершился неудачей
...
}


Да, так поступить можно. Но существует и другой способ, который будет рассмотрен в следующем разделе.

Добавлено: 20 Июля 2018 08:18:31 Добавил: Андрей Ковальчук

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

Итак, Perl предоставляет три стандартных дескриптора STDIN, STDOUT и STDERR, автоматически открытых для файлов и устройств, заданных родительским процессом (вероятно, командным процессором). Если вам понадобятся другие файловые дескрипторы, воспользуйтесь оператором open. Этот оператор выдает запрос к операционной системе на открытие канала ввода/вывода между вашей программой и внешним миром. Несколько примеров:

open CONFIG, "dino";
open CONFIG, "<dino";
open BEDROCK, ">fred";
open LOG, ">>logfile";


Первая команда открывает файловый дескриптор с именем CONFIG для файла dino. Иначе говоря, (существующий) файл dino открывается, а все хранящиеся в нем данные становятся доступными для программы через файловый дескриптор CONFIG. Происходящее напоминает обращение к данным файла через STDIN при включении в командную строку конструкций перенаправления командного процессора вроде <dino. Более того, во втором примере использована именно такая конструкция. Вторая команда делает то же, что и первая, но знак < явно указывает, что файл должен использоваться для ввода, хотя это и так делается по умолчанию. Использовать знак < при открытии файла для ввода необязательно, но мы включаем его, потому что, как показано в третьем примере, со знаком > файл открывается для вывода. Файловый манипулятор BEDROCK открывается для вывода в новый файл с именем fred. По аналогии с тем, как знак > используется для перенаправления вывода в командном процессоре, выходные данные направляются в новый файл с именем fred. Если файл с таким именем уже существует, он стирается и заменяется новым.

Четвертый пример показывает, как использовать два знака > (тоже по аналогии с командным процессором) при открытии файла в режиме присоединения. А именно, если файл уже существует, новые данные будут дописываться в конец. Если же файл не существует, он создается по аналогии с режимом >. Например, режим присоединения удобен при работе с журнальными файлами; программа записывает несколько строк в конец журнала при каждом запуске. Собственно, поэтому в четвертом примере файловый дескриптор назван LOG («журнал»), а файл – logfile.

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

my $selected_output = "my_output";
open LOG, "> $selected_output";


Обратите внимание на пробел после знака >. Perl игнорирует его, но пробел предотвращает неприятные сюрпризы – например, если $selected_output будет содержать строку ">passwd" (что приведет к открытию файла в режиме присоединения, а не в режиме записи). В современных версиях Perl (начиная с Perl 5.6) стал возможен вызов open с тремя аргументами:

open CONFIG, "<", "dino";
open BEDROCK, ">", $file_name;
open LOG, ">>", &logfile_name();


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

Добавлено: 20 Июля 2018 08:17:57 Добавил: Андрей Ковальчук