ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП |
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы. |
Доступ к файламВведениеФайлы занимают центральное место в обработке данных. Как и во всем остальном в Perl, простые операции с файлами выполняются просто, а сложные... как-нибудь да выполняются. Стандартные задачи (открытие файлов, чтение данных, запись данных) используют простые функции ввода/вывода и операторы, а более экзотические функции способны даже на асинхронный ввод/вывод и блокировку (locking) файлов. В этой главе рассматривается механика доступа к файлам: открытие файлов, передача сведений о том, с какими файлами вы собираетесь работать, блокировка и т. д. Глава 8 «Содержимое файлов» посвящена работе с содержимым файлов: чтению, записи, перестановке строк и другим операциям, которые становятся возможными после получения доступа к файлу. Следующий фрагмент выводит все строки файла /usr/local/widgets/data, содержащие слово "blue":open (INPUT, "< /usr/local/widgets/data") or die "Couldn't open /usr/local/widgets/data for reading: $!\n"; while (INPUT) { print if /blue/, } close(INPUT); Получение файлового манипулятора Доступ к файлам в Perl организуется при помощи файловых манипуляторов (filehandle) - таких, как INPUT из предыдущего примера. Манипулятор - это символическое имя, которое представляет файл в операциях чтения/записи. Файло- вые манипуляторы не являются переменными. В их именах отсутствуют префиксы $, @ или %, однако они наряду с функциями и переменными попадают в символьную таблицу Perl. По этой причине не всегда удается сохранить файловый манипулятор в переменной или передать его функции. Приходится использовать префикс *, который является признаком тип-глоба - базовой единицы символьной таблицы Perl: $var = *STDIN; my sub($var, *LOGFILE); Файловые манипуляторы, сохраняемые в переменных подобным образом, не используются напрямую. Они называются косвенными файловыми манипуляторами (indirect filehandle), поскольку косвенно ссылаются на настоящие манипуляторы. Два модуля, IO::File (стал стандартным, начиная с версии 5.004) и FileHandle (стандартный с версии 5.000), могут создавать анонимные файловые манипуляторы. Когда в наших примерах используются модули IO::File или IO::Handle, аналогичные результаты можно получить с применением модуля FileHandle, поскольку сейчас он является интерфейсным модулем (wrapper). Ниже показано, как выглядит программа для поиска "blue" с применением модуля IO::File в чисто объектной записи: use 10::File: $input = 10::File->new("< /usr/local/widgets/data") or die "Couldn't open /usr/local/widgets/data for reading: $!\n": while (defined($line = $input->getline())) { chomp($line); STDOUT->print($line) if $line =~ /blue/; } $input->close(); Как видите, без прямого использования файловых манипуляторов программа
читается намного легче. Кроме того, она гораздо быстрее работает. Но поделимся
одним секретом: из этой программы можно выкинуть все стрелки и вызовы
методов. В отличие от большинства объектов, объекты IO::File не обязательно
использовать объектно-ориентированным способом. В сущности, они представляют
собой анонимные файловые манипуляторы и потому могут использоваться везде,
где допускаются обычные косвенные манипуляторы. В рецепте 7.16 рассматриваются
эти модули и префикс *. Модуль IO::File и символические файловые манипуляторы
неоднократно встречаются в этой главе. Стандартные файловые манипуляторы
Каждая программа при запуске получает три открытых глобальных файловых
манипулятора: STDIN, STDOUT и STDERR. STDIN {стандартный ввод) является
источником входных данных по умолчанию. В STDOUT {стандартный вывод) по
умолчанию направляются выходные данные. В STDERR {стандартный поток ошибок)
по умолчанию направляются предупреждения и ошибки. В интер- активных программах
STDIN соответствует клавиатуре, a STDOUT и STDERR - экрану монитора:
open(LOGFILE, "> /tmp/log") or die "Can't write /tmp/log: $!";
7.1. Открытие файлаПроблемаИзвестно имя файла. Требуется открыть его для чтения или записи в Perl.РешениеФункция open отличается удобством, sysopen - точностью, а модуль IO::File позволяет работать с анонимным файловым манипулятором. Функция open получает два аргумента: открываемый файловый манипулятор и строку с именем файла и специальными символами, определяющими режим открытия:open(SOURCE, "< $path") or die "Couldn't open $path for reading: $!\n"; open(SINK, "> $path") or die "Couldn't open $path for writing: $!\n"; где SOURCE - файловый манипулятор для ввода, a SINK - для вывода. Функции sysopen передаются три или четыре аргумента: файловый манипулятор, имя файла, режим и необязательный параметр, определяющий права доступа. Режим представляет собой число, конструируемое из констант модуля Fcnti: use Fcnti; sysopen(SOURCE, $path, O.RDONLY) or die "Couldn't open $path for reading: $!\n"; sysopen(SINK, $path, 0_WRONLY) or die "Couldn't open $path for writing: $!\n"; Аргументы метода new модуля IO::File могут задаваться в стиле как open, так и sysopen. Метод возвращает анонимный файловый манипулятор. Кроме того, также возможно задание режима открытия в стиле fopen(3): use 10::File; # По аналогии с open $sink = 10::File->new("> $filename") or die "Couldn't open $filename for writing: $!\n"; # По аналогии с sysopen $fh = 10::File->new($filename, 0_WRONLY|0_CREAT) or die "Couldn't open $filename for reading: $!\n"; # По аналогии с fopen(3) библиотеки stdio $fh = 10::File->new($filename, "r+") or die "Couldn't open $filename for read and write: $!\n"; КомментарийВсе операции ввода/вывода осуществляются через файловые манипуляторы независимо от того, упоминаются манипуляторы в программе или нет. Файловые манипуляторы не всегда связаны с конкретными файлами - они также применяются для взаимодействия с другими программами (см. главу 16 «Управление процессами и межпроцессные взаимодействия») и в сетевых коммуникациях (см. главу 17 «Сокеты»). Функция open также применяется для работы с файловыми дескрипторами, данная возможность рассматривается в рецепте 7.19. Функция open позволяет быстро и удобно связать файловый манипулятор с файлом. Вместе с именем файла передаются сокращенные обозначения стандартных режимов (чтение, запись, чтение/запись, присоединение). Функция не позволяет задать права доступа для создаваемых файлов и вообще решить, нужно ли создавать файл. Если вам потребуются подобные возможности, воспользуйтесь функцией sysopen, которая использует константы модуля Fcnti для управления отдельными компонентами режима (чтение, запись, создание и усечение). Большинство программистов начинает работать с open задолго до первого использования sysopen. В таблице показано соответствие между режимами функции open («Файл»), константами sysopen («Флаги») и строками fopen(3), передаваемыми 10: :File->new («Символы»). Столбцы «Чтение» и «Запись» показывают, возможно ли чтение или запись для данного файлового манипулятора. «Присоединение» означает, что выходные данные всегда направляются в конец файла независимо от текущей позиции (в большинстве систем). В режиме усечения функция open уничтожает все существующие данные в открываемом файле.
open(FH, "< $path") or die$!;
Смотри также --------------------------------
7.2. Открытие файлов с нестандартными именамиПроблемаТребуется открыть файл с нестандартным именем - например, "-"; начинающимся с символа <, > или |; содержащим начальные или конечные пропуски; закапчивающимся символом |. Функция open не должна принимать эти функции за служебные, поскольку вам нужно совершенно иное.РешениеВыполните предварительное преобразование:$filename =~ s#"(\s)#./$1#; open(HANDLE, "< $filename\0") or die "cannot open $filename : $!\n": Или просто воспользуйтесь функцией sysopen: sysopen(HANDLE, $filename, 0_RDONLY) or die "cannot open $filename : $!\n"; КомментарийФункция open определяет имя файла и режим открытия по одному строковому аргументу. Если имя файла начинается с символа, обозначающего один из режимов, open вполне может сделать что-нибудь неожиданное. Рассмотрим следующий фрагмент:$filename = shift @ARGV; open(INPUT, $filename) or die "cannot open $filename : $!\n": Если пользователь указывает в командной строке файл ">/etc/passwd", программа попытается открыть /etc/passwd для записи - со всеми вытекающими последствиями! Режим можно задать и явно (например, для записи): open(OUTPUT, ">$filename") or die "Couldn't open $filename for writing: $!\n"; но даже в этом случае пользователь может ввести имя ">data", после чего программа будет дописывать данные в конец файла data вместо того, чтобы стереть прежнее содержимое. Самое простое решение - воспользоваться функцией sysopen, у которой режим и имя файла передаются в разных аргументах: use Fcnti; # Для файловых констант sysopen(OUTPUT, $filename, 0_WRONLY|0_TRUNC) or die "Couldn't open $filename for writing: $!\n"; А вот как добиться того же эффекта с функцией open для имен файлов, содержащих начальные или конечные пропуски: $file =~ зГ(\з)#./$1#; open(HANDLE, "> $file\0") or die "Could't open $file for OUTPUT : $!\n"; Такая подстановка защищает исходные пропуски, но не в абсолютных именах типа " /etc/passwd", а лишь в относительных (" passwd"). Функция open не считает нуль-байт ("\0") частью имени файла, но благодаря ему не игнорируются конечные пропуски. Волшебная интерпретация файловых имен в функции open почти всегда оказывается удобной. Вам никогда не приходится обозначать ввод или вывод с помощью особой формы "-". Если написать фильтр и воспользоваться простой функцией open, пользователь сможет передать вместо имени (})айла строку "gzip -de bible. gz |" - фильтр автоматически запустит программу распаковки. Вопросы безопасности open актуальны лишь для программ, работающих в особых условиях. Если программа должна работать под управлением чего-то другого - например, сценариев CGI или со сменой идентификатора пользователя, - добросовестный программист всегда учтет возможность ввода пользователем собственного имени файла, при котором вызов open для простого чтения превратится в перезапись 4)айла или даже запуск другой программы. Параметр командной строки Perl -Т обеспечивает проверку ошибок. > Смотри также -------------------------------
7.3. Тильды в именах файловПроблемаИмя файла начинается с тильды (например, -usemame/blah), однако функция open не интерпретирует его как обозначение домашнего каталога (home directory).РешениеВыполните ручное расширение с помощью следующей подстановки:$filename =- s{ ~ ~ ( ["/]* ) } { $1 ? (getpwnam($1))[7] : ( $ENV{HOME} || $ENV{LOGDIR} | (getpwuid($>))[7] ) }ех; КомментарийНас интересуют следующие применения тильды:-user -user/blah -/blah где user - имя пользователя. Если ~ не сопровождается никаким именем, используется домашний каталог текущего пользователя. В данной подстановке использован параметр /е, чтобы заменяющее выражение интерпретировалось как программный код Perl. Если за тильдой указано имя пользователя, оно сохраняется в $1 и используется getpwnam для выбора домашнего каталога пользователя из возвращаемого списка. Найденный каталог образует заменяющую строку. Если за тильдой не указано имя пользователя, подставляется либо текущее значение переменной окружения HOME или LOGDIR. Если эти переменные не определены, задается домашний каталог текущего пользователя. > Смотри также
7.4. Имена файлов в сообщениях об ошибкахПроблемаПрограмма работает с файлами, однако в предупреждения и сообщения об ошибках Perl включается только последний использованный файловый манипулятор, а не имя файла.РешениеВоспользуйтесь именем файла вместо манипулятора:open($path, "< $path") or die "Couldn't open $path for reading : $!\n"; КомментарийСтандартное сообщение об ошибке выглядит так: Argument "3\n" isn't numeric in multiply at tallyweb line 16,> Смотри также -------------------------------
7.5. Создание временных файловПроблемаТребуется создать временный файл и автоматически удалить его при завершении программы. Допустим, вы хотите записать временный конфигурационный файл, который будет передаваться запускаемой программе. Его имя должно быть известно заранее. В других ситуациях нужен временный файл для чтения и записи данных, причем его имя вас не интересует.РешениеЕсли имя файла не существенно, воспользуйтесь методом класса new_tmpfile модуля IO::File для получения файлового манипулятора, открытого для чтения и записи:use 10::File; $fh = 10::File->new_tmpfile or die "Unable to make new temporary file: $!"; Если имя файла должно быть известно, получите его функцией tmpnam из модуля POSIX и откройте файл самостоятельно: use 10::File; use POSIX qw(tmpnam); # Пытаться получить временное имя файла до тех пор, # пока не будет найдено несуществующее имя do { $name = tmpnam() } until $fh = 10::File->new($name, 0_ROWR|0_CREAT|0_EXCL); # Установить обработчик, который удаляет временный файл tt при нормальном или аварийном завершении программы END { unlink($name) or die "Couldn't unlink $name : $!" } # Перейти к использованию файла.... КомментарийЕсли все, что вам нужно, - область для временного хранения данных, воспользуйтесь методом new_tmpfile модуля IO::File. Он возвращает файловый манипулятор для временного файла, открытого в режиме чтения/записи фрагментом следующего вида:for (;;) { $name = tmpnam(); sysopen(TMP, $tmpnam, 0_RDWR | 0_CREAT | 0_EXC) && last; } unlink $tmpnam; Файл автоматически удаляется при нормальном или аварийном завершении программы. Вам не удастся определить имя файла и передать другому процессу, потому что у него нет имени. В системах с поддержкой подобной семантики имя удаляется еще до завершения метода. Впрочем, открытый файловый манипулятор может наследоваться производными процессами'. Ниже показан пример практического применения new_tmpfile. Мы создаем временный файл, выполняем запись, возвращаемся к началу и выводим записанные данные: use 10::File; $fh = 10::File->new_tmpfile or die "10::File->new_tmpfile: $!"; $fh->autorlush(1); print( $fh "$i\n" while $i++ < 10; seek($fh, 0, 0); print "Tmp file has: ", <$rh>; Во втором варианте создается временный файл, имя которого можно передать другому процессу. Мы вызываем функцию POSIX: :tmpnam, самостоятельно открываем файл и удаляем его после завершения работы. Перед открытием файла мы не проверяем, существует ли файл с таким именем, поскольку при этом может произойти подмена - кто-нибудь создаст файл между проверкой и созданием2. Вместо этого tmpnam вызывается в цикле, что гарантирует создание нового файла и предотвращает случайное удаление существующих файлов. Теоретически метод new_tmpfile не должен возвращать одинаковые имена разным процессам. > Смотри также --------------------------------
7.6. Хранение данных в тексте программыПроблемаНекоторые данные должны распространяться вместе с программой и интерпретироваться как файл, но при этом они не должны находиться в отдельном файле.РешениеЛексемы __DATA__ и __END__ после исходного текста программы отмечают начало блока данных, который может быть прочитан программой или модулем через файловый манипулятор DATA. В модулях используется лексема __DATA__:while () { # Обработать строку } „DATA__ и Данные Аналогично используется __END__ в главном файле программы: while ( # Обработать строку } -END__ # Данные КомментарийЛексемы __DATA__ и __END__ обозначают логическое завершение модуля или сценария перед физическим концом файла. Текст, находящийся после __ОАТА__ или __END__, может быть прочитан через файловый манипулятор DATA уровня пакета. Предположим, у нас имеется гипотетический модуль Primes; текст после __DATA__ в файле Primes.pm может быть прочитан через файловый манипулятор Primes::DATA. Лексема __END__ представляет собой синоним __DATA__ в главном пакете. Текст, следующий после лексем __END__ в модулях, недоступен. Появляется возможность отказаться от хранения данных в отдельном файле и перейти к построению автономных программ. Такая возможность нередко используется для документирования. Иногда в программах хранятся конфигурационные или старые тестовые данные, использованные при разработке программ, - они могут пригодиться в процессе отладки. Манипулятор DATA также применяется для определения размера или даты последней модификации текущей программы или модуля. В большинстве систем переменная $0 содержит полное имя файла для работающего сценария. В тех системах, где значение $0 оказывается неверным, можно воспользоваться манипулятором DATA для определения размера, даты модификации и т. д. Вставьте в конец файла специальную лексему __DATA__ (и предупреждение о том, что __DATA__ не следует удалять), и файловый манипулятор DATA будет связан с файлом сценария.use POSIX qw(strftime); $raw_time = (stat(DATA))[9]; $size = -s DATA; $kilosize = int($size / 1024) . 'k'; print " Script size is $kilosize\n";
Last script update: %c (%Z)\n", localtime($raw_time));
|