07 апреля 2020

Доступ к файлам

    Как мы уже знаем, для доступа к файлу из программы Perl необходим дескриптор. Дескриптор файла создается функцией open (), которая является списковой операцией Perl:
open ДЕСКРИПТОР, ИМЯ_ФАЙЛА; open ДЕСКРИПТОР;
     При выполнении операции open с заданным в параметрах именем файла открывается соответствующий файл и создается дескриптор этого файла. В качестве дескриптора файла в функции open () можно использовать выражение — его значение и будет именем дескриптора. Имя файла задается непосредственно в виде строкового литерала или выражения, значением которого является строка. Операция open без имени файла открывает файл, имя которого содержится в скалярной переменной $ДЕСКРИПТОР, которая не может быть лексической переменной, определенной функцией ту(). Пример 7.1 демонстрирует использование операции open () для открытия файлов.
#! peri -w
$var = "out.dat";
$FILE4 = "file4.dat";
open FILE1, "in.dat"; # Имя файла задано строкой
open FILE2, $var; # Имя файла'задано переменной
open FILE3, "/perlourbook/01/".$var; # Имя файла вычисляется в выражении
open FILE4; # Имя файла в переменной $FILE4

Замечание
Если задано не полное имя файла, то открывается файл с указанным именем и расположенный в том же каталоге, что и программа Perl. Можно задавать полное имя файла (см. третий оператор open примера 7.1), однако следует иметь в виду, что оно зависит от используемой операционной системы. Например, в Windows следует обязательно задавать имя диска: d: /perlourbook/01/Chapterl. doc.
Замечание
В системе UNIX можно открыть достаточно много файлов, тогда как в DOS и Windows количество открытых файлов зависит от установленного значения переменной окружения FILE и варьируется от 20 до 50 одновременно открытых файлов.
     Любой файл можно открыть в одном из следующих режимов: чтения, записи или добавления в конец файла. Это осуществляется присоединением соответствующего префикса к имени файла: < (чтение), > (запись), » (добавление). Если префикс опущен, то по умолчанию файл открывается в режиме чтения. Запись информации в файл, открытый в режиме записи (префикс >), осуществляется в начало файла, что приводит к уничтожению содержащейся в нем до его открытия информации. Информация, содержащаяся в файле, открытом в режиме добавления (префикс »), не уничтожается, новые записи добавляются в конец файла. Если при открытии файла в режиме записи или добавления не существует файла с указанным именем, то он создается, что отличает эти режимы открытия файла от режима чтения, при котором файл должен существовать. В противном случае операция открытия завершается с ошибкой и соответствующий дескриптор не создается.
     Perl позволяет открыть файл еще в одном режиме — режиме чтения/записи. Для этого перед префиксом чтения <, записи > или добавления » следует поставить знак плюс +. Отметим различия между тремя режимами чтения/записи +<, +> и +». Первый и третий режимы сохраняют содержимое открываемого файла, тогда как открытие файла с использованием второго режима (+>) сначала очищает содержимое открываемого файла. Третий режим отличается от первых двух тем, что запись в файл всегда осуществляется в конец содержимого файла.

Замечание
Некоторые операционные системы требуют устанавливать указатель чтения/записи файла при переключении с операций чтения на операции записи. В Perl для этого предназначена функция seek (), описание которой будет дано несколько позже в этом же параграфе.
      Открытие файла и создание для него дескриптора функцией open () охватывает все практически важные режимы работы с файлом. Однако возможности этой функции не позволяют задать права доступа для создаваемых файлов, а также вообще решить, следует ли создавать файл, если его не существует. Для подобного "тонкого" открытия файлов можно использовать функцию sysopeno, которая позволяет программисту самому задать отдельные компоненты режима работы с файлом: чтение, запись, создание, добавление, очистка содержимого и т. д. Синтаксис этой функции таков:
sysopen ДЕСКРИПТОР, ИМЯ_ФАЙЛА, ФЛАГ [, РАЗРЕШЕНИЕ];
     Здесь параметр ИМЯ_ФАЙЛА представляет имя файла без префиксов функции open (), определяющих режим открытия файла. Последний задается третьим параметром ФЛАГ — числом, представляющим результат операции побитового ИЛИ (|) над константами режимов, определенными в модуле Fcnti. Состав доступных констант зависит от операционной системы. В табл. 7.1 перечислены константы режима, встречающиеся практически во всех операционных системах.
