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

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

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

open

(с немного странным синтаксисом).

use strict;
use warnings;

my $filename = 'отчет.txt';
open(my $fh, '>', $filename) or die "Не могу открыть '$filename' $!";
print $fh "Мой первый отчет, сгенерированный с помощью perl\n";
close $fh;
print "готово\n";

Это хороший рабочий пример и мы к нему еще вернемся, но сперва давайте попробуем пример попроще:

Простой пример

use strict;
use warnings;

open(my $fh, '>', 'отчет.txt');
print $fh "Мой первый отчет, сгенерированный с помощью perl\n";
close $fh;
print "готово\n";

Здесь тоже нужны кое-какие объяснения. Функция open принимает 3 параметра.

Первый, $fh, это скалярная переменная, которую мы объясляем в вызове open(). Мы могли бы объявить ее раньше, но обычно проще объявить ее прямо в вызове, хотя на первый взгляд это может выглядеть немного странно. Второй параметр определяет, каким образом мы открываем файл. В данном случае мы поставили знак "больше" (>), что значит, что файл открывается для записи. Третий параметр - путь к файлу, который мы хотим открыть.

Когда эта функция вызывается, она присваивает переменной $fh специальный ключ, который называется указателем файла (file-handle). Нам не важно само содержимое этой переменной; в дальнейшем мы просто используем ее. Главное, обратите внимание, что содержимое файла по-прежнему находится на диске, и НЕ попадает в переменную $fh.

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

Этот вызов print() запишет текст в наш файл.

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

Последняя строчка print "done\n" нужна только для того, чтобы пояснить следующий пример:

Обработка ошибок

Давайте возьмем предыдущий пример и заменим имя файла на несуществующий путь. Например:

open(my $fh, '>', 'некое_странное_название/отчет.txt');

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

print() on closed file-handle $fh at ...
готово

На самом деле, это просто предупреждение; скрипт продолжает выполняться, и поэтому мы увидим на экране слово "готово".

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

В любом случае, налицо проблема. Мы попытались открыть файл. Это не получилось, но мы все равно пытались туда что-то записать.

Лучше бы нам проверить, успешно ли сработал open(), прежде чем продолжать.

К счастью, вызов open() сам по себе возвращает TRUE в случае успеха и FALSE при отказе, так что мы можем сделать так:

Открой или умри (open or die)

open(my $fh, '>', 'некое_странное_название/отчет.txt') or die;

Это "стандартная идиома" open or die. Очень часто встречается в Perl.

die - это вызов функции, которая бросит исключение и таким образом завершит наш скрипт.

"open or die" это логическое выражение. Как вы знаете из предыдущей части учебника, "or" в Perl (как и во многих других языках) сокращается. Это значит, что если левая часть вернет TRUE, сразу понятно, что все выражение будет равно TRUE, так что правая часть вообще не выполняется. С другой стороны, если левая часть вернет FALSE, то правая часть выполнится, и результат ее выполнения и будет результатом всего выражения.

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

Если open() выполнится успешно, он вернет TRUE, и правая часть так и не выполнится. Скрипт просто перейдет к следующей строчке.

Если же open() не выполнится, он вернет FALSE. Тогда выражение справа от or тоже выполняется. Это приводит к исключению, и скрипт завершается.

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

Если мы запустим скрипт с этим изменением, мы получим сообщение об ошибке:

Died at ...

и НЕ увидим "готово".

Улучшаем сообщение об ошибке

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

open(my $fh, '>', 'некое_странное_название/отчет.txt')
  or die "Не могу открыть файл 'некое_странное_название/отчет.txt'";

выдаст

  Не могу открыть файл 'некое_странное_название/отчет.txt' ...

Так-то лучше, но в какой-то момент кто-нибудь попробует поменять путь на корректную директорию...

open(my $fh, '>', 'корректная_директория_с_опечаткой/отчет.txt')
  or die "Не могу открыть файл 'некое_странное_название/отчет.txt'";

...но сообщение об ошибке будет старым, потому что путь поменяли только в вызове open(), но не в сообщении.

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

my $filename = 'некое_странное_название/отчет.txt';
open(my $fh, '>', $filename) or die "Не могу открыть файл '$filename'";

Теперь у нас правильное сообщение об ошибке, но мы по-прежнему не знаем, почему она произошла. Пойдем на шаг дальше, и используем $! - встроенную переменную Perl - чтобы выдать то, что нам сообщила система об ошибке:

my $filename = 'корректная_директория_с_опечаткой/отчет.txt';
open(my $fh, '>', $filename) or die "Не могу открыть файл '$filename' $!";

Этот код выдаст

Не могу открыть файл 'корректная_директория_с_опечаткой/отчет.txt' No such file or directory ...

Так гораздо лучше.

Ну а теперь вернемся к исходному примеру.

Больше?

Знак "больше" в вызове open может показаться непонятным, но если вы знакомы с перенаправлениями в командной строке, вы поймете, что он значит. А если нет, просто представьте, что это стрелка, показывающая направление потока данных: в файл справа.

Не латиница?

Если вам нужно работать с символами, не входящими в таблицу ASCII, вам стоит сохранять их в UTF-8. Чтобы это сделать, нужно сообщить Perl'у, что вы открываете файл в кодировке UTF-8.

open(my $fh, '>:encoding(UTF-8)', $filename)
  or die "Не могу открыть файл '$filename'";