Открытие и закрытие файла

Для того чтобы получить в программе доступ к информации, содержащейся в файле, следует прежде всего создать дескриптор файла, который в дальнейшем будет являться «представителем» этого файла в различных операциях извлечения из него и записи в него информации. Но до того как можно будет осуществлять различные манипуляции с содержимым файла, он должен быть открыт, а после завершения работы с ним — закрыт.
Обычно во всех языках программирования дескриптор файла (в других языках могут использоваться иные термины для механизма ссылки на файл, который мы в Perl называем дескриптором) создается одновременно с открытием файла. В Perl для этих целей служит стандартная функция ореn( ), являющаяся списковой операцией. Ее вызов может иметь три различные формы:
open ДЕСКРИПТОР, РЕЖИМ, ИМЯ_ФАЙЛА; 
open ДЕСКРИПТОР, ИМЯ_ФАЙЛА; 
open ДЕСКРИПТОР;
Первые две формы операции open открывают файл с именем, равным значению параметра ИМЯ_ФАЙЛА, представленного либо строкой, либо выражением, значение которого должно быть строкой. При этом создается и ассоциируется с файлом его дескриптор, имя которого определяется параметром ДЕСКРИПТОР. Этот параметр должен представлять правильный идентификатор Perl (последовательность алфавитно-цифровых символов, начинающаяся с буквы), но может быть и выражением. В этом случае вычисленное значение выражения обязательно также должно быть правильным идентификатором.
Операция open без имени файла открывает файл, имя которого содержится в скалярной переменной $ДЕСКРИПТОР, создавая для него дескриптор, определяемый параметром ДЕСКРИПТОР. Заметим, что переменная, содержащая имя открываемого файла, не может быть локальной лексической, определяемой функцией my( ).
Листинг 6.6 демонстрирует использование операции open( ) для открытия файлов. (Пока мы не ввели понятие режима открытия файла, считаем, что все файлы открываются только для чтения.)
Листинг 6.6. Разные формы операции open открытия файлов
#! perl  -w
# Имя файла задано строкой
open FILE1, "in.dat" or die "Ошибка открытия файла in.dat";
# Имя файла задано выражением
$name = "out";
$extent = "dat";
open FILE2, $name."."$extent or
  die "Ошибка открытия файла $name.$extent ";
