| Содержание | Предисловие | Введение | Ссылки
| Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10
| Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19
| Приложение А | Приложение Б | Приложение В | Приложение Г |

Глава 10

    В этой главе:


Дескрипторы файлов и проверка файлов

Что такое дескриптор файла

Дескриптор файла в Perl-программе это имя соединения для ввода-вывода между вашим Perl-процессом и внешним миром. Мы уже видели дескрипторы файлов и пользовались ими, сами того не зная: stdin это дескриптор, которым именуется соединение между Perl-процессом и стандартным вводом UNIX. Аналогичным образом в Perl существует stdout (для стандартного вывода) и stderr (для стандартного вывода ошибок). Это те же самые имена, что и используемые библиотекой стандартного ввода-вывода в С и C++, которую Perl задействует в большинстве операций ввода-вывода.

Имена дескрипторов файлов похожи на имена помеченных блоков, но они берутся из другого пространства имен (поэтому у вас может быть скаляр $fred, массив Sfred, хеш %fred, подпрограмма sfred, метка fred, а теперь и дескриптор файла fred). Как и метки блоков, дескрипторы файлов используются без специального префиксного символа, поэтому их можно спутать с существующими или возможными в будущем зарезервированными словами (для команд, подпрограмм и др.). Рекомендуем составлять дескрипторы файлов только из прописных букв. Во-первых, они будут хорошо выделяться в тексте программы, и, во-вторых, благодаря этому программа не даст сбой при введении нового зарезервированного слова.

Открытие и закрытие дескриптора файла

В Perl есть три дескриптора файлов, stdin, stdout и stderr, которые автоматически открываются для файлов или устройств, установленных родительским процессом программы (вероятно, shell). Для открытия дополнительных дескрипторов используется функция open. Она имеет следующий синтаксис:

open(ДЕСКРИПТОР,"имя") ;

где дескриптор новый дескриптор файла, а имя имя файла (или устройства), которое будет связано с новым дескриптором. Этот вызов открывает файл для чтения. Чтобы открыть файл для записи, используйте ту же функцию open, но поставьте перед именем файла знак "больше" (как в shell):

open(OUT, ">выходной_файл");

Мы увидим, как использовать этот дескриптор, в разделе "Использование дескрипторов файлов". Как и в shell, файл можно открыть для добавления, поставив перед именем два знака "больше чем", т.е.

open (LOGFILE, " мой_фаил_регчстрации") ;

Все формы функции open в случае успешного выполнения возвращают значение "истина", а в случае неудачи "ложь". (Например, при попытке открытия файла для чтения выдается значение "ложь", если файла нет или доступ к нему запрещен; при открытии файла для вывода возвращается значение "ложь", если файл защищен от записи или если невозможна запись в каталог либо доступ к этому каталогу.)

Закончив работать с дескриптором файла, вы можете закрыть его, воспользовавшись операцией close:

close(LOGFILE) ;

Попытка повторного открытия дескриптора файла приводит к автоматическому закрытию ранее открытого файла. Это же происходит и при выходе из программы. По этой причине многие Perl-программы не обременяют себя выполнением операции close. Если же вы хотите, чтобы все было сделано аккуратно или чтобы все данные записывались на диск незадолго до завершения программы, эта операция необходима. Вызов close может закончиться неудачей, если диск переполнен, если удаленный сервер, на котором находился файл, стал недоступен либо если возникла какая-нибудь иная проблема. Рекомендуем всегда проверять возвращаемые значения всех системных вызовов.

Небольшое отступление: функция die

Считайте этот раздел большой сноской, сделанной посреди страницы. Дескриптор файла, который не удалось успешно открыть, все равно может использоваться в программе, причем без каких-либо предупреждений*. Если вы читаете данные из дескриптора файла, вы сразу же получите признак конца файла. Если вы записываете данные в дескриптор, эти данные попросту выбрасываются (как обещания участников прошлогодней избирательной кампании).

Скорее всего, вы захотите проверить результат выполнения функции open и получить сообщение об ошибке, если этот результат не оправдал ваши ожидания. Естественно, вы можете приправить свою программу разными штучками вроде

unless (open (DATAPLACE,">/tmp/dataplace") ) (

print "Sorry, I couldn't create /tmp/dataplace\n";

} else {

# остальная часть программы }

Но это очень объемная задача, и встречается она достаточно часто, поэтому в Perl для нее предусмотрено специальное сокращение. Функция die получает список, который может быть заключен в круглые скобки, выводит этот список (как это делает print) на стандартное устройство вывода ошибок, а затем завершает Perl-процесс (тот, который выполняет Perl-программу) с ненулевым кодом выхода (который, как правило, означает, что произошло нечто необычное**). Используя эту функцию, приведенный выше код можно переписать так:

unless (open DATAPLACE,">/tmp/dataplace") f

die "Sorry, I couldn't create /tmp/dataplace\n";

}

