Некоторые сведения о Perl 5/Ввод и вывод

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

Язык Perl предоставляет весь необходимый функционал для чтения потоков данных и записи их в файлы. В этой главе мы рассмотрим как реализуется ввод/вывод в Perl.

Операции ввода

править

Данные в Perl программу можно вводить следующими стандартными инструментами:

  • через стандартный поток ввода STDIN с помощью операции <>;
  • через файлы системы, открывая их и получая дескрипторы;
  • через операцию qx{} или наклонные кавычки ``.

В этом разделе мы рассмотрим различные подходы к вводу, кроме файлов (которые будут рассмотрены позже). Начнем с операции qx{}.

qx{} (или более короткая ее форма ``) позволяет исполнить строковые литералы как команды для командной оболочки операционной системы. После того как команды выполнятся, весь вывод можно присвоить переменной, причем команда различает скалярный и списковый контексты. В скалярном контексте весь вывод сохраняется одной строкой. В списковом контексте каждая строка вывода будет записываться отдельно.

# Скалярный контекст
$date = `date`;

$out = qx {
    ps
};

# Списковый контекст
@out = qx {
    ps
};

print $date;

print $out;

foreach (@out) {
    print "line: " . $_; 
}
 9 янв 2023 г. 11:12:33
      PID    PPID    PGID     WINPID   TTY         UID    STIME COMMAND      
     1495       1    1495      17984  cons0    1052188   Jan  5 /usr/bin/bash
     2475    1495    2475      16208  cons0    1052188 10:58:03 /usr/bin/perl
     2476    2475    2475      11076  cons0    1052188 10:58:03 /usr/bin/ps  
line:       PID    PPID    PGID     WINPID   TTY         UID    STIME COMMAND      
line:      2477    2475    2475      19128  cons0    1052188 10:58:03 /usr/bin/ps  
line:      1495       1    1495      17984  cons0    1052188   Jan  5 /usr/bin/bash
line:      2475    1495    2475      16208  cons0    1052188 10:58:03 /usr/bin/perl

В списковом контексте строки будут разбиваться по символу разделителя, записанному во встроенной переменной $/, в которой по умолчанию записан символ новой строки \n. Управляя этим значением, можно управлять формированием вывода. Разделителем не обязательно может быть один символ: им может быть целая строка.

Выражение qx{} вычисляется каждый раз когда встречается интерпретатором perl. Код возврата исполняемой команды будет записан в переменной $? (подобно командной оболочке Bourne Shell).

Второй операцией ввода является <> (угловые скобки). Обычно она используется, чтобы читать по дескриптору (когда он указан между скобками), но когда его нет, весь ввод осуществляется через дескриптор STDIN. Дескриптор STDIN может быть связан с различными устройствами ввода (все зависит от того, как была вызвана Perl программа), но обычно он связывается с клавиатурой, поэтому попытка чтения по этому дескриптору обычно всегда приостанавливает исполнение в ожидании, когда пользователь что-нибудь введет и нажмет ↵ Enter.

Чтобы прочитать что-нибудь из дескриптора, достаточно присвоить результат операции переменной. Операция <> понимает скалярный и списковый контексты.

# Файл: main.pl

# Простейшее чтение из STDIN. В данном примере программа
# работает как echo, печатая на устройстве вывода все, что
# ввел пользователь.
while ($line = <>) {
    print $line;
}

Операция <> читает до тех пор, пока не получит символ перевода строки или символ конца потока EOF. В программе выше мы зациклили ввод за счет того, что операция ввода будет возвращать ИСТИНУ до тех пор, пока не получит EOF. Чтобы послать этот символ с клавиатуры, нужно нажать сочетание Ctrl + d.

Если вызвать эту же программу следующим образом

$ perl main.pl <<END
hello
world
END
# то получим
hello
world

В данном случае командная оболочка связывает STDIN не с клавиатурой, а с некоторым файлом (предоставляемым оболочкой), в который она записывает две строки. Эти строки мы и прочитали в программе.

Операция <> по умолчанию пишет в переменную $_. Пользуясь этим, программу можно написать еще проще.

while(<>) {  # Пишет в $_
    print;   # Печатает $_
}

Массив аргументов @ARGV

править

Любой программе Perl командная оболочка может передать аргументы. Все аргументы командной оболочки записываются в предопределенный массив @ARGV так, как они были записаны в оболочке.

Обычно вам не следует парсить входящие аргументы самостоятельно: лучше всего использовать встроенный модуль Getopt::Long (что такое модули мы подробно разберем в отдельной главе). Данный модуль позволит вам легко парсить длинные и короткие опции командной оболочки.

Для примера мы просто покажем, как выглядит массив @ARGV.

#!/usr/bin/perl
# Файл: main.pl
foreach (@ARGV) {
    print $_ . "\n";
}

Вызовем программу например так:

$ ./main.pl --help -s 34 param1 param2
--help
-s    
34    
param1
param2

Переменные окружения

править

Perl, как и любой другой процесс, может принимать из среды исполнения переменные окружения, которые автоматически складываются в хеш %ENV. Этот хеш можно считать ещё одним каналом для приема данных в программу Perl.

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