Таблица 7.1. Константы режима доступа к файлу
Константа Значение
0_RDONLY Только чтение
0_WRONLY Только запись
O_RDWR Чтение и запись
O_CREAT Создание файла, если он не существует
О EXCL Завершение с ошибкой, если файл уже существует
0_APPEND Добавление в конец файла %
     Права доступа (необязательный параметр РАЗРЕШЕНИЕ) задаются в восьмеричной системе и при их определении учитывается текущее значение маски доступа к процессу, задаваемого функцией umasko. Если этот параметр не задан, то Perl использует значение 0666.
(О правах доступа читайте документацию Perl для установленной на вашем компьютере операционной системе.)

Совет
Если возникают затруднения с установкой прав доступа, то придерживайтесь следующего правила: для обычных файлов передавайте 0666, а для каталогов и исполняемых файлов 0777.
     В примере 7.2 собраны операции открытия файлов функцией open (} и эквивалентные ИМ ОТКРЫТИЯ С ПОМОЩЬЮ фуНКЦИИ sysopen () .
use Fcnti;
# Только чтение
open FF, "< file.txt";
sysopen FF, "file.txt", O_RDONLY;
# Только запись (создается, если не существует,
# и очищается содержимое, если существует)
open FF, "> file.txt";
sysopen FF, "file.txt", 0_WRONLY | 0_CREAT | OJTRUNC;
# Добавление в конец (создается, если не существует)
open FF, "» file.txt";
sysopen FF, "file.txt", OJJRONLY I 0_CREAT I O_APPEND;
# Чтение/запись (файл должен существовать) open FF, "+< file.txt"; sysopen FF, "file.txt", O_RDWR;
# Чтение/запись (файл очищается)
open FF, "+> file.txt";
sysopen FF, "file.txt", O_RDWR | 0_CREAT I OJTRUNC;
     При открытии файла функции open о и sysopen о возвращают значение о, если открытие файла с заданным режимом произошло успешно, и неопределенное значение undef в противном случае. Всегда следует проверять успешность выполнения операции открытия файла, прекращая выполнение программы функцией die (). Эта функция отображает список передаваемых ей параметров и завершает выполнение сценария Perl:
open(FF, "+< $file") or'die "Нельзя открыть файл $file: $!";
     Обратите внимание, в сообщении функции die () используется специальная переменная $!, в которой хранится системное сообщение или код ошибки. Эта информация помогает обнаружить и исправить ошибки в программе. Например, если переменная $fiie содержит имя не существующего файла, то при выполнении предыдущего оператора пользователь может увидеть сообщение следующего вида:
Нельзя открыть файл file.txt: No such file or directory at D:\PERL\EX2.PL line 4.
Английский текст этого сообщения представляет информацию, содержащуюся в Переменной $!.
     Для полноты описания работы с функцией open о следует сказать, что если имя файла представляет строку "-", то открываемый файл соответствует стандартному вводу STDIN. Это означает, что ввод с помощью созданного дескриптора файла осуществляется со стандартного устройства ввода. Если имя файла задано в виде строки ">-", то это соответствует выводу на стандартное устройство вывода, представленное в программе дескриптором STDOUT.

Замечание
Если стандартный ввод или вывод были перенаправлены, то ввод/вывод с помощью дескрипторов, соответствующих файлам "-" и ">-", будет осуществляться в файл, определенный в операции перенаправления стандартного ввода или вывода.
     Последнее, что нам хотелось бы осветить в связи с дескрипторами файлов, - это созданиедескриптора-дубликата. Если в строке имени файла после префикса режима открытия следует амперсанд "&", то ее оставшаяся часть рассматривается как имя дескриптора файла, а не как имя открываемого файла. В этом случае создается независимая копия этого дескриптора с именем, заданным первым параметром функции open <). Оба дескриптора имеют общий указатель текущей позиции файла, но разные буферы ввода/вывода. Закрытие одного из дескрипторов не влияет на работу другого. В программах Perl возможность создания копии дескриптора в основном применяется для восстановления стандартных файлов ввода/вывода после их перенаправления на другие файлы (пример 7.3).
#! peri -w
# Создание копии дескриптора STDOUT open(OLDOUT, ">&STDOUT");
# Перенаправление стандартного вывода
open(STDOUT, "> file.out") or die "Невозможно перенаправить STDOUT: $!";
# Печать в файл file.out
print "Информация в перенаправленный STDOUTXn";
# Закрытие перенаправленного дескриптора стандартного вывода close(STDOUT) or die "Невозможно закрыть STDOUT: $!";
# Восстановить файл стандартного вывода
open(STDOUT, ">&OLDOUT") or die "Невозможно восстановить STDOUT: $!";
# Закрыть копию дескриптора стандартного вывода STDOUT close(OLDOUT) or die "Невозможно закрыть OLDOUT: $!";
# Печать в восстановленный файл стандартного вывода print "Информация в восстановленный STDOUTXn";

