Открытие и закрытие файла
Для того чтобы получить в программе доступ к информации, содержащейся в файле, следует прежде всего
создать дескриптор файла, который в дальнейшем будет являться «представителем» этого файла в различных
операциях извлечения из него и записи в него информации. Но до того как можно будет осуществлять различные
манипуляции с содержимым файла, он должен быть открыт, а после завершения работы с ним — закрыт.
Обычно во всех языках программирования дескриптор файла (в других языках могут использоваться иные
термины для механизма ссылки на файл, который мы в 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( ) значение этой переменной обнуляется, тогда как
при неявном закрытии оно остается равным номеру последней прочитанной записи старого файла и продолжает
увеличиваться при операциях чтения из нового файла.