$ MYVAR=test perl -e 'print "$_ $ENV{$_}\n" for (keys %ENV);' | grep "MYVAR"

Лексемы __END__ и __DATA__

править

В язык Perl включены две специальные лексемы, которые позволяют хранить данные прямо в исходном файле программы.

Лексема __END__ определяет конец исходного кода программы и на этом слове интерпретация принудительно завершается. Разработчики Perl обычно размещают после этой лексемы документацию к исходному коду, обычно на языке разметки POD (Plain Old Documentation), которая может быть прочитана стандартными средствами чтения документации Perl.

#!/usr/bin/perl

print "Hello, World!";

__END__

Эти строки не будут интерпретированы perl.

Вторая лексема __DATA__ похожа на __END__ с той разницей, что данные после нее могут быть прочитаны из программы. Использование этого самое разное. Самое простое это хранение какой-то стандартной конфигурации к модулю; текста для сообщений и другое.

Чтобы обратиться к этим данным, нужно использовать файловый дескриптор <имя-пакета>::DATA.

#!/usr/bin/perl

while (my $data = <DATA>) {
    print "$data";
}

__DATA__
line 1
line 2
line 3
line 4

Операции вывода

править

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

print [<дескриптор>] <список>
print <дескриптор>

printf [<дескриптор>] <формат>, <список>

# Начиная с версии 5.10
say [<дескриптор>] <список>
say <дескриптор>

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

Функции печатают переменные, передаваемые списками. Если они не указаны явно, то используется переменная $_.

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

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

Функция say появилась относительно недавно, поэтому, чтобы ее использовать, необходимо в начале включить возможность ее вызова:

use feature qw{ say }; # Включаем функцию say()
say "Hello, World!"

Начиная с версии интерпретатора 5.10 можно просто указать версию с помощью use(), и тогда возможность использовать функцию включается неявно:

use v5.10; # С 5.10 и выше функция включается неявно
say "Hello, World!"

Все приведенные функции принимают на печать списки (т.е. являются списковыми операциями). Это может приводить к ошибочным ситуациям.

# НЕВЕРНО
print (2 + 2) ** 2;
# Напечатает 4, а не 16

В вышеприведенном примере на печать будет выведено 4, когда ожидалось 16. Это происходит потому, что выражение в скобках является термом. Далее функция print будет рассматривать выражение в скобках, как список своих параметров, а так как конструкция print (2 + 2) является термом, то сначала напечатается сумма; затем результат печати вернет 1, которая будет в конце возведена в квадрат.

Чтобы исправить эту ошибку, в подобных ситуациях нужно всегда указывать дескриптор явно:

# ПРАВИЛЬНО
print STDOUT (2 + 2) ** 2;
# Напечатает 16

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

В функциях печати вы можете быстро печатать массивы. Простой массив можно распечатать как минимум тремя способами:

@arr = (1,2,3);

print "@arr\n";  # Всегда используйте кавычки, чтобы элементы разделялись пробелами

# Перебором в цикле
foreach (@arr) {
  print "$_\n";
}

# Функцией join. Преимущество здесь в том, что можно менять разделитель.
print join(" ",@arr),"\n";

С хеш-массивами немного сложнее, так как стандартные функции печати не умеют их печатать:

%person = (name => "Larry", surname => "Wall");

while ( ($k,$v) = each %person ) {  # Функция each итеративно парами извлекает элементы
    print "$k => $v\n";
}

print map { "$_ => $person{$_}\n" } keys %person;  # Преобразуем хеш-массив в список и печатаем его

print "@{[ %person ]}\n";   # Строим новый массив из элементов хеш-массива

{
    my @temp = %person;
    print "@temp\n";
}

Ввод и вывод в файлы

править

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

Во всех этих случаях мы должны открыть файл в некотором режиме и связать его с дескриптором. Дескриптор — это специальный программный объект, который предоставляет интерфейс ввода/вывода системы, через который мы пишем/читаем файл. Одна программа может открыть за раз много дескрипторов (все зависит от ограничений, накладываемых системой на процесс), причем один дескриптор в один момент может быть связан только с одним файлом. Тем не менее, допускается один и тот же дескриптор перепривязывать на другие файлы.

В этой главе мы рассмотрим основные вопросы записи и чтения в файлы. Эти вопросы являются базовыми для любых типов файлов.

Файловые дескрипторы

править

С точки зрения Perl, дескриптор — это символическое имя в программе, представляющее простой файл, файл устройства, сокета или программного канала. В программе Perl дескриптор файла чаще всего открывается функцией open(), которой передается имя дескриптора, режим доступа и путь к файлу в файловой системе. Например

open(FILE, "> /tmp/file.data");

создает дескриптор FILE и присоединяет его к файлу /tmp/file.data. В данном случае файл открывается только на запись, о чем говорит символ >. Символ режима открытия файла не обязательно записывать во втором аргументе, как показано в примере[1]: вообще говоря вы передаете функции список, а дальше он будет проверен на наличие нужных частей:

# Символ режима можно вынести в отдельный аргумент.
open(FILE, '<', "test.txt");
# или так
open FILE, '<', "test.txt"; # Потому что это именованная списковая операция.

Дескриптор должен представлять правильный идентификатор и не должен совпадать с зарезервированными словами Perl. Имя файла "-" является специальным. При открытии дескриптора в режиме < с передачей этого пути служит указанием открыть стандартное устройство ввода, а в режиме > — стандартное устройство вывода.

Созданный дескриптор попадает в таблицу символов пространства имен.

Строго говоря, дескриптор не является переменной, поэтому ему не нужен идентификатор типа. По этой причине дескриптор нельзя напрямую использовать в операциях (которые требуют знания контекста). При передаче дескриптора куда-либо используется символ звездочка (например, *FILE), которая дает ссылку на дескриптор. Получив ссылку, ее можно присвоить переменной, которую можно использовать в операциях:

$ref_file = *FILE;
print $ref_file "Hello\n"; # Печатаем в файл

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

Любая программа Perl неявно открывает три дескриптора:

  • STDIN — связывается со стандартным устройством ввода;
  • STDOUT — связывается со стандартным устройством вывода;
  • STDERR — связывается со стандартным устройством вывода (как правило вывод через него не буферизуется).

По умолчанию STDIN пользуется операция <>, простые функции вывода используют STDOUT, а STDERR, например, пользуется функция warn().

Стандартные дескрипторы допускается переоткрывать, но сделать это можно только один раз. При этом восстановить предыдущее состояние через open() будет невозможно. Чтобы восстанавливать дескрипторы, их можно дублировать, о чем будет рассказано ниже.

Открытие/закрытие дескриптора

править

Дескриптор файла можно открыть одним из следующих вызовов:

open <идентификатор-дескриптора>, <имя-файла>;
open <идентификатор-дескриптора>;

Идентификатор дескриптора может вычисляться в выражении. По умолчанию файл открывается на чтение, если не указан конкретный режим. Если имя файла задано без префикса пути, то Perl будет пытаться искать файл в рабочем каталоге. Имя может зависеть от операционной системы: так, в Windows нужно указывать метку логического диска для абсолютных путей, однако слеши не обязательно менять на обратные.

Вместо пути можно указать знак тире -, тогда открываемый файл соответствует STDIN, т.е. >- соответствует записи в STDIN. Если стандартный дескриптор до это уже был перенаправлен в некоторый файл, то такой вызов будет ассоциироваться с ним.

Если не указан путь к файлу, то предполагается путь в переменной $<идентификатор-дескриптора>, причем ее имя становится идентификатором дескриптора:

$FILE = "file.dat";
open FILE;  # Предполагается, что путь есть в переменной $FILE.

Любой файл может быть открыт в одном из режимов:

  • < (чтение);
  • > (запись);
  • >> (дозапись).

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

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

Чтобы открыть файл одновременно на чтение и на запись, перед знаком режима нужно поставить плюс +, т.е. +>, +>>, +<.

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

sysopen <идентификатор-дескриптора>, <имя-файла>, <флаги> [, <umask>];

Эта функция похожа на open(), но она позволяет гибко выставить режимы с помощью флагов, определенных в модуле Fcntl. Набор флагов может зависеть от операционной системы, но нижеперечисленные поддерживаются во всех:

  • O_RDONLY — только чтение;
  • O_WRONLY — только запись;
  • O_RDWR — чтение и запись;
  • O_CREAT — создать файл, если не существует;
  • O_EXCL — завершить вызов с ошибкой, если файл существует;
  • O_APPEND — режим дописывания;
  • O_TRUNC — очистить содержимое файла перед записью.

Читателю, программировавшему для *nix, данные флаги могут быть знакомы. Дело в том, что Perl не придумывает велосипед и практически в неизменном виде копирует стандартную библиотеку ввода/вывода Си и *nix. Эти флаги представлены двоичными числами, которые можно объединять поразрядным И (|).

Последний не обязательный аргумент передает umask-маску, задающую флаги доступа для новых файлов. Данная маска задается восьмеричным числом. Если она не указана, то используется число 0666.

Функции open() и sysopen() возвращают 0, если удалось открыть дескриптор, и неопределенное значение undef, в противном случае.

Чтобы закрыть дескриптор, следует вызвать функцию

close [<дескриптор>];

Функция возвращает ИСТИНУ, если дескриптор удалось закрыть и выполнить всю системную работу, связанную с ним. Если опустить аргумент, то функция закроет дескриптор, на который последний раз указывала функция select().

Следует отметить, что закрывать дескрипторы явно иногда не обязательно. Если вы пытаетесь открыть новый файл по дескриптору, который уже используется, то перед открытием нового файла close() для старого будет вызвана неявно. Также при завершении Perl-программы все открытые дескрипторы закрываются автоматически. С другой стороны, явное закрытие дескрипторов позволяют выявлять очень редкие системные ошибки, например, когда буферы не сбрасываются на диск, потому что на нем закончилось место. Явное закрытие очищает переменную $., которая хранит последнюю прочитанную запись, тогда как неявное закрытие ее не очищает.

