В этой части Учебника Perl мы узнаем, как читать файлы в Perl.

В этот раз мы обратимся к текстовым файлам.

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

Исключение

Случай 1: Выдать исключение, если не удалось открыть файл:

use strict;
use warnings;

my $filename = 'data.txt';
open(my $fh, '<:encoding(UTF-8)', $filename)
  or die "Could not open file '$filename' $!";

while (my $row = <$fh>) {
  chomp $row;
  print "$row\n";
}

Предупредить или ничего не говорить

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

use strict;
use warnings;

my $filename = 'data.txt';
if (open(my $fh, '<:encoding(UTF-8)', $filename)) {
  while (my $row = <$fh>) {
    chomp $row;
    print "$row\n";
  }
} else {
  warn "Could not open file '$filename' $!";
}

Объяснение

Давайте разъясним эти два случая:

Для начала откройте текстовый редактор и создайте файл «data.txt» с несколькими строками:

First row
Second row
Third row

Открытие файла для чтения довольно похоже на то, как мы открывали его для записи, но вместо знака «больше» (>) мы используем знак «меньше» (<).

В этот раз мы также устанавливаем кодировку UTF-8. В большинстве кода, который вам доведется видеть, вы встретите только знак «меньше».

use strict;
use warnings;

my $filename = 'data.txt';
open(my $fh, '<:encoding(UTF-8)', $filename)
  or die "Could not open file '$filename' $!";

my $row = <$fh>;
print "$row\n";
print "done\n";

Получив указатель файла, мы можем читать из него с помощью того же оператора чтения строки, что и для чтения с клавиатуры (STDIN). Так мы получим первую строку файла. Затем мы выводим содержимое этой строки и пишем «done» в знак того, что этот пример закончен.

Запустив этот скрипт, мы увидим

First row

done

Вы можете спросить, откуда взялась пустая строка перед «done».

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

Как и в случае с чтением из STDIN, нам обычно не нужен этот перевод строки в конце, так что мы используем chomp(), чтобы избавиться от него.

Чтение более чем одной строки

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

use strict;
use warnings;

my $filename = $0;
open(my $fh, '<:encoding(UTF-8)', $filename)
  or die "Could not open file '$filename' $!";

while (my $row = <$fh>) {
  chomp $row;
  print "$row\n";
}
print "done\n";

Каждый раз при проверке условия цикла while будет выполняться строка my $row = <$fh>, которая считывает следующую строку из файла. Если в этой строке что-то есть, это условие будет считаться истинным. Даже пустые строки содержат перевод строки в конце, так что когда мы их считываем, переменная $row будет содержать \n, что в булевом контексте будет истиной.

После прочтения последней строки, в следующей итерации оператор чтения (<$fh>) вернет undef, что считается ложным значением. Цикл while прервется.

Граничный случай

Существует, однако, граничный случай, когда самая последняя строка содержит лишь один 0, без перевода строки. Код, представленный выше, может расценить такую строку как ложное значение, и цикл не выполнится. К счастью, Perl немного жульничает. Именно в этом случае (чтение из файла в цикле while) perl по сути действует так, как если бы вы написали while (defined my $row = <$fh>) {, и, таким образом, даже такие строки будут обрабатываться правильно.

open без die

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

Но что если это необязательный файл конфигурации? Если мы можем его прочитать, мы изменим какие-то настройки, а если нет, то просто используем значения по умолчанию.

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

if (open(my $fh, '<:encoding(UTF-8)', $filename)) {
  while (my $row = <$fh>) {
    chomp $row;
    print "$row\n";
  }
} else {
  warn "Could not open file '$filename' $!";
}

В этом случае мы проверяем значение, возвращаемое вызовом open. Если оно истинно, переходим к чтению файла.

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

if (open(my $fh, '<:encoding(UTF-8)', $filename)) {
  while (my $row = <$fh>) {
    chomp $row;
    print "$row\n";
  }
}