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

В Perl реализовано два набора функций для осуществления операций чтения/записи в файл.
Функции одного набора используют буфер — некоторую область памяти — для накопления читаемой/записываемой в файл информации, после заполнения которого или закрытия файла осуществляется физическая запись данных буфера на диск или их пересылка в программу. Эти функции, к которым относятся print, readline, <>, read, getc, seek и tell, по существу, представляют собой интерфейсные функции к процедурам буферизованной библиотеки ввода-вывода stdio языка С. Использование буферизованных операций ввода-вывода ускоряет чтение/запись данных в файлы.
Функции второго набора, к которым относятся sysread, syswrite и sysseek, обращаются непосредственно к функциям ввода-вывода операционной системы, осуществляя прямые физические операции чтения/записи данных без накопления их в промежуточном буфере.
Открывать файл для доступа как буферизованными, так и небуферизованными функциями можно любой из двух функций — open( ) или sysopen( ) — при открытии файла не регламентируется, каким набором функций следует обрабатывать содержащуюся в нем информацию. Единственное требование заключается в том, что не рекомендуется смешивать эти два подхода для одного файла в одном сеансе открытия, так как это может привести к непредсказуемым результатам.
ВНИМАНИЕ При работе с одним и тем же файлом не следует смешивать вызовы буферизованных и небуферизованных функций ввода-вывода. Подобная практика может приводить к непредсказуемым коллизиям. Если требуется, например, использовать небуферизованных функции чтения/записи, а информация из файла уже читалась буферизованной операцией о, то следует закрыть файл, снова его открыть и использовать для работы с ним небуферизованные функции.

Буферизованный ввод-вывод

Чаще всего в программе осуществляется обработка текстовых файлов. Операция <>, операндом которой является дескриптор файла, читает информацию из файла целыми «записями», которые обычно представляют строки текстового файла. Примененная в скалярном контексте, она читает текущую запись файла, увеличивая на единицу специальную переменную $., отслеживающую количество прочитанных записей. В списковом контексте эта же операция прочитает все записи файла, если она выполняется для этого файла первой, или оставшиеся записи, если до нее были выполнены другие операции чтения из файла, и возвращает список, элементами которого являются прочитанные записи файла. Более точно можно сказать, что операция <> в списковом контексте читает оставшиеся записи файла, начиная с текущей его позиции, которая неявно изменяется при каждой операции чтения, а может быть изменена и явным способом с помощью функции seek( ), c которой мы познакомимся чуть позже. Забегая вперед, скажем, что все файлы в Perl не имеют никакой структуры, а представляются, как и в С, потоком байтов.
Разделитель записей хранится в специальной переменной $/, и по умолчанию им является символ новой строки \n. Таким образом, если пользователь не устанавливал собственный разделитель записей, то под записью файла в операции <> подразумевается строка текстового файла. Задание другого разделителя записей осуществляется обычной операцией присваивания переменной $/ нового символа или последовательности символов разделителя записей. В листинге 6.9 демонстрируются некоторые приемы чтения из файла операцией <>.
Листинг 6.9. Чтение из файла операцией <>
#! perl  -w
open(F1, "in.dat") or die "Ошибка открытия файла: $!";
open(F2, "out.dat") or die "Ошибка открытия файла:  $!";
$line1 = <F1>; # Первая запись файла in.dat
$line2 = <F1>; # Вторая запись файла in.dat
@rest = <F1>;  # Оставшиеся записи файла in.flat
$/ = ">";      # Задание другого разделителя записей
@f2 = <F2>;
# Печать прочитанных записей файла out.dat
for($i=0; $i<=$#f2; $i++) {
  print "Запись ".($i+1).": $f2[$i]\n";
}
$/ = "\n";  # Восстановление разделителя по умолчании
close(Fl) or die $!;
close(F2) or die $!;
open(F3, "out.dat") or die "Ошибка открытия файла: $!";
print <F3>; # Печать всего файла 
close(F3) or die $!;
Несколько комментариев к программе из листинга 6.9. В переменные $line1 и $line2 читаются соответственно первая и вторая строки файла in.dat, так как используется умалчиваемый разделитель записей \n. Элементы массива @rest хранят строки с третьей по последнюю этого же файла, так как в операторе присваивания операция чтения <F1> выполняется в списковом контексте.
Перед чтением записей файла out.dat устанавливается новый разделитель записей — символ >. Если файл out.dat, например, содержит только одну строку
Иванов> Петров> Сидоров> Конец
то элементы массива @f2 будут содержать следующие значения:
$f2[0] = "Иванов>"
$f2[l] = "Петров>"
$f2[2] = "Сидоров>"
$f2[3] = "Конец"
ПРИМЕЧАНИЕ Если при создании файла out.dat его единственная строка завершена переходом на новую строку (нажата клавиша Enter), то $f2[3] будет содержать строку "Конец\n".
В последнем операторе печати программы из листинга 6.9 операция <F3> выполняется в списковом контексте, так как функция print( ) является списковой операцией и ей требуется список значений. Если же мы захотели бы при выводе на печать предварить каждую запись каким-либо текстом и предполагали бы для этого использовать следующий оператор:
print "Запись:".<F3>;
то получили бы распечатку только первой строки файла, так как в этом случае операция <F3> будет выполняться в скалярном контексте, создаваемом операцией конкатенации строк ( . ). Чтобы получить требуемый результат, следовало бы воспользоваться циклом while:
while(<F3>) { 
  print "Запись: ".$_; # Печать очередной строки связанного
                       # с дескриптором F3 файла
}
Напомним, что если результат операции <> не присваивается никакой переменной, то результат ее выполнения сохраняется в специальной переменной S_.
Фактически операция чтения записей <> в Perl реализуется встроенной функцией readline( ), которую можно вызывать и непосредственно. Единственным ее параметром является ссылка на дескриптор файла, а так как дескриптор не принадлежит ни к одному из допустимых типов данных (скаляр, массив скаляров или хэш-массив), то следует использовать ссылку на специальный внутренний тип данных Perl typeglob. Для этого достаточно поставить перед дескриптором файла префикс *:
readline *ДЕСКРИПТОР;
Например, следующие два оператора полностью эквивалентны:
$line = <STDIN>;
$line = readline *STDIN;
Естественно, все, что говорилось о символах разделения записей, хранящихся в специальной переменной $/, относится и к операции readline( ).
Запись в файл, открытый в режиме записи или добавления, осуществляется функцией print( ) с первым параметром, являющимся дескриптором файла:
print ДЕСКРИПТОР СПИСОК_ВЫВОДА;
Эта операция записывает содержимое элементов списка в том порядке, в котором они определены в вызове функции, и не добавляет в конец списка никакого разделителя записей или символа перехода на новую строку. Об этом должен позаботиться сам программист, либо явно добавляя в конец списка вывода символ разделителя записей или новой строки, либо воспользовавшись возможностью, предоставляемой специальной переменной $\. Функция print добавляет в конец своего списка вывода содержимое этой специальной переменной, которая по умолчанию содержит пустую строку:
# Явное задание разделителя записей
print F1 @recl1, "\n";
$\ = "\n";  # Установка разделителя записей
            # Теперь разделитель записей будет выводиться неявно 
            # каждой следующей функцией print 