* остальная часть программы

Можно пойти еще дальше. Вспомним, что для сокращения записи можно использовать операцию | (логическое ИЛИ):

open(DATAPLACE,">/tmp/dataplace") I I

die "Sorry, I couldn't create /tmp/dataplace\n";

Таким образом, die выполняется только в том случае, если значение, получаемое в результате выполнения функции open, "ложь". Читать это нужно так: "открыть этот файл или умереть!" Это простой способ запомнить, какую логическую операцию использовать И либо ИЛИ.

* Если вы не выполняете программу с ключом -w.

** Фактически die просто генерирует исключение, но поскольку мы не показываем вам, как обрабатывать исключения, она ведет себя так, как здесь написано. Подробности см. в главе 3 книги Programming Perl (функция eval) или на man-странице perlfunc{\).

К сообщению, выдаваемому в случае "смерти" (оно строится на основе аргумента функции die) автоматически присоединяется имя Perl-программы и номер строки, поэтому вы можете легко определить, какая именно функция die несет ответственность за преждевременный выход из программы. Если же вы не хотите указывать номер строки или имя файла, поставьте в конец "предсмертного" текста символ новой строки. Например:

die "you gravy-sucking pigs";

выводит файл и номер строки, а

die "you gravy-sucking pigs\n";

не выводит.

Еще одна удобная штука внутри die-строк переменная $!, которая содержит строку с описанием самой последней ошибки операционной системы. Используется она так:

open (LOG, " logfile") || die "cannot append: $!";

Например, в результате может быть выдано сообщение " cannot append:

Permission denied".

Имеется также функция "вызова при закрытии", которую большинство пользователей знают как warn. Она делает все, что делает die, только "не умирает". Используйте ее для выдачи сообщений об ошибках на стандартный вывод:

open(LOG," log") 11 warn "discarding logfile output\n";

Использование дескрипторов файлов

После того как дескриптор файла открыт для чтения, из него можно читать строки точно так же, как со стандартного ввода stdin. Например, для чтения строк из файла паролей используется такой код:

open (ЕР,"/etc/passwd");

while (<EP>) (

chomp;

print "I saw $_ in the password file!\n";

}

Обратите внимание: вновь открытый дескриптор помещен в угловые скобки, аналогично тому как ранее мы использовали stdin.

Если вы открыли дескриптор файла для записи или добавления и хотите ввести в него что-либо (с помощью функции print), этот дескриптор нужно поставить сразу за ключевым словом print, перед остальными аргументами. Запятой между дескриптором и остальными аргументами быть не должно:

print LOGFILE "Finished item $n of $max\n";

print STDOUT "hi, world!\n"; # как print "hi, world!\n"

В этом случае сообщение, начинающееся со слова Finished, посылается в дескриптор файла logfile, который, предположительно, был открыт в программе ранее. Сообщение hi, world направляется на стандартный вывод, как и раньше, когда вы не указывали дескриптор. Мы говорим, что stdout это дескриптор файла по умолчанию для оператора print.

Предлагаем вам способ копирования данных из файла, заданного в переменной $а, в файл, указанный в переменной $Ь. Он иллюстрирует почти все, о чем мы рассказывали на последних нескольких страницах:*

open(IN,$a) || die "cannot open $a for reading: $!";

open(OUT,">$b") || die "cannot create $b: $!";