Замечание
В программах следует избегать работу с одним файлом через несколько дескрипторов-копий.

     По завершении работы с файлом он закрывается функцией close 0. Единственным необязательным параметром этой функции является дескриптор, ассоциированный с файлом:
close ДЕСКРИПТОР;
     Эта функция возвращает значение Истина, если успешно очищен буфер ввода/вывода и закрыт системный дескриптор файла. Вызванная без параметра, функция close закрывает файл, связанный с текущим дескриптором, установленным функцией select 0.
     Следует отметить, что закрывать файлы в программе функцией close о не обязательно. Дело в том, что открытие нового файла с дескриптором, уже связанным с каким-либо файлом, закрывает этот старый файл. Более того, при завершении программы все открытые в ней файлы закрываются. Однако такое неявное закрытие файлов таит в себе потенциальные ошибки из-за невозможности определить, завершилась ли эта операция корректно. Может оказаться, что при записи в файл переполнится диск, или будет разорвана связь с удаленным устройством вывода. Подобные ошибки можно "отловить", если использовать явное закрытие файла и проверять содержимое специальной переменной $!:
closet FILEIO ) or die "Ошибка закрытия файла: $!";
      Существует еще один нюанс, связанный с явным закрытием файлов. При чтении из файла специальная переменная $. (если ее значение не изменено явным образом в программе) хранит номер последней прочитанной записи файла. При явном закрытии файла функцией close о значение этой переменной обнуляется, тогда как при неявном закрытии оно остается равным номеру последней прочитанной записи старого файла и продолжает увеличиваться при операциях чтения из нового файла.
     Чтение информации из файла осуществляется операцией о, операндом которой является дескриптор файла. В скалярном контексте при первом выполнении эта операция читает первую запись файла, устанавливая специальную переменную $., отслеживающую количество прочитанных записей, равной 1. Последующие обращения к операции чтения из файла с тем же дескриптором приводят к последовательному чтению следующих записей. В списковом контексте эта операция читает все оставшиеся записи файла и возвращает список, элементами которого являются записи файла. Разделитель записей хранится в специальной переменной $/, и по умолчанию им является символ новой строки "\n". Perl позволяет задать и другой разделитель записей обычной операцией присваивания переменной $/ нового символа разделителя записей. В примере 7.4 демонстрируются некоторые приемы чтения из файла.
#! peri -w
open(Fl, "in.dat") or die "Ошибка открытия файла: $!";
open(F2, "out.dat") or die "Ошибка открытия файла: $!";
$linel = <F1>; # Первая запись файла in.dat $line2 = <F1>; # Вторая запись файла in.dat
@rest =• <F1>; # Оставшиеся записи файла in.dat
$/=":"; I Задание другого разделителя записей файла @f2 = <F2>;
# Печать прочитанных записей файла out.dat for($i=0; $i<=$#f2; $i++) { print "$f2[$i]\n";
}
$/ = "\n"; # Восстановление умалчиваемого разделителя записей
close(Fl) or die $!; close(F2) or die $!;
open(F3, "out.dat") or die "Ошибка открытия файла: $!"; print <F3>; # Печать всего файла close(F3) or die $!;
     Несколько комментариев к программе примера 7.4. В переменные $iinel и $iine2 читаются соответственно первая и вторая строка файла in.dat, так как используется умалчиваемый разделитель записей "\п". Элементы массива @rest хранят строки с третьей по последнюю этого же файла: в операторе присваивания операция чтения <FI> выполняется в списковом контексте.
     Перед чтением записей файла out.dat устанавливается новый разделитель записей — символ ":". Если файл out.dat, например, содержит только одну строку
111: 222: 333: Конец
то элементы массива @ f г будут содержать следующие значения:
$f2[0] = "111:" $f2[l] = "222:" $f2[2] = "333:" $f2[3] = "Конец"

Замечание
Если при создании файла out.dat его единственная строка завершена переходом на новую строку (нажата клавиша <Enter>), то $f2[3], будет содержать строку "конец\п".
     При достижении конца файла операция о возвращает неопределенное значение, которое трактуется как Ложь. Это обстоятельство обычно используется для организации чтения записей файла в цикле:
while($line = <F1>) {
print $line; f Печать очередной строки связанного
# с дескриптором F1 файла } •
Запись в файл, открытый в режиме записи или добавления, осуществляется функцией print () с первым параметром, являющимся дескриптором файла:
print ДЕСКРИПТОР СПИСОК_ВЫВОДД;
     Эта операция записывает содержимое элементов списка в том порядке, в котором они определены в вызове функции, и не добавляет в конец списка разделителя записей. Об этом должен позаботиться сам программист:
$/=":"; # Разделитель записей
print Fl @recll, $/; # Запись в файл первой записи
print Fl @rec!2, $/; tt Запись в файл второй записи

Замечание
Между дескриптором и первым элементом списка вывода не должно быть запятой. Если такое случится, то компилятор peri выдаст ошибку:
No comma allowed after filehandle
     Если в функции print не указан дескриптор файла, то по умолчанию вывод осуществляется в стандартный файл вывода с дескриптором STDOUT. Эту установку можно изменить функцией select (). Вызванная без параметров, она возвращает текущий умалчиваемый дескриптор для вывода функциями print () и write (). Если ей передается единственный параметр, то этот параметр должен быть дескриптором файла. В этом случае она также возвращает текущий умалчиваемый дескриптор и меняет его на дескриптор, определенный переданным ей параметром.
$oldfilehandle = select(Fl); I Сохранение текущего дескриптора по
# умолчанию и назначение нового F1
print $line; # Вывод в дескриптор F1 select($oldfilehandle); # Восстановление старого дескриптора
# по умолчанию print $line; # Вывод в старый дескриптор
     Файлы в Perl интерпретируются как неструктурированные потоки байтов. При работе с файлом через дескриптор отслеживается его текущая позиция. Операции чтения/записи выполняются с текущей позиции файла. Если, например, была прочитана запись длиной 80 байт, то следующая операция чтения или записи начнется с 81 байта файла. Для определения текущей позиции в файле используется функция tell (), единственным параметром которой может быть дескриптор файла. Она возвращает текущую позицию в связанном с дескриптором файле. Эта же функция без параметра возвращает текущую позицию в файле, для которого была в программе выполнена последняя операция чтения.
     Текущая позиция в файле автоматически изменяется в соответствии с выполненными операциями чтения/записи. Ее можно изменить с помощью функции seek о, которой передаются в качестве параметров дескриптор файла, смещение и точка отсчета. Для связанного с дескриптором файла устанавливается новая текущая позиция, смещенная на заданное параметром СМЕЩЕНИЕ число байт относительно точки отсчета:
seek ДЕСКРИПТОР, СМЕЩЕНИЕ, TO4KAJDTC4ETA;
     Параметр ТОЧКА_ОТСЧЕТА может принимать одно из трех значений: о — начало файла, 1 — текущая позиция, 2 — конец файла. Смещение может быть как положительным, так и отрицательным. Обычно оно отрицательно для смещения относительно конца файла и положительно для смещения относительно начала файла. Для задания точки отсчета можно воспользоваться константами SEEK_SET, SEEK_CUR и SEEK_END из модуля ю: :Seekabie, которые соответствуют началу файла, текущей позиции и концу файла. Естественно, необходимо подключить этот модуль к программе с помощью ключевого слова use. Например, следующие операторы устанавливают одинаковые текущие позиции в файлах:
use 10::Seekable; seek FILE1, 5, 0; seek FILE2, 5, SEEK_SET;
     Для перехода в начало или в конец файла следует использовать нулевое смещение относительно соответствующих точек отсчета при обращении к функции seek ():
seek FILE1, 0, 0; # Переход в начало файла seek FILE1, 0, 2; § Переход в конец файла
     Кроме операции чтения записей файла о, Perl предоставляет еще два способа чтения информации из файла: функции getc () и read (). Первая читает один байт из файла, тогда как вторая читает записи фиксированной длины.
     Функция getc возвращает символ в текущей позиции файла, дескриптор которого передан ей в качестве параметра, или неопределенное значение в случае достижения конца файла или возникновении ошибки. Если функция вызывается без параметра, то она читает символ из стандартного файла ввода STDIN.
getc; t Чтение символа из STDIN
getc Fl; # Чтение символа в текущей позиции файла с дескриптором F1
Функции read () передаются три или четыре параметра и ее синтаксис имеет вид:
read ДЕСКРИПТОР, ПЕРЕМЕННАЯ, ДЛИНА [,СМЕЩЕНИЕ] ;
     Она читает количество байтов, определенное значением параметра ДЛИНА, в скалярную переменную, определяемую параметром ПЕРЕМЕННАЯ, из файла с дескриптором, заданным первым параметром ДЕСКРИПТОР. Возвращаемое значение — действительное количество прочитанных байтов, о при попытке чтения в позиции конца файла и неопределенное значение в случае возникновения ошибки. Параметр СМЕЩЕНИЕ определяет количество сохраняемых байтов из содержимого переменной ПЕРЕМЕННАЯ, т. е. запись прочитанных из файла данных будет добавлена к содержимому переменной после байта, определяемого значением параметра СМЕЩЕНИЕ. Отрицательное значение смещения -п (п — целое число) означает, что из содержимого переменной ПЕРЕМЕННАЯ отбрасываются последние п байтов и к оставшейся строке добавляется запись, прочитанная из файла. Пример 7.5 демонстрирует чтение записей фиксированной длины в предположении, что файл in.dat содержит три строки данных:
One Two Three
#! peri -w
open(Fl, "in.dat") or die "Ошибка открытия файла: $!";
$string = "1234567890";
read Fl, $string, 6; I Чтение шести байт в переменную без смещения
print $string,"\n"; # $string = "OneXnTw"
read Fl, $string, 6, length($string);
print $string,"\n"; # $string = "One\nTwo\nThre"
     Функция length о возвращает количество символов (байтов) в строковых данных, хранящихся в скалярной переменной, переданной ей в качестве параметра. После выполнения первой операции чтения содержимое переменной $string было уничтожено, так как эта функция read о вызывалась без смещения. Тогда как при втором чтении хранившиеся данные в переменной $string были полностью сохранены.
     Операции о, print, read, seek и tell относятся к операциям буферизованного ввода/вывода, т. е. они для повышения скорости выполнения используют буферы. Perl для выполнения операций чтения из файла и записи в файл предлагает также аналоги перечисленных функций, не использующие буферы при выполнении соответствующих операций с содержимым файла.
Функции sysread и syswrite являются не буферизованной заменой операции
о И ФУНКЦИИ print, а ФУНКЦИЯ sysseek Заменяет ФУНКЦИИ seek И tell.
Функции не буферизованного чтения и записи получают одинаковые параметры, которые соответствуют параметрам функции read:
sysread ДЕСКРИПТОР, ПЕРЕМЕННАЯ, ДЛИНА [,СМЕЩЕНИЕ]; syswrite ДЕСКРИПТОР, ПЕРЕМЕННАЯ, ДЛИНА [.СМЕЩЕНИЕ];
     Смысл всех параметров аналогичен параметрам функции read (). Возвращаемым значением этих функций является истинное количество прочитанных/записанных байт, о в случае достижения конца файла или undef при возникновении ошибки.
Параметры функции sysseek о полностью соответствуют параметрам функции seek():
sysseek ДЕСКРИПТОР, СМЕЩЕНИЕ, ТОЧКА_ОТСЧЕТА;
Все, сказанное относительно использования функции seek о, полностью переносится и на ее не буферизованный аналог.
Функциональность буферизованной операции tell () реализуется следующим вызовом функции sysseek:
$position = sysseek Fl, 0, 1; # Текущая позиция указателя файла
Пример 7.6 демонстрирует использование не буферизованных функций, ввода/вывода для обработки содержимого файла.
#! peri -w use Fcntl;
# Открытие файла в режиме чтение/запись sysopen Fl, "in.dat", OJRDWR;
# Чтение блока в 14 байт
$read = sysread Fl, $string, 14;
warn "Прочитано $read байт вместо 14\n" if $read != 14;
# Установка текущей позиции (на 15 байт). $position = sysseek Fl, 0, 1; die "Ошибка позиционирования: $!\п" unless defined $position;
# Запись строки в текущей позиции $string = "Новое значение"; $written = syswrite Fl, $string, length($string);
die "Ошибка записи: $!\n" if $written != length($string); # Закрытие файла
close Fl or die $!;
     При работе с не буферизованными функциями ввода/вывода следует всегда проверять завершение операции чтения, записи или позиционирования. Стандартная система ввода/вывода, через которую реализуется буферизованный ввод/вывод, сама проверяет и отвечает за завершение указанных операций, если процесс был прерван на середине записи. При не буферизованном вводе/выводе об этом должен позаботиться программист.

Совет
При работе с одним и тем же файлом не следует смешивать вызовы буферизованных и не буферизованных функций ввода/вывода. Подобная практика может приводить к непредсказуемым коллизиям.