# Имя файла задано переменной
$var = "/реrl/bin/perl.bat";
open FILE3, $var or die "Ошибка открытия файла $var";
# Имя файла содержится в переменной $FILE4
$FILE4 = "file4.dat";
open FILE4 or die "Ошибка открытий файла $FILE4";
# Имя дескриптора файла содержится в переменной $FH
$FH = "FILE5";
$FILE5 = "file5.dat":
open $FH or die "Ошибка открытия файла $FILE5";
Обратите внимание, что во всех операторах открытия файла проверяется возвращаемое значение функции open, которое равно «истина» в случае успешного открытия файла и «ложь» в противном случае. Причем, если произошла какая-то ошибка, то вызывается стандартная функция die( ), которая печатает сообщение и прекращает выполнение сценария. Обычно считается, что ошибка открытия файла столь серьезна, что программа не может далее выполняться. Если в вашем случае это не так, то можно просто проверить (а это рекомендуется делать всегда!) возвращаемое функцией open значение и предпринять необходимые действия в самой программе.
ПРИМЕЧАНИЕ Если задано не полное имя файла, то открывается файл с указанным именем и расположенный в текущем активном каталоге. Можно задавать полное имя файла (см. третий оператор open из листинга 6.6), однако следует иметь в виду, что оно зависит от используемой операционной системы. Например в Windows следует обязательно задавать имя дисковода d:/perl/bin/perl.bat, если только файл не расположен на текущем активном диске. Количество одновременно открытых в программе файлов зависит от используемой операционной системы. Например, в системе UNIX можно открыть достаточно большое количество файлов, тогда как в DOS и Windows количество открытых файлов зависит от установленного значения переменной окружения FILES (от одного до 255) и в основном варьируется от 20 до 50 одновременно открытых файлов.
Обратимся теперь к режимам открытия файлов. Любой файл можно открыть в одном из трех следующих режимов: чтения, записи или добавления информации в конец файла. Для этого следует воспользоваться первой формой операции open и в качестве второго ее параметра РЕЖИМ задать соответственно следующие строки: "<" (чтение), ">" (запись), ">>" (добавление) или переменную, содержащую один из перечисленных символов.
В случае второй и третьей форм операции открытия файла open( ) следует присоединить соответствующий префикс к имени файла. Например, если необходимо открыть файл file.dat для добавления в него информации, то имя файла, передаваемое в функцию open, должно выглядеть следующим образом: ">>file.dat". Если префикс опушен, то по умолчанию файл открывается в режиме чтения, то есть подразумевается следующее имя файла: "<file.dat".
При открытии файла в режиме записи вся содержащаяся в нем информация уничтожается, причем если файла с указанным именем не существует, то он создается. Информация, содержащаяся в файле, открытом в режиме добавления, не уничтожается, новые записи добавляются в конец файла. И так же, как и в случае с файлом, открываемом в режиме записи, если не существует файла с указанным именем, то он создается. В случае с открытием файла в режиме чтения он должен существовать, иначе операция открытия завершается с ошибкой и соответствующий дескриптор не создается.
Perl позволяет открыть файл еще в одном режиме— режиме чтения/записи, который позволяет как писать в файл, так и читать из него. Для этого следует добавить символ плюс (+) перед символом любого режима открытия файла. Отметим различия между тремя режимами чтения/записи: "+<", "+>" и "+>>". Первый и третий режимы сохраняют содержимое открываемого файла, тогда как открытие файла с использованием второго режима ("+>") сначала очищает содержимое открываемого файла. Третий режим отличается от первых двух тем, что запись в файл всегда осуществляется в конец содержимого файла, тогда как первые два позволяют записывать информацию в любое место файла.
Первый из двух, в общем-то одинаковых, режимов чтения/записи "+<" и "+>" считается безопасным (информация в файле не уничтожается) и предпочтителен в употреблении по сравнению со вторым.
ПРИМЕЧАНИЕ Некоторые операционные системы требуют устанавливать указатель чтение/записи файла при переключении с операций чтения на операции записи. В Perl для этого предназначена функция seek( ), описание которой будет дано несколько позже в этом же параграфе. При открытии файлов в режиме чтения/записи с помощью функции ореn( ) в форме с одним и двумя параметрами соответствующие трем возможным режимам последовательности символов должны быть добавлены в виде префикса к именам файлов с допускаемым между ними и именами файлов произвольным числом пробелов, например, "+<file.dat" или "+>>file.dat".
Открытие файла и создание для него дескриптора функцией open( ) является хорошим и удобным способом установки режима работы с файлом, а также при необходимости создания нового файла. Однако возможности этой функции не позволяют задать права доступа для вновь создаваемых файлов, что является их важной характеристикой в системе UNIX (см. примечание ниже), а также решить проблему, следует ли создавать файл, если его не существует.
ПРИМЕЧАНИЕ В UNIX для каждого файла определены права доступа для владельца файла (пользователя, создавшего его), для группы пользователей, в которую входит владелец файла, и для всех остальных пользователей. Эти права в каждой из перечисленных групп включают в себя право на чтение файла, право на запись в файл и право на выполнение файла, представляемые в виде трехбитового двоичного числа, которое удобно интерпретировать в виде восьмеричного числа: значение 4 определяет право на чтение, 2 — на запись и 1 — на выполнение. Остальные восьмеричные числа до 7 включительно представляют комбинацию прав доступа. Например, значение 6 определяет права чтения и записи (4+2=6). Таким образом, для каждой из перечисленных групп пользователей (владелец файла, пользователи его группы и все остальные) с помощью восьмеричного числа от 0 до 7 можно задать все возможные комбинации прав доступа, что обычно представляется в виде трехзначного восьмеричного числа. Например, значение 0666 (ноль ничего не определяет, а служит всего лишь указателем, что это восьмеричное число) сообщает, что для файла определены права на чтение и запись, но не на выполнение, для всех трех групп возможных пользователей.
Функция open( ) создает файлы со значением прав доступа, равным 0666.
Для «тонкого» открытия файлов с разрешением создания файла в случае его отсутствия, с указанием прав доступа для вновь создаваемого файла и т. п., следует вместо функции open(), которая является интерфейсом к соответствующей функции стандартной библиотеки stdio языка С, использовать стандартную функцию Perl sysopen( ), непосредственно обращающуюся к функции open( ) операционной системы и позволяющую программисту самому задать отдельные компоненты режима работы с файлом: чтение, запись, создание, добавление, очистка содержимого и т. д. Вызов этой функции выглядит следующим образом:
sysopen ДЕСКРИПТОР, ИМЯ_ФАЙЛА, РЕЖИМ [, РАЗРЕШЕНИЕ];
Здесь параметр ИМЯ_ФАЙЛА представляет просто имя файла, параметр РЕЖИМ определяет режим открытия файла и представляет собой число, являющееся результатом операции побитового ИЛИ (|) над константами режимов, определенными в модуле Fcntl. Состав доступных констант зависит от операционной системы. В табл. 6.1 перечислены константы режима, встречающиеся практически во всех операционных системах.
Таблица 6.1. Наиболее употребительные константы режима доступа к файлу
Константа Значение
О_RDONLY Только чтение
О_WRONLY Только запись
О_RDWR Чтение и запись
O_CREAT Создание файла, если он не существует
О_EXCL Завершение с ошибкой, если с файл уже существует
О_APPEND Добавление в конец файла
О_TRUNC Очистка содержимого файла
Чтобы получить доступ к перечисленным в табл. 6.1 именованным константам режимов, следует в программе подключить стандартный модуль Fcntl оператором use Fcntl;, который обычно записывается в начале программы.
Необязательный параметр РАЗРЕШЕНИЕ определяет права доступа к создаваемому файлу в системе UNIX и задается в виде восьмеричного числа. Заметим, что при определении фактических прав доступа к файлу учитывается также текущее значение маски доступа к процессу, в котором выполняется сценарий, задаваемый функцией umask( ). Если параметр РАЗРЕШЕНИЕ не задан, то в качестве прав доступа Perl использует значение 0666.
СОВЕТ Если возникают затруднения с установкой прав доступа, то придерживайтесь следующего правила: для обычных файлов передавайте 0666, а для каталогов и исполняемых файлов — 0777.
С открытием файлов функцией open( ) вместе с эквивалентными им обращениями к функции sysopen( ) можно ознакомиться в листинге 6.7.
Листинг 6.7. Открытие файлов
use Fcntl;
# Только чтение
open FF, "< file.txt";
sysopen FF, "file.txt", O_RDONLY;
# Только запись (создается, если не существует,
# и очищается содержимое, если существует)
open FF, "> file.txt";
sysopen FF, "file.txt", O_WRONLY | O_CREAT | O_TRUNC;
# Добавление в конец (создается, если не существует)
open FF, ">> file.txt";
sysopen FF, "file.txt", O_WRONLY | O_CREAT | O_APPEND;
# Чтение/запись (файл должен существовать)
open FF, "+< file.txt"; 
sysopen FF, "file.txt", O_RDWR;
# Чтение/запись (файл очищается)
open FF, "+> file.txt";
sysopen FF, "file.txt", O_RDWR | O_CREAT | O_TRUNC;
При открытии файла функции open( ) и sysopen( ) возвращают значение 0, если открытие файла с заданным режимом произошло успению, и неопределенное значение undef в противном случае. Еще раз напомним, что всегда следует проверять успешность выполнения операции открытия файла, прекращая выполнение программы функцией die( ). В отображаемом сообщении функции die( ) можно использовать специальную переменную $!, в которой хранится системное сообщение или код ошибки. Эта информация помогает обнаружить и исправить ошибки в программе. Например, если в операторе
open FH, "$file" or die "Нельзя открыть файл $file: $!";
переменная $file содержит имя несуществующего файла, то при его выполнении пользователь может увидеть сообщение следующего вида:
Нельзя открыть файл file.txt: No such file or directory at ex06-00.pl line 4.
Для полноты описания работы с функцией open( ) следует сказать, что если имя файла представляет строку "-", то открываемый файл соответствует стандартному устройству ввода с дескриптором STDIN. Значение, равное строке ">-", соответствует выводу на стандартное устройство вывода с дескриптором STDOUT.
ПРИМЕЧАНИЕ Если стандартный ввод или вывод были переназначены при запуске сценария из командной строки, то операции ввода-вывода с помощью дескрипторов, соответствующих файлам "-" и ">-", будут осуществляться в файлы, определенные во время переназначения стандартного ввода или вывода.
Последнее, что нам хотелось бы рассмотреть в связи с дескрипторами файлов, — это создание дескриптора-дубликата. Эта возможность реализуется только при использовании функции open( ) с одним или двумя параметрами. Если в параметре имени файла используется строка, начинающаяся с любого префикса режима открытия файла, за которым следует амперсанд (&), то ее оставшаяся часть рассматривается не как имя открываемого файла, а как дескриптор уже открытого файла или числовой дескриптор файла, если она представляет числовое значение. В этом случае создается еще один дескриптор с именем, определяемым первым параметром функции open( ), ассоциированный с уже открытым файлом, то есть создается дескриптор-дубликат. Оба дескриптора имеют общий указатель текущей позиции файла, но разные буферы ввода-вывода. Закрытие одного из дескрипторов не влияет на работу другого.
Ранее мы упоминали, что невозможно восстановить ввод-вывод на стандартные устройства ввода-вывода после их переназначения в программе путем открытия файлов с дескрипторами STDIN, STDOUT или STDERR. Так вот, использование дескрипторов-дубликатов позволяет осуществить восстановление стандартных устройств ввода-вывода после их программного переназначения (листинг 6.8).
Листинг 6.8. Переназначение и восстановление стандартного вывода
#! perl -w
# Создание копии текущего дескриптора STDOUT
open(OLDSTDOUT, ">&STDOUT");
# Переназначение стандартного вывода
open(STDOUT, "> file.out") or die "Невозможно переназначить STDOUT: $!";
# Печать в файл file.out
print "Информация в переназначенный STDOUT\n":
# Закрытие переназначенного стандартного вывода
close(STDOUT) or die "Невозможно закрыть STDOUT:  $!";
# Восстановить дескриптор стандартного устройства вывода
open(STDOUT, ">&OLDSTDOUT") or die "Невозможно восстановить STDOUT: $!";
# Закрыть копию дескриптора стандартного устройства вывода 
close(OLDSTDOUT) or die "Невозможно закрыть OLDOUT:  $!";
# Печать в восстановленный файл стандартного вывода
print "Информация в восстановленный STDOUT\n";
В начале программы из листинга 6.8 создается дескриптор-дубликат на стандартное устройство вывода, которое далее переназначается на вывод в некоторый файл. Когда необходимо снова ассоциировать дескриптор STDOUT со стандартным устройством вывода, то для этих целей используется созданный в начале программы дескриптор-дубликат.
Казалось бы, зачем так мудрить, ведь после переназначения стандартного устройства вывода можно дескриптор STDOUT снова ассоциировать со стандартным устройством вывода, открыв с этим дескриптором специальный файл ">-"? Однако такая схема не сработает. После «восстановления» стандартного устройства вывода любая печать в STDOUT будет осуществлять вывод в тот файл, на который был первоначально переназначен STDOUT. В действительности операция open STDOUT, ">-" снова ассоциирует дескриптор STOUT с самим собой, то есть в конечном счете с тем файлом, на который он был ранее переназначен!
Попытка перед операцией «восстановления» STDOUT закрыть его приведет вообще к отключению печати на стандартное устройство вывода.
По завершении работы с файлом он закрывается функцией close( ). Единственным необязательным параметром этой функции является дескриптор, ассоциированный с файлом:
close ДЕСКРИПТОР;
Эта функция возвращает значение «истина», если успешно очищен буфер ввода-вывода и закрыт системный числовой дескриптор файла. Вызванная без параметра, функция close закрывает файл, связанный с текущим дескриптором, установленным функцией select( ).
Следует отметить, что закрывать файлы в программе функцией close( ) не обязательно. Дело в том, что открытие нового файла с дескриптором, уже связанным с каким-либо файлом, закрывает этот старый файл. Более того, при завершении программы все открытые в ней файлы закрываются. Однако такое неявное закрытие файлов таит в себе потенциальные ошибки из-за невозможности определить, завершилась ли эта операция корректно. Может оказаться, что при записи в файл переполнится диск или будет разорвана связь с удаленным устройством вывода. Подобные ошибки можно «отловить», если использовать явное закрытие файла и проверять содержимое специальной переменной $!:
close (FILEIO ) or die "Ошибка закрытия файла: $!";
Существует еще один нюанс, связанный с явным закрытием файлов. При чтении из файла специальная переменная $. (если ее значение не изменено явным образом в программе) хранит номер последней прочитанной записи файла. При явном закрытии файла функцией close( ) значение этой переменной обнуляется, тогда как при неявном закрытии оно остается равным номеру последней прочитанной записи старого файла и продолжает увеличиваться при операциях чтения из нового файла.

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


Реклама