Если функция close() завершается с ошибкой, то ее причина записывается в переменную $!.

Примеры

use Fcntl; # для sysopen

# Примеры с функцией open

open FILE, "< example.txt" or warn "Warning: $!";   # на чтение. Файл должен существовать.
open FILE, "> example.txt" or warn "Warning: $!";   # на запись. Файл будет создан, если его нет.
open FILE, ">> example.txt" or warn "Warning: $!";  # на дозапись. Файл будет создан, если его нет.

open FILE, "+< example.txt" or warn "Warning: $!";  # на чтение и запись. Файл должен существовать.
open FILE, "+> example.txt" or warn "Warning: $!";  # на чтение и запись. Файл очищается.
open FILE, "+>> example.txt" or warn "Warning: $!"; # на чтение и на дозапись.

# Примеры с функцией sysopen

sysopen FILE1, "example1.txt", O_RDONLY, 0666 or warn "Warning: $!";   # на чтение. Файл должен существовать.
sysopen FILE1, "example1.txt", O_WRONLY | O_CREAT | O_TRUNC or warn "Warning: $!"; # на запись
sysopen FILE1, "example1.txt", O_WRONLY | O_CREAT | O_APPEND or warn "Warning: $!"; # на дозапись
sysopen FILE1, "example1.txt", O_RDWR or warn "Warning: $!"; # на чтение и запись. Файл должен существовать
sysopen FILE1, "example1.txt", O_RDWR | O_CREAT | O_TRUNC or warn "Warning: $!"; # на чтение и запись. Файл очищается.

# Примеры с close

close FILE  or warn "Warning: $!";
close FILE1 or warn "Warning: $!";

Дескриптор-дубликат

править

Файловый дескриптор можно продублировать, т.е. создать независимую копию. Оба дескриптора будут связаны с одним и тем же файлом, иметь общий файловый указатель, но разные буферы ввода/вывода. Закрытие одного дескриптора не будет влиять на работу другого.

Чтобы продублировать дескриптор, в аргументе с именем файла нужно указать его идентификатор с амперсандом &:

# Без пробелов !
open(NEWSTDOUT, ">&STDOUT"); # NEWSTDOUT - дубликат STDOUT

Дублирование дескрипторов зачастую используется, чтобы сохранить предыдущее состояние. Например так следует поступать со стандартными дескрипторами STDIN, STDOUT и STDERR.

open OLDSTDOUT, ">&STDOUT";
open STDOUT, "> example.txt" or die "$!";

# Временно весь вывод будет направляться в файл
print "Hello, World\n";

# Восстанавливаем дескриптор STDOUT
close STDOUT or warn "$!";
open STDOUT, ">&OLDSTDOUT" or die "$!";
close OLDSTDOUT or warn "$!";

В остальных ситуациях дублирование в общем то бесполезно.

Чтение/запись файлов

править

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

  • буферизированное чтение/запись. Здесь операции осуществляются высокоуровневыми функциями, использующими буферы ввода/вывода. Использование буферов позволяет уменьшить нагрузку на систему, так как данные не сбрасываются на устройства хранения, пока буфер не заполнится. Кроме того, функции чтения/записи контролируют некоторые нюансы, например что данные действительно записались или прочитались, благодаря чему код становится компактнее;
  • не буферизированное чтение/запись позволяет полностью контролировать всю процедуру чтения/записи. Обычно такой подход используется для записи/чтения двоичных файлов.

Тонкости буферизации

править

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

В этих случаях, чтобы устройство работало эффективнее, разумно нагрузить устройство сразу большим количеством данных, вместо того, чтобы передавать их малыми порциями. Для этого операционная система вводит буфер между приложением и устройством ввода/вывода, который читает асинхронно. Удобно представлять буфер как непрерывную память с двумя указателями, один из которых указывает позицию, до которой осуществилась запись, а другой — позицию, до которого произошло чтение. Когда приложение делает операцию записи в буфер, оно размещает данные в его свободном пространстве, а затем смещает указатель записи буфера на следующий свободный адрес. Когда ядро операционной системы получает процессорное время, оно увидит что в буфере есть разница между адресами указателей записи и чтения, возьмет данные между адресами указателей, сделает системный вызов для записи их на устройство, а когда оно закончит, сместит указатель чтения на тот же адрес, на котором был указатель записи в момент операции, и так до бесконечности. Этот алгоритм описывает исходящий буфер с точки зрения приложения, когда указатель чтения находится в позиции догоняющего, однако, входящий буфер работает похоже, просто стороны меняются местами.

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