print F1 @rесl2;
ВНИМАНИЕ Между дескриптором файла и первым элементом списка вывода не должно быть запятой. Если такое случится, то интерпретатор perl выдаст ошибку: No comma allowed after filehandle.
При записи информации в файл функцией print( ) можно воспользоваться еще одной полезной возможностью. Если задано значение специальной переменной $, , то оно вставляется между элементами списка вывода. Например, если мы хотим, чтобы значения элементов списка выводились не сплошным потоком символов, а были разделены пробелом, то следует установить значение этой специальной переменной равным пробелу:
$var1 = "11111";
Svar2 = "22222";
print $var1, $var2, "\n";
$, = " ";
print $var1, $var2, "\n";
Первый оператор print напечатает:
1111122222
Тогда как при выполнении второго оператора print мы получим строку:
11111 22222
ВНИМАНИЕ При установке значений специальных переменных $\ и $, их действие распространяется на все последующие вызовы функции print( ).
Если в функции print не указан дескриптор файла, то по умолчанию вывод осуществляется в стандартный файл вывода с дескриптором STDOUT, а если не задан список вывода, то выводится содержимое специальной переменной $_.
Установку дескриптора функции print( ) по умолчанию можно изменить стандартной функцией select( ). Вызванная без параметров, она возвращает текущий дескриптор файла вывода по умолчанию для функций print( ) и write( ). Если ей передается единственный параметр, то этот параметр должен быть дескриптором файла. В этом случае она также возвращает текущий дескриптор по умолчанию и меняет его на дескриптор, определенный переданным ей параметром. Пример использования функции select( ) приведен ниже.
# Сохранение текущего дескриптора по умолчанию и назначение
# F1 новым умалчиваемым дескриптором
$oldfilehandle = select(Fl);
# Вывод в файл,  ассоциированный с дескриптором Fl
print $line;
# Восстановление старого дескриптора по умолчанию 
select($oldfilehandle);
# Вывод в файл, ассоциированный со старым дескриптором 
print $line;
Как уже отмечалось, файлы в Perl интерпретируются как неструктурированные потоки байтов. То, что с помощью операции <> и функции print( ) мы соответственно читаем или записываем целую последовательность байтов, которую мы называем «записью», ни в коем случае не связано с какой-то определенной структурой файла. Просто эти операции так организованы, что одна читает, а вторая записывает последовательности байтов. В действительности мы можем читать и записывать информацию в файл побайтно.
Для каждого открытого файла создается системная переменная, которая отслеживает его текущую позицию, то есть место в файле, начиная с которого функции чтения читают, а функции записи записывают информацию. Следовательно, мы можем говорить, что операции чтения/записи выполняются с текущей позиции файла. При выполнении любой операции чтения/записи указатель текущей позиции файла перемещается на количество прочитанных или записанных байтов. Если, например, была прочитана запись длиной 80 байт с самого начала файла, то следующая операция чтения или записи начнется с позиции 81 байта файла.
Для определения текущей позиции в файле используется функция tell( ), единственным параметром которой может быть дескриптор файла. Она возвращает текущую позицию в ассоциированном с заданным дескриптором файле. Эта же функция без параметра возвращает текущую позицию в файле, для которого была в программе выполнена последняя операция чтения.
Текущая позиция в файле автоматически изменяется в соответствии с выполненными операциями чтения/записи, но ее можно менять и явным образом с помощью функции seek( ), которой передаются в качестве параметров дескриптор файла, смещение и точка отсчета. Для связанного с дескриптором файла устанавливается новая текущая позиция, смещенная на заданное параметром СМЕЩЕНИЕ число байтов относительно точки отсчета:
seek ДЕСКРИПТОР, СМЕЩЕНИЕ, ТОЧКА__ОТСЧЁТА;
Параметр ТОЧКА_ОТСЧЕТА может принимать одно из трех значений: 0 - начало файла, 1 - текущая позиция, 2 - конец файла. Смещение может быть как положительным, так и отрицательным. Оно отрицательно относительно конца файла, положительно относительно начала файла и может быть как положительным, так и отрицательным относительно текущей позиции. Задать точки отсчета можно так же с помощью именованных констант SEEK_SET, SEEK_CUR и SEEK_END, определенных в поставляемом с Perl пакете IO::Seekable, что делает программу лучше читаемой. Эти константы в том порядке, как мы их перечислили, соответствуют началу файла, текущей позиции и концу файла. Для использования указанных именованных констант, естественно, необходимо подключить в программе этот модуль с помощью ключевого слова use. Например, следующие операторы устанавливают одинаковые текущие позиции в файлах:
use IO::Seekable:
seek FILE1, 5, 0;
seek FILE2, 5, SEEK_SET;
В языке нет специальных функций перехода в начало или конец файла. Если необходимо позиционировать файл в начало или конец, следует использовать нулевое смещение относительно соответствующих точек отсчета при вызове функции seek( ):
seek FILE1, 0, 0;  # Переход в начало файла 
seek FILE1, 0, 2;  # Переход в конец файла
Кроме уже знакомых нам операций чтения записей файла <> и readline( ), Perl предоставляет еще две функции чтения содержимого файлов — getc( ) и read( ). Первая читает один байт из файла, тогда как вторая читает записи заданной длины, то есть последовательность байтов определенной длины.
Функция getc( ) читает и возвращает символ в текущей позиции файла, дескриптор которого передан ей в качестве параметра, или неопределенное значение в случае достижения конца файла либо возникновения ошибки. Если функция вызывается без параметра, то она читает символ из стандартного файла ввода STDIN.
getc;     # Чтение символа из STDIN 
getc F1;  # Чтение символа в текущей позиции файла с 
          # дескриптором F1