while (<IN>) { # прочитать строку из файла $а в $_

print ОПТ $_; # вывести эту строку в файл $Ь ) close(IN) || die "can't close $a: $!";

close (ОПТ) || die "can't close $b: $!";

Операции для проверки файлов

Теперь вы знаете, как открыть дескриптор файла для вывода, уничтожив существующий файл с таким же именем. Предположим, вы хотите удостовериться, что файла с таким именем не существует (чтобы избежать случайного уничтожения своей электронной таблицы или очень важного календаря дней рождений). Если бы вы писали сценарий shell, вы использовали бы для проверки существования файла нечто вроде -е имя_фаила. Аналогичным образом в Perl применяется операция -е $filevar, которая проверяет факт существования файла, заданного в скалярной переменной $filevar. Если этот файл существует, результат "истина"; в противном случае операция дает "ложь"**. Например:

$name = "index.html";

if (-e $name) (

print "I see you already have a file named $name\n";

} else (

print "Perhaps you'd like to make a file called $name\n";

}

* Хотя при наличии модуля File:: Copy этот способ оказывается лишним.

** Это не совсем хорошо, если вы работаете с lock-файлами или если файлы часто появляются и исчезают. В этом случае вам нужно обратиться к функциям sysopen и flock, которые описаны в книге Programming Perl, или изучить примеры, приведенные в главе 19.

Операнд операции -е любое скалярное выражение, вычисление которого дает некоторую строку, включая строковый литерал. Вот пример, в котором проверяется наличие файлов index-html и index.cgi в текущем каталоге:

if (-е "index.html" && "index.cgi") (

print "You have both styles of index files here.\n";

1

Существуют и другие операции. Например, -r $filevar возвращает значение "истина", если заданный в $filevar файл существует и может быть прочитан. Операция -w $filevar проверяет возможность записи в файл. В следующем примере файл с заданным пользователем именем проверяется на возможность чтения и записи:

print "where? ";

$filename <STDIN>;

chomp $filename; # выбросить этот надоедливый символ новой строки if (-r $filename &S -w $filename) (

# файл существует, я могу читать его и записывать в него

}

Есть много других операций для проверки файлов. Полный перечень их приведен в таблице 10.1.

Таблица 10.1. Операции для проверки файлов и их описание

Обозначение

Описание

-r

Файл или каталог доступен для чтения

-w

Файл или каталог доступен для записи

-X

Файл или каталог доступен для выполнения

Файл или каталог принадлежит владельцу

-R

Файл или каталог доступен для чтения реальным пользователем, но не "эффективным" пользователем (отличается от -r для программ с установленным битом смены идентификатора пользователя)

-W

Файл или каталог доступен для записи реальным пользователем, но не "эффективным" пользователем (отличается от -w для программ с установленным битом смены идентификатора пользователя)

-X

Файл или каталог доступен для выполнения реальным пользователем, но не "эффективным" пользователем (отличается от -х для программ с установленным битом смены идентификатора пользователя)

-0

Файл или каталог принадлежит реальному пользователю, но не "эффективному"пользователю (отличается от -о для программ с установленным битом смены идентификатора пользователя)

Обозначение

Описание

Файл или каталог существует

-2

Файл существует и имеет нулевой размер (каталоги пустыми не бывают)

-s

Файл или каталог существует и имеет ненулевой размер (значение размер в байтах)

-f

Данный элемент обычный файл

-d

Данный элемент каталог

-1

Данный элемент символическая ссылка

-S

Данный элемент порт

-P

Данный элемент именованный канал (FIFO-файл)

-b

Данный элемент блок-ориентированный файл (например, монтируемый диск)

Данный элемент байт-ориентированный файл (например, файл устройства ввода-вывода)

-u

У файла или каталога установлен идентификатор пользо

 

 

вателя

-g

У файла или каталога установлен идентификатор группы

-k

У файла или каталога установлен бит-липучка

-t

Выполнение операции isatty() над дескриптором файла дало значение "истина"

-T

Файл текстовый

-B

Файл двоичный

-M

Время с момента последнего изменения (в днях)

-A

Время с момента последнего доступа (в днях)

-C

Время с момента последнего изменения индексного дескриптора (в днях)

 

Большинство этих проверок возвращает просто значение "истина" или "ложь". О тех, которые этого не делают, мы сейчас поговорим.

Операция -s возвращает значение "истина", если файл непустой, но это значение особого вида. Это длина файла в байтах, которая интерпретируется как "истина" при ненулевом значении.

Операции -м, -а и -с (да-да, в верхнем регистре) возвращают количество дней соответственно с момента последнего изменения файла, доступа к нему и изменения его индексного дескриптора*. (Индексный дескриптор содержит всю информацию о файле; подробности см. на man-странице, посвященной системному вызову stat.) Возвращаемое значение десятичное число,

* Эти значения определяются относительно времени запуска программы, занесенного в системном формате времени в переменную $ "т. Если запрашиваемое значение относится к событию, которое произошло после начала работы программы, оно может быть отрицательным.

соответствующее прошедшему времени с точностью до 1 секунды: 36 часов возвращается как 1,5 дня. Если при отборе файлов будет выполнено сравнение этого показателя с целым числом (например, с 3), то вы получите только те файлы, которые были изменены ровно столько дней назад, ни секундой раньше и ни секундой позже. Это значит, что для получения всех файлов, значение определенного показателя для которых находится в диапазоне от трех до четырех дней, вам, вероятно, нужно будет использовать операцию сравнения диапазонов*, а не операцию сравнения значений.

Эти операции могут работать не только с именами файлов, но и с дескрипторами. Для этого нужно просто указать в качестве операнда дескриптор файла. Так, чтобы проверить, доступен ли для выполнения файл, открытый как somefile, можно сделать следующее:

if (-х SOMEFILE) (

# файл, открытый как SOMEFILE, доступен для выполнения

}

Если имя или дескриптор файла не указаны (т.е. даны только операции

*г или -s), то по умолчанию в качестве операнда берется файл, указанный в переменной $_ (опять эта переменная!). Так, чтобы проверить список имен файлов и установить, какие из них доступны для чтения, нужно просто-напросто написать следующее:

foreach (@some_list_of_filenames) (

print "5_ is readable\n" if -r; # то же, что и -г $_ >

Функции stat и Istat

Вышеупомянутые операции весьма эффективны при проверке различных атрибутов конкретного файла или дескриптора файла, но полную информацию с их помощью получить нельзя. Например, не предусмотрена операция проверки, которая возвращала бы число ссылок на файл. Чтобы добраться до остальных сведений о файле, вызовите функцию stat, которая возвращает практически все, что возвращает системный вызов stat в POSIX (надеемся, мы сказали больше, чем вы хотите знать).

Операнд функции stat дескриптор файла или выражение, посредством которого определяется имя файла. Возвращаемое значение либо undef, если вызов неудачен, либо 13-элементный список**, который легче всего описать с помощью такого списка скалярных переменных:

($dev,$ino,$mode,$nlink,$uid,$gid,$rdev, $size, $atinie, $mtime, $ctime, $blksize,$blocks) = stat (. . .)

* Или операцию int.

** Если вам тяжело запомнить порядок значений, возвращаемых функцией stat, можете обратиться к модулю File: :stat, впервые введенному в выпуске 5.004. Он обеспечивает доступ к этим значениям следующим образом:

$file_owner = stat($filename)->uid

Имена здесь соответствуют частям структуры stat, подробно описанной на man-странице stat(T). Рекомендуем изучить приведенные там подробные пояснения.

Например, чтобы получить идентификаторы пользователя и группы из файла паролей, нужно записать:

($uid,,$gid) = (stat("/etc/passwd")) [4,5];

и этого окажется достаточно.

Вызов функции stat с именем символической ссылки возвращает информацию о том, на что указывает эта ссылка, а не сведения о самой ссылке (если только она не указывает на что-то в текущий момент недоступное). Если вам нужна информация о самой символической ссылке (большей частью бесполезная), используйте вместо stat функцию istat (которая возвращает те же данные в том же порядке). С элементами, которые не являются символическими ссылками, функция istat работает аналогично stat.

Как и в операциях проверки файлов, операнд функций stat и Istat по умолчанию $_. Это значит, что операция stat будет выполняться над файлом, заданным скалярной переменной $_.

Упражнения

Ответы приведены в приложении А.

1. Напишите программу чтения имени файла из stdin, открытия этого файла и выдачи его содержимого с предварением каждой строки именем файла и двоеточием. Например, если считано имя fred, а файл fred состоял из трех строк, a a a, bbb и с с с, вы должны увидеть fred: а а а, fred: bbb И fred: ссс.

2. Напишите программу, которая приглашает ввести имя входного файла, имя выходного файла, образец поиска и заменяющую строку, после чего копирует входной файл в выходной, заменяя все экземпляры образца этой строкой. Попробуйте выполнить эту программу с несколькими файлами. Можете ли вы заменить существующий файл (не проводите эту операцию над чем-то важным!)? Можете ли вы использовать символы регулярных выражений в искомой строке? Можете ли вы использовать $1 в заменяющей строке?

3. Напишите программу чтения списка имен файлов и выдачи информации о том, какие файлы доступны для чтения, для записи и (или) для выполнения, а какие файлы не существуют. (Каждую проверку можно выполнять для каждого имени файла по мере их чтения или для всей совокупности имен после прочтения их всех. Не забудьте удалять символ новой строки в конце каждого прочитанного имени файла.)

4. Напишите программу чтения списка имен файлов и поиска среди них самого старого. Выведите на экран имя файла и его возраст в днях.




|     Назад     |     Вперед     |


| Содержание | Предисловие | Введение | Ссылки
| Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10
| Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19
| Приложение А | Приложение Б | Приложение В | Приложение Г |