Интерпретатор perl при реализации буферизованных операций обычно использует ещё один логический буфер, называемый STDIO, который находится выше буфера операционной системы. Таким образом, прежде чем данные запишутся в буфер, выделенный ядром, они сначала запишутся в логический буфер, а когда он заполнится, то только тогда произойдет сброс данных в буфер операционной системы. Perl разрешает вам управлять буфером STDIO с помощью специального скаляра $| (по умолчанию имеет значение ЛОЖЬ), который управляет автоматическим сбросом. Если он установлен в ИСТИНУ, то будет работать режим автоматического сброса для каждой записи для дескриптора, на который указывает последний вызов функции select(). Иногда это может быть необходимо, например когда данные записываются синхронно порциями, много меньшими размера буфера, и без автоматического сброса приложение может просто зависнуть. В этих случаях буферизацию нужно отключать через установку значения скаляра $|. Обычно это делают так

my $prevHandler = select(FILE_HANDLE);  # Вернет дескриптор, который был до установки текущего.
$| = 1;
select($prevHandler);                   # Восстанавливаем предыдущий дескриптор.

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

Использование sysread() и syswrite() дают вам полную свободу и не используют логический буфер STDIO. Далее по тексту мы говорим буферизованное чтение/запись, когда используется логический буфер STDIO.

Буферизированное чтение/запись

править

Операция <> и следующие функции используют буферы ввода/вывода: print, read, seek.

Простейшее чтение файлов реализуется через операцию <>, в которой между скобками нужно указать дескриптор файла. В скалярном контексте операция возвращает очередную прочитанную строку до разделителя, указанного в переменной $/, со смещением внутреннего файлового указателя на следующий символ после разделителя. Операция возвращает прочитанную строку вместе с разделителем, либо возвращает пустую строку. Пользуясь этим, можно реализовать построчное чтение файла от начала до конца.

Пусть у нас есть файл example.txt со следующим содержимым:

line 1
line 2
line 3
line 4
line 5

За пятой строкой нет перевода строки, а идет сразу конец файла.

open FILE, "example.txt" or die "$!";

$line = <FILE>; # Прочитана первая строка (вместе с символом перевода строки)
print "$line";  # line 1\n

$line = <FILE>; # Прочитана вторая строка
print "$line"; # line 2\n

# Внутренний файловый указатель смещен на первый символ третьей строки

# Дочитываем файл
while (<FILE>) {
    print;
}
# line 3\n
# line 4\n
# line 5\0

# Вернет пустую строку, так как читать больше нечего: внутренний файловый указатель находиться в конце файла.
$line = <FILE>;
print "Empty: $line";

Теперь пусть файл имеет такое содержимое:

a:b:c:d:e:f

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

open FILE, "example.txt" or die "$!";

{  # Используем блок, чтобы поменять $/ в локальной области видимости.
    local $/ = ':';  # Меняем разделитель.
    @lines = <FILE>; # Читаем файл прямо в массив.
}

foreach (@lines) {
    print "$_\n";
}

Результат

a:
b:
c:
d:
e:
f

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

open FILE, "example.txt" or die "$!";
@chunks = split(/:/, <FILE>);
foreach (@chunks) {
    print "$_\n";
}
# a
# b
# c
# d
# e
# f

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

...
foreach (@{[<FILE>]}) {
    print "$_\n";
}

Эта конструкция может показаться на данный момент не понятной, так как конструирование анонимных массивов мы будем обсуждать в отдельной главе. Здесь в анонимный массив, конструируемый операцией [], помещается содержимое файла, читаемое операцией <>. Чтобы операции производились в списковом контексте, мы помещаем конструкцию в еще одни скобки @{}: разыменовывание анонимного массива, в который мы ранее записали содержимое файла.

Если файл имеет очень большой размер, его следует читать порциями. Для этого прибегают к функции

read <дескриптор>, <переменная>, <длина> [, <смещение>];

Функция read(), читает из файла ровно столько байт, сколько указано в параметре <длина>, и помещает прочитанное в <переменная>. В отличие от <>, эта функция возвращает действительное значение прочитанных байт; 0 — если чтение достигло конца файла, и неопределенное значение в случае ошибки. Параметр <смещение> указывает, сколько байт нужно отсчитать в <переменная> и начать вставлять прочитанные данные. Так, отрицательное смещение -n говорит, что в <переменная> нужно отсчитать последние n байтов и к оставшемуся результату добавить прочитанную строку. Положительное смещение, большее чем длина текущей строки в <переменная>, будет создавать пробел из символа \0, после которого будет вставляться прочитанная строка. По умолчанию смещение равно нулю, что означает затирание предыдущих данных после чтения.

На практике файлы с помощью read() читаются примерно так:

open FILE, "example.txt" or die "$!";

$buffer_size = 1024;

while (read (FILE, $data, $buffer_size) > 0) {
    print "$data";
}

Иногда приходится читать файл по одному символу. Для этого можно воспользоваться функцией getc(), которая возвращает один символ в текущей позиции внутреннего указателя файла и неопределенное значение в случае конца файла или ошибки.

open FILE, "example.txt" or die "$!";

while ($data = getc(FILE)) {
    print "$data"; # Читаем файл символ за символом
}

Мы уже не один раз упомянули о внутреннем указателе файла, который является подобием виртуальной каретки (как в печатной машинке). Любая операция чтения/записи над файлом приводит к смещению этого указателя на число прочитанных/записанных байт. Этим указателем можно управлять с помощью функции seek() из модуля IO::Seekable:

seek <дескриптор>, <смещение-указателя>, <точка-отсчета>;

# Под точкой отсчета понимается одна из предопределенных констант:
# SEEK_SET (0) — от начала файла.
# SEEK_CUR (1) — с текущей позиции.
# SEEK_END (2) — с конца файла.

В следующем примере мы читаем посимвольно один файл с единственной строкой "abcdef" справа налево и записываем прочитанные символы в другой файл.

use IO::Seekable; # Для seek

# Генерируем входящий файл таким нехитрым способом.
qx {
    echo -n "abcdef" > "in.txt"
};

open IN, "in.txt" and open OUT, ">out.txt" or die "$!"; # Открываем файлы

seek IN, -1, SEEK_END;  # Смещаем указатель файла на позицию перед последним символом.
while ($character = getc(IN) and tell(IN) > 1) {  # Читаем файл справа налево, пока не упремся в начало файла.
    print OUT $character;    # Печатаем в другой файл прочитанный символ.
    seek IN, -2, SEEK_CUR;   # Смещаем указатель на две позиции налево относительно текущей позиции.
}
print OUT $character;        # Печатаем первый символ, так как условие выхода в цикле составлено так.

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

При записи по дескриптору, вы обычно указываете его в вызове, однако, когда операций вывода очень много для одного и того же дескриптора, становится утомительным постоянно его писать. С помощью функции select(<дескриптор>), можно указать дескриптор по умолчанию, тогда любые вызовы print и write без явного дескриптора будут направлены на дескриптор по умолчанию. Вызов функции возвращает предыдущее значение, поэтому, если нужно, можно скопировать это значение, чтобы позже восстановить старый дескриптор по умолчанию.

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

Обнаружение конца чтения

править

Обычно при чтении файла программа ожидает наступления условия EOF (англ. End-Of-File), чтобы закрыть дескриптор и освободить ресурсы системы. Это условие может наступать по-разному. Например, в интерактивном режиме работы терминала вы можете закрыть STDIN, послав сигнал на терминал сочетанием Ctrl+d.

При выполнении операции чтения с помощью read() и sysread(), они возвращают undef при наступлении события EOF. Таким образом, вы можете безопасно читать дескриптор, например, таким циклом

while (1) {
     my $bytes = read(STDIN, $buffer, 100);
     die "Error" unless defined($bytes);
     last unless $bytes > 0;
}

С другой стороны, операция <> возвращает undef и при наступлении события EOF и в случае ошибки, поэтому для перехвата ошибки следует всегда проверять $!:

...
undef $!;
while (my $line = <STDIN>) {
    $data .= $line;
}
die "Abnormal reading" if defined ($!);

Для проверки того, что в дескрипторе наступило условие EOF, можно использовать функцию eof(FH), которая возвращает ИСТИНУ, если условие наступило. Без аргументов проверяет дескриптор, из которого последний раз выполнялось чтение. Обычно не рекомендуется использовать eof() в больших программах, так как она имеет очень много скрытых правил. Если эта функция используется, обычно это говорит о проблемах в структуре программы.

Тонкости контроля ошибок чтения/записи

править

Для контроля ошибок в Perl используется специальная переменная $!, которая имеет ненулевое значение в случае ошибки. Эта переменная работает по-разному в разном контексте:

  • Если переменная используется в операции, которая ожидает число, то она хранит числовую константу (например, EACCES), определенную в заголовочных файлах операционной системы.
  • Если переменная используется в операции, которая ожидает строку, то она хранит сообщение об ошибке, например "Permission denied".

Чтобы программы были портируемыми, обычно используется стандартный модуль Errno, чтобы импортировать отдельные константы ошибок и использовать имена, а не числовые значения.

use Errno qw { EACCES ENOENT };
my $result = open (FH, ">/etc/passwd");
if (!$result) {
   if ($! == EACCES) {
      warn "You do not have permission to open this file.";
   } elsif ($! == ENOENT) {
      warn "File not found.";
   } else {
      warn "Other error.";
   }
}

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

Оформление конца строки в разных системах

править

При выполнении построчного чтения следует помнить, что ни в одной системе нет единого подхода к оформлению конца строки. Например, в Unix используется символ LF, который соответствует восьмеричному \012 в ASCII кодировке, а в Windows используются два символа CRLF или \015\012.

Чтобы программа оставалась переносимой между системами, следует использовать при необходимости скаляр $/, который хранит признак конца строки.

Не буферизированное чтение/запись

править

Следующие функции реализуют не буферизированное чтение/запись:

# Чтение
sysread <дескриптор>, <переменная>, <длина> [, <смещение>];

# Запись
syswrite <дескриптор>, <переменная>, <длина> [, <смещение>];

# Управление файловым указателем
sysseek <дескриптор>, <смещение>, <точка-отсчета>;
  • Значения всех аргументов аналогичны их буферизованным аналогам. Основной разницей является то, что для реализации tell(), нужно использовать sysseek():
    $position = sysseek <дескриптор>, 0, 1; # Вернет текущую позицию
    
  • Операции чтения/записи при не буферизованном чтении/записи не всегда могут завершаться успешно. Вы всегда должны сверяться с тем, что возвращает функция чтения/записи (напомним, что они возвращают число прочитанных/записанных байтов).
  • Нельзя смешивать буферизованное и не буферизованное чтение/запись для одного и того же файла. Подобная практика может приводить к непредсказуемым коллизиям.

Пример

$total_read = 0;
open FILE, "< example.txt";

$bufsize = 1024;

while (my $read = sysread(FILE, $buffer, $bufsize)) {
    print "$read\n";
    $total_read += $read;
}

print "Total read: $total_read\n";

Текстовый и двоичный подходы чтения

править

Обычно, когда операция чтения ориентируется не на размер передаваемых данных, а на байты, которые передаются, такой подход чтения называют текстовым. Напротив, когда операции известно сколько максимально байт она может прочитать/записать за один раз, говорят о двоичном подходе чтения.

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

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

Стандартное буферизированное чтение в Perl может работать в любом из этих режимов, но по умолчанию используется текстовый. Чтобы открытый дескриптор читался/писался в двоичном режиме, необходимо использовать на нём функцию binmode(FILEHANDLE, [, $discp]), которая делает переключения в буферизированном чтении, заставляя читать/писать дескриптор как двоичный файл. Начиная с Perl 5.6, вторым аргументом вы можете передавать дисциплину, например, значение :raw заставляет читать/писать двоично, отключая разделитель строк и интерпретацию управляющих символов, а значение :crlf снова включает текстовый режим. Функция binmode() выполняет реальную работу только в системах, в которых разделитель состоит более чем из одного символа, как в Windows.

Функция open() позволяет указать на необходимость двоичного ввода/вывода (и даже кодировку) в момент открытия дескриптора. Для этого нужно указать флаг рядом с режимом открытия:

open (FH, '<:raw', $filename);                # открыть в двоичном режиме чтения
open (FH, '>:raw', $filename);                # открыть в двоичном режиме записи
open (FH, '<:encoding(UTF-8)', $filename);    # интерпретировать входящую кодировку как UTF-8

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

Операции с файлами

править

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

chmod <флаги-доступа>, <список-файлов>;
Аналогична *nix утилите chmod: выставляет флаги доступа для владельца файла, группы пользователей и всех остальных. Флаги доступа обычно задаются восьмеричным числом (именно числом, а не строкой) в первом аргументе, после чего нужно передать список файлов. Возвращает число, которое означает число файлов, для которых процедура прошла успешно.
chown <идентификатор-пользователя>, <идентификатор-группы>, <список-файлов>;
Аналогична *nix утилите chown: меняет владельца и группу файла. В отличие от системной утилиты, идентификаторы нужно указывать числом. Значение -1 интерпретируется многими системами, как ничего не менять. Возвращает число, которое означает число файлов, для которых процедура прошла успешно.
link <исходный-файл>, <новый-файл>;
symlink <исходный-файл>, <новый-файл>;
Создают соответственно жесткую и символическую файловую ссылку. В случае жесткой ссылки возвращается ИСТИНА или ЛОЖЬ, в зависимости от успеха операции. В случае символической ссылки, возвращается 1 в случае успеха и 0 — в случае провала. Удалить созданные ссылки на файлы можно функцией unlink(), которой нужно передавать список из файлов. Данная функция удаляет одну ссылку на каждый файл, переданный в списке. Если ссылки на файл не существует, то удаляется сам файл. Функция unlink() возвращает число, которое выражает для скольки файлов из переданного списка операция удалась успешно.
rename <старое-имя>, <новое-имя>;
Переименовывает файл, заданный первым аргументом, на имя, во-втором аргументе. В случае успеха функция возвращает 1 и 0 — в случае провала.
truncate <дескриптор>, <длина>;
truncate <имя-файла>, <длина>;
Усекает файл до заданной длины. Значение длины может быть как меньше текущего размера файла, так и больше. В случае успешного усечения возвращается ИСТИНА, иначе — неопределенное значение undef.
stat <дескриптор>;
Возвращает структуру индексного дескриптора (inode) для файла.
my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
    $atime,$mtime,$ctime,$blksize,$blocks) = stat($filename);

# Здесь:
# dev — номер устройства в файловой системе.
# ino — номер индексного дескриптора.
# mode — тип и права доступа для файла.
# nlink — число жестких ссылок. Если нет, то равно 1.
# uid — числовой идентификатор владельца файла.
# gid — числовой идентификатор группы.
# rdev — идентификатор устройства (только для специальных файлов).
# size — размер файла в байтах.
# atime — время последнего обращения к файлу с начала эпохи.
# mtime — время последнего изменения файла с начала эпохи.
# ctime — время изменения индексного дескриптора с начала эпохи.
# blksize — предпочтительный размер блока для операций ввода/вывода.
# blocks — фактическое количество выделенных блоков для размещения файла.
Отметим, что не все поля структуры поддерживаются в разных операционных системах. В списковом контексте функция возвращает не пустой список в случае успеха. Если вместо дескриптора передать нижнее подчеркивание stat(_), то функция возвращает закэшированное значение последнего удачного вызова. Данную функцию можно использовать для файлов, ссылок и каталогов. Для получения информации о символических ссылках используется функция lstat(), для которой все аналогично. Если система не поддерживает символические ссылки, то вызов lstat() аналогичен вызову stat().

Кроме этих функций, для файлов предусмотрены следующие унарные операции, которые используются, как правило, в условных конструкциях:

  • -r — файл может читаться эффективным uid/gid;
  • -w — в файл может записывать эффективный uid/gid;
  • -x — файл может быть исполнен эффективным uid/gid;
  • -o — эффективный uid/gid является владельцем файла;
  • -R — файл может читаться действительным uid/gid;
  • -W — в файл может писать действительный uid/gid;
  • -X — файл может быть исполнен действительным uid/gid;
  • -O — действительный uid/gid является владельцем файла;
  • -e — файл существует;
  • -z — размер файла равен нулю;
  • -s — размер файла не равен нулю (размер возвращается как результат);
  • -f — файл является обычным;
  • -d — файл является каталогом;
  • -l — файл является символической ссылкой;
  • -p — файл является именованным FIFO каналом или дескриптор связан с таким каналом;
  • -S — файл является сокетом;
  • -b — файл является блочным устройством;
  • -c — файл является символьным устройством;
  • -t — дескриптор связан с терминалом;
  • -u — у файла поднят флаг setuid;
  • -g — у файла поднят флаг setgid;
  • -k — у файла установлен sticky bit;
  • -T — файл является текстовым;
  • -B — файл является бинарным (двоичным);
  • -M — возраст файла в днях на момент выполнения программы;
  • -A — возраст файла в днях с последнего обращения к файлу;
  • -C — возраст файла в днях для времени последней модификации индексного дескриптора файла.

Унарные операции применяются к строке, содержащей имя файла; к выражению, которое вычисляет имя файла, или к файловому дескриптору. Если параметр не задан, то проверяется значение в переменной $_. В случае ИСТИНЫ возвращается 1. В случае ЛЖИ возвращается пустая строка, а в случае, если файла не существует, возвращается значение undef.

Операции -T и -B работают следующим образом. Если первый блок файла содержит более 30% необычных символов (управляющие последовательности или байты с установленными старшими битами), то файл признается двоичным, иначе он признается текстовым. Если эти операции применяются к дескриптору, то проверяется не файл, а буфер ввода/вывода. Обе операции возвращают ИСТИНА, если связанный с дескриптором файл пуст или внутренний указатель находится в конце файла.

При выполнении унарных текстовых операций всегда неявно вызывается функция stat(), причем ее результат всегда кэшируется.

Операции с каталогами

править

Каталоги в *nix являются специальными файлами, помеченными в rdev как каталоги. Каталоги хранят пары, состоящие из объекта каталога и значения его индексного дескриптора.

Для работы с каталогами в Perl предусмотрены следующие функции.

opendir  <дескриптор>, <имя каталога>;
closedir <дескриптор>;
readdir <дескриптор>;
Соответственно открывает/закрывает/читает каталог. Допускается, чтобы дескрипторы каталогов и файлов имели одинаковые имена в одно и то же время (внутри Perl разберется что к чему), однако, вряд ли вы захотите так сделать, так как это запутает того, кто будет читать программу. Функция readdir() для открытого каталога в списковом контексте возвращает список имен всех файлов или пустой список, если имена уже были прочитаны. В скалярном контексте функция возвращает имя очередного файла в каталоге или неопределенное значение undef, если все имена уже прочитаны. Функцией rewinddir(<дескриптор>) можно установить текущую позицию каталога в начало, чтобы повторно запросить имена файлов в нем. Функция readdir() возвращает всегда относительное имя файла: абсолютное имя вы должны формировать самостоятельно.
mkdir <имя-каталога>, <флаги-доступа>;
Создаёт каталог с указанным именем и флагами доступа (в восьмеричной форме). Если задаётся не полное имя каталога, то новый каталог будет создан в текущем рабочем каталоге. Если каталог был создан, возвращает ИСТИНУ, иначе — ЛОЖЬ. В случае ошибки в специальной переменной $! будет записана причина.
chdir [<дескриптор>];
Делает указанный каталог рабочим. Если каталог опущен, то рабочем становится каталог указанный в переменной окружения под именем HOME или LOGDIR, когда HOME не определена.
rmdir [<имя-каталога>];
Удаляет указанный каталог. Если переменная не указана, то берется значение из переменной $_. В случае успеха возвращает ИСТИНУ и ЛОЖЬ в противном случае, записывая причину ошибки в переменную $!. Эта функция удаляет только пустые каталоги. Чтобы удалить не пустой каталог, нужно его сначала очистить ото всех файлов.

Пример работы с каталогом.

opendir DIR, "/bin" or die "$!";
# Печатает все файлы не являющиеся каталогами.
while ($name = readdir DIR) {
    next if -d $name;
    print "$name\n";
}
closedir DIR;

Примечания

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