Функция read( ) читает определенное число байтов, начиная с его текущей позиции. Она может вызываться с тремя или четырьмя параметрами, и ее вызов имеет вид:
read ДЕСКРИПТОР, ПЕРЕМЕННАЯ, ДЛИНА [,СМЕЩЕНИЕ];
Эта функция читает количество байтов, определенное целым значением параметра ДЛИНА, в скалярную переменную, определяемую параметром ПЕРЕМЕННАЯ, из файла с дескриптором, заданным первым параметром ДЕСКРИПТОР. Возвращаемое значение — действительное количество прочитанных байтов, 0 при попытке чтения в позиции конца файла и неопределенное значение в случае возникновения других ошибок. Необязательный параметр СМЕЩЕНИЕ определяет, после какого байта содержимого переменной ПЕРЕМЕННАЯ будет сохранена прочитанная из файла запись. Он может иметь и отрицательное значение смещения -n (n — целое число). Это означает, что из содержимого переменной ПЕРЕМЕННАЯ отбрасываются последние n байтов и к оставшейся строке добавляется запись, прочитанная из файла. Листинг 6.10 демонстрирует чтение записей определенной длины из файла in.dat, содержащего три строки данных:
********
* PERL *
********
Листинг 6.10. Чтение записей определенной длины
#! perl -w
open(F1,  "in.dat") or die "Ошибка открытия файла: $!";
$str = "1234567890";
read F1, $str, 9;  # Чтение девяти байтов в
                   # переменную $str без смещения
print $str,"\n";   # $str = "********\n"
read F1, $str, 8, length($str); 
print $str,"\n";   # $str - "*******\n* PERL *"
В программе из листинга 6.10 функция length( ) используется для определения количества символов (байтов), содержащихся в скалярной переменной. После выполнения первой операции чтения содержимое переменной $str было уничтожено, так как эта функция read ( ) вызывалась без смешения. При втором чтении хранившиеся данные в переменной $str были полностью сохранены. Обратите внимание, что символ перехода на новую строку, содержащийся в первой строке файла in.dat, также учитывается при чтении функцией read( ) записей определенной длины. Следует не забывать об этом обстоятельстве при чтении информации из «многострочного» файла функцией read( ).

Небуферизованный ввод-вывод

Функции чтения из файла sysread( ), записи в файл syswrite( ) и установки указателя текущей позиции файла sysseek( ) являются аналогами рассмотренных нами функций read( ),print( ) и seek( ), но, в отличие от последних, они напрямую обращаются к соответствующим функциям операций системы, а не к функциям стандартной библиотеки ввода-вывода С, минуя тем самым создаваемый этими функциями буфер для выполнения операций чтения и записи в файл. Заметим, что аналога буферизованной функции tell ( ) не существует, ее функциональность реализуется функцией sysseek( ).
При вызове функций небуферизованного чтения и записи им передается одинаковый набор параметров, полностью соответствующий параметрам функции read:
sysread ДЕСКРИПТОР, ПЕРЕМЕННАЯ, ДЛИНА [,СМЕЩЕНИЕ];
syswrite ДЕСКРИПТОР, ПЕРЕМЕННАЯ, ДЛИНА [,СМЕЩЕНИЕ];
Возвращаемым значением этих функций является истинное количество соответственно прочитанных или записанных в файл байтов, 0 в случае достижения конца файла или undef при возникновении ошибки.
Соответственно набор параметров функции sysseek( ) полностью соответствует передаваемым параметрам в функцию seek( ):
sysseek ДЕСКРИПТОР, СМЕЩЕНИЕ, ТОЧКА_ОТСЧЕТА;
Все сказанное относительно использования функции seek( ) полностью переносится и на ее небуферизованный аналог.
Функциональность буферизованной операции tell( ) реализуется следующим вызовом функции sysseek( ):
$position = sysseek Fl, 0, 1;  # Текущая позиция указателя файла
Программа демонстрирует использование небуферизованных функций ввода-вывода для обработки содержимого файла.
#! perl -w 
use Fcntl;
# Открытие файла в режиме чтение/запись
sysopen F1, "in.dat", O_RDWR;
# Чтение блока в 14 байтов
$read = sysread F1, $string, 14;
warn "Прочитано $read байтов вместо 14\n" if $read != 14;
# Установка текущей позиции (на 15 байтов) 
$position = sysseek Fl, 0, 1; 
die "Ошибка позиционирования: $!\n" unless defined $position;
# Запись строки в текущей позиции
$string = "Новое Значение";
$written = syswrite F1, $string, length($string); 
die "Ошибка записи: $!\n" if $written != length($string);
# Закрытие файла 
close F1 or die $!;
При работе с небуферизованными функциями ввода-вывода следует всегда проверять завершение операции чтения, записи или позиционирования. Стандартная система ввода-вывода, через которую реализуется буферизованный ввод-вывод, сама проверяет завершение указанных операций и отвечает за него, если процесс по каким-то причинам был прерван на середине записи. При небуферизованном вводе-выводе об этом должен позаботиться программист.

Следующая страница Содержание главы


Реклама