Приглашаем посетить
Чернышевский (chernyshevskiy.lit-info.ru)

Каталоги

Назад
Глава 9 Каталоги
Вперед

Каталоги

Введение

Для полноценного понимания работы с каталогами необходимо понимать механизмы, заложенные в ее основу. Наш материал ориентирован на файловую систему UNIX, поскольку функции каталогов Perl разрабатывались для системных функций и особенностей именно этой системы, однако в определенной степени он относится и к большинству других платформ. Файловая система состоит из двух компонентов: набора блоков данных, где хранится содержимое файлов и каталогов, и индекса к этим блокам. Каждому объекту файловой системы, будь то обычный файл, каталог, ссылка или специальный файл (вроде файлов из каталога /deu), соответствует определенный элемент индекса. Элементы индекса называются индексными узлами (inode). Поскольку индекс является одномерным, индексные узлы определяются по номерам. Каталог представляет собой файл специального формата, помеченный в индексном узле как каталог. Блоки данных каталога содержат множество пар. Каждая пара содержит имя объекта каталога и соответствующий ему индексный узел. Блоки данных каталога /usr/Ып могут содержать следующую информацию:
Имя      Индексный узел
bc        17
du        du 29
nvi        8
pine       55
vi       8
Подобную структуру имеют все каталоги, включая корневой (/). Чтобы прочитать файл /usr/bin/vi, операционная система читает индексный узел /, находит в его блоках данных информацию о /usr, читает индексный узел /usr, находит в его блоках данных информацию о /usr/bin, читает индексный узел/usr/bin, находит в его блоках данных информацию о /usr/bin/vi, читает индексный узел /usr/bin/vi, после чего читает данные из блока данных. Имена, хранящиеся в каталогах, не являются полными. Файл /usr/bin/vi хранится в каталоге /usr/bin под именем vi. Если открыть каталог /usr/bin и последовательно читать его элементы, вы увидите имена файлов (patch, login и vi) вместо полных имен /usr/bin/patch, /usr/bin/rlogin и /usr/bin/vi. Однако индексный узел - больше, чем просто указатель на блоки данных. Каждый индексный узел также содержит информацию о типе представляемого объекта (каталог, обычный файл и т. д.) и его размере, набор битов доступа, информацию о владельце и группе, время последней модификации объекта, количество элементов каталога, ссылающихся на данный узел, и т. д. Одни операции с файлами изменяют содержимое блоков данных файла; другие ограничиваются изменением индексного узла. Например, при дополнении или усечении файла в его индексном узле изменяется информация о размере. Некоторые операции изменяют элемент каталога, содержащий ссылку на индексный узел файла. Изменение имени файла влияет только на элемент каталога; ни данные файла, ни его индексный узел не изменяются. В трех полях структуры индексного узла хранится время последнего обращения, изменения и модификации: atime, ctime и mtime. Поле atime обновляется при каждом чтении данных файла через указатель на его блоки данных. Поле mtime обновляется при каждом изменении содержимого файла. Поле ctime обновляется при каждом изменении индексного узла файла. Ctime не является временем создания; в стандартных версиях UNIX время создания файла определить невозможно. При чтении файла изменяется только значение atime. Переименование файла не отражается на atime, ctime или mtime, поскольку изменяется лишь элемент каталога (хотя при этом меняются atime и mtime для каталога, в котором находится файл). Усечение файла не влияет на atime (поскольку мы не читаем, а лишь изменяем поле размера в элементе каталога), но изменяет ctime (из-за изменения поля размера) и mtime (из-за изменения содержимого, хотя бы и косвенного). Чтобы получить индексный узел по имени файла или каталога, можно воспользоваться встроенной функцией stat. Например, индексный узел файла /usr/Ып/п может быть получен следующим образом:
@entry = stat("/usr/bin/vi") or die "Couldn't stat /usr/bin/vi : $!";

Следующий фрагмент получает индексный узел для каталога /usr/bin:
@entry = stat("/usr/bin") or die "Couldn't stat /usr/bin : $!";

Функция stat также вызывается и для файловых манипуляторов:
(Sentry = stat(INFILE) nr die "Couldn't stat INFILE : $'":

Функция stat возвращает список значений, хранящихся в полях элемента каталога. Если получить информацию не удалось (например, если файл не существует), функция возвращает пустой список. В приведенных примерах пустой список проверялся конструкцией or die. He путайте с конструкцией 11 die, поскольку выражение будет преобразовано в скалярный контекст и функция stat сообщит лишь о том, успешно ли она была вызвана. Список при этом не возвращается. Впрочем, кэш _ (см. ниже) все же будет обновлен. Элементы списка, возвращаемые функцией stat, перечислены в следующей таблице.

Элемент

Обозначение

Описание

0

dev

Номер устройства в файловой системе

1

ino

Номер индексного узла

2

mode

Режим файла (тип и права доступа)

3

nlink

Количество (прямых) ссылок на файл

4

uid

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

 

 

 

 

файла

5

gid

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

6

rdev

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

 

 

 

 

для специальных файлов)

7

size

Общий размер файла в байтах

8

atime

Время последнего обращения (в секундах с начала

 

 

 

 

эпохи)

9

mtime

Время последней модификации (в секундах с начала

 

 

 

 

эпохи)

10

ctime

Время изменения индексного узла (в секундах с начала

 

 

 

 

эпохи)

11

biksize

Предпочтительный размер блока для операций

 

 

 

 

ввода/вывода в файловой системе

12

blocks

Фактическое количество выделенных блоков

 

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

use File::stat;

$inode = stat("/usr/bin/vi");

$ctime = $inode->ctime;

$size = $inode->size;

Кроме того, в Perl предусмотрен набор операторов, вызывающих функцию sta:

и возвращающих лишь один атрибут. Эти операторы совокупно называются операторами -X , поскольку их имена состоят из дефиса, за которым следует один символ. Они построены по образцу операторов test командного интерпре татора.


326

-X

Глава 9 Поле

Каталоги stat Значение

mode

 

 

Файл может читаться текущими UID/GID

-w

mode

 

 

Файл может записываться текущими UID/GID

-X

mode

 

 

Файл может исполняться текущими UID/GID

mode

 

 

Владельцем файла является текущий UID

-R

mode

 

 

Файл может читаться фактическими UID/GID

-W

mode

 

 

Файл может записываться фактическими UID/GID

-X

mode

 

 

Файл может исполняться фактическими UID/GID

-0

mode

 

 

Владельцем файла является фактический UID

 

 

 

 

Файл существует

-z

size

 

 

Размер файла равен нулю

-s

size

 

 

Размер файла отличен от нуля (возвращает размер)

-f

mode,

rdev

Файл является обычным файлом

-d

mode,

rdev

Файл является каталогом

-1

mode

 

 

Файл является символической ссылкой

-P

mode

 

 

Файл является именованным каналом (FIFO)

-S

mode

 

 

Файл является сокетом

-b

rdev

 

 

Файл является блочным специальным файлом

-c

rdev

 

 

Файл является символьным специальным файлом

-t

rdev

 

 

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

-u

mode

 

 

У файла установлен бит setuid

-9

mode

 

 

У файла установлен бит setgid

-k

mode

 

 

У файла установлен бит запрета

-T

 

 

 

 

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

-B

-

 

 

Файл является двоичным (противоположность -Т)

-M

mtime

 

 

Возраст файла в днях на момент запуска сценария

-A

atime

 

 

То же для времени последнего обращения


Функция stat и операторы -X кэшируют значения, полученные при вызове сис темной функции stat(2). Если stat или оператор -X вызывается для специального файлового манипулятора _ (один символ подчеркивания), то вместо повторного вызова stat будет использована информация, хранящаяся в кэше. Это позволяет проверять различные атрибуты файла без многократного вызова stat(2) или воз никновения опасности перехвата:

open( F, "< $filename" )
or die "Opening $filename: $!\n";
unless (-s F && -Т _) {
die "$filename doesn't have text in it.\n";
}

Однако отдельный вызов stat возвращает информацию лишь об одном индекс ном узле. Как же получить список содержимого каталога? Для этой цели в Perl предусмотрены функции opendir, readdir и closed! г:
opendir(DIRHANDLE, "/usr/bin") or die "couldn't open /usr/bin : $!";
while ( defined ($filename = readdir(dirhandle)) ) {
print "Inside /usr/bin is something called $filename\n";
} closedir(DIRHANDLE);

Функции чтения каталога намеренно разрабатывались по аналогии с функци ями открытия и закрытия файлов. Однако если функция open вызывается для ма нипулятора файла, то opendir получает манипулятор каталога. Внешне они похо жи, но работают по-разному: в программе могут соседствовать вызовы open (BIN, "/a/file") и opendir(BIN, "/a/dir"), и Perl не запутается. Вы - возможно, но Perl точно не запутается. Поскольку манипуляторы файлов отличаются от манипуля торов каталогов, вы не сможете использовать оператор о для чтения из манипу лятора каталога. Имена файлов в каталоге не обязательно хранятся в алфавитном порядке. Что бы получить алфавитный список файлов, прочитайте все содержимое каталога и отсортируйте его самостоятельно. Отделение информации каталога от информации индексного узла может быть связано с некоторыми странностями. Операции, изменяющие каталог, требуют права записи для каталога, но не для файла. Большинство операций, изменяющих содержимое файла, требует права записи в файл. Операции, изменяющие права доступа к файлу, требуют, чтобы вызов осуществлялся владельцем файла или привилегированным пользователем. Могут возникнуть странные ситуации - на пример, появляется возможность удаления файла, который нельзя прочитать, или записи в файл, который нельзя удалить. Хотя из-за подобных ситуаций файловая система на первый взгляд кажется нелогичной, в действительности они способствуют широте возможностей UNIX. Реализация ссылок (два имени, ссылающиеся на один файл) становится чрезвы чайно простой - в двух элементах каталога просто указывается один номер ин дексного узла. Структура индексного узла содержит количество элементов ката лога, ссылающихся на данный файл (n link в списке значений, возвращаемых stat), что позволяет операционной системе хранить и поддерживать лишь одну копию времени модификации, размера и других атрибутов файла. При уничто жении ссылки на элемент каталога блоки данных удаляются лишь в том случае, если это была последняя ссылка для индексного узла данного файла, а сам файл не остается открытым ни в одном процессе. Можно вызвать unlink и для откры того файла, но дисковое пространство будет освобождено лишь после его закры тия последним процессом. Ссылки делятся на два типа. Тип, описанный выше (два элемента каталога, в которых указан один номер индексного узла), называется прямой (или жесткой) ссылкой (hard link). Операционная система не может отличить первый элемент каталога, соответствующий файлу (созданный при создании файла), от всех по следующих ссылок на него. Со ссылками другого типа - символическими ссылка ми - дело обстоит совершенно иначе. Символические ссылки представляют со бой файлы особого типа: в блоке данных хранится имя файла, на который указывает ссылка. Символические ссылки имеют особое значение mode, отличающее их от обычных файлов. При вызове open для символической ссылки операционная сис тема открывает файл, имя которого указано в блоке данных.

Резюме

Имена файлов хранятся в каталогах отдельно от размера, атрибутов защиты и прочих метаданных, хранящихся в индексном узле. Функция stat возвращает информацию индексного узла (метаданные). Функции opendir, readdir и их спутники обеспечивают доступ к именам фай лов в каталоге с помощью манипулятора каталога. Манипулятор каталога похож на файловый манипулятор, но не идентичен ему. В частности, для манипулятора каталога нельзя вызвать о. Права доступа к каталогу определяют, можете ли вы прочитать или записать список имен файлов. Права доступа к файлу определяют, можете ли вы изменить метаданные или содержимое файла. В индексном узле хранятся три атрибута времени. Ни один из них не опреде ляет время создания файла.

9.1. Получение и установка атрибутов времени

Проблема

Требуется получить или изменить время последней модификации (записи или изменения) или обращения (чтения) для файла.

Решение

Функция stat получает атрибуты времени, а функция utime устанавливает их зна-чения. Обе функции являются встроенными в Perl:

($READTIME, $WRITETIME) = (stat($filename))[8,9];
utime($NEWREADTIME, $NEWWRITETIME, $filename);

Комментарий

Как говорилось во введении, в традиционной файловой системе UNIX с каждым индексным узлом связываются три атрибута времени. Любой пользователь мо жет установить значения atime и mtime функцией utime, если он имеет право запи си в каталог, содержащий файл. Изменить с time практически невозможно. Сле дующий пример демонстрирует вызов функции utime:
$SECONDS_PER_DAY = 60 60 * 24;
($atime, $mtime) = (stat($file))[8,9], $atirne -= 7 * $seconds_per_day;
$mtime -= 7 * $seconds_per_day;
utime($atime, $mtime, $file)
or die "couldn't backdate $file by a week w/ utime: $!";

Функция utime должна вызываться для обоих атрибутов, atime и mtlme. Если вы хотите задать лишь одно из этих значений, необходимо предварительно полу чить другое с помощью функции stat:
$mtime = (stat $file)[9];
utime(time, $mtime, $file);

Применение модуля File::stat упрощает этот фрагмент:
use File::stat;
utime(time, stat($file)->mtime, $file);

Функция utime позволяет сделать вид, будто к файлу вообще никто не при трагивался (если не считать обновления ctime). Например, для редактирования файла можно воспользоваться программой из примера 9.1. Пример 9.1. uvi
#!/usr/bin/perl -w # uvi - редактирование файла в vi без изменения атрибутов времени
$file = shift or die "usage: uvi filename\n";
($atime, $mtime) = (stat($file))[8,9];
system($ENV{EDITOR} || "vi", $file);
utime($atime, $mtime, $file)
or die "couldn't restore $file to orig times: $!":


Смотри также: Описание функций stat и utime в perlfunc(1) стандартный модуль File::stat и страница руководства utime(3).

9.2. Удаление файла

Проблема

Требуется удалить файл. Функция Perl delete вам не подходит.

Решение

Воспользуйтесь функцией Perl unlink:
unlink($FILENAME) or die "Can't delete $FILENAME: $!\n":
unlink(@FILENAMES) == (filenames or die
"Couldn't unlink all of @FILENAMES: $!\n";

Комментарий

Функция unlink была названа по имени системной функции UNIX. В Perl она получает список имен файлов и возвращает количество успешно удаленных фай лов. Возвращаемое значение можно проверить с помощью | | или о г:
unlink($file) or die "Can't unlink $file: $!";

Функция unlink не сообщает, какие файлы не были удалены - лишь их общее количество. Следующий фрагмент проверяет, успешно ли состоялось удаление нескольких файлов, и выводит количество удаленных файлов:
unless (($count = unlink(@filelist)) == ofilelist) { warn "could only delete $count of " . (ofilelist) . " files";
}

Перебор @filelist в цикле foreach позволяет выводить отдельные сообщения об ошибках. В UNIX удаление файла из каталога требует права записи для каталога', а не для файла, поскольку изменяется именно каталог. В некоторых ситуациях появ ляется возможность удаления файла, в который запрещена запись, или записи в файл, который нельзя удалить. Если удаляемый файл открыт некоторым процессом, операционная система удаляет элемент каталога, но не освобождает блоки данных до закрытия фай ла во всех процессах. Именно так работает функция new_tmpfile в IO::File (см. ре цепт 7.5).

Смотри также: Описание функции unlink в perlfunc(1)\ страница руководства unlink(2). Идея с удаленным файлом, который продолжает оставаться доступным, применяет ся в рецепте 7.5.

9.3. Копирование или перемещение файла

Проблема

Необходимо скопировать файл, однако в Perl не существует встроенной коман ды копирования.

Решение

Воспользуйтесь функцией copy стандартного модуля File::Copy:
use File::Copy;
copy($oldfile, $newfile);


Если для каталога не был установлен бит запрета 010000, который разрешает удаление только владельцу В общих каталогах тина/tmp по соображениям безопасности обычно используется режим 01777. То же самое делается и вручную:
open(IN, "< Soldfile") or die "can't open $oldfile: $!";
open(OUT, "> $newfile") or die "can't open $newfile: $!";
$blksize = (stat in)[11] || 16384; # Желательный размер блока?
while ($len = sysread in, $buf, $blksize) { if (!defined $len) {
next if $! =~ /"interrupted/;
die "System read error: $!\n";
} $offset = 0;
while ($len) { # Частичные операции записи
defined($written = syswrite out, $buf, $len, $offset)
or die "System write error: $!\en";
$len -= $written;
$offset += $written; }
}
close(IN);
close(OUT);

Также можно воспользоваться программой copy вашей системы:
system("cp $oldfile $newfile"); # unix
system("copy $oldfile $newfile"); # dos, vms

Комментарий

Модуль File::Copy содержит функции copy и move. Они удобнее низкоуровне вых функций ввода/вывода и обладают большей переносимостью по сравнению с вызовом system. Функция move допускает перемещение между каталогами а стандартная функция Perl rename - нет (обычно).
use File::Copy;
copy("datafile.dat", "datafile.bak") or die "copy failed: $!";
move("datafile.new", "datafile.dat" ) or die "move failed: $!";

Поскольку обе функции возвращают лишь простой признак успешного завер шения, вы не сможете легко определить, какой файл помешал успешному копи рованию или перемещению. При ручном копировании файлов можно узнать, ка кие файлы не были скопированы, но в этом случае ваша программа забивается сложными вызовами sysread и syswrite.

Смотри также: Описание функций rename, read и syswrite в perlfunc(1); документация по стан дартному модулю File::Copy.

9.4. Распознавание двух имен одного файла

Проблема

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

Решение

Создайте хэш, кэшируемый по номеру устройства и индексного узла для уже встречавшихся файлов. В качестве значений хэша используются имена файлов:
%seen =();
sub do_my_thing {
my $filename = shift;
my ($dev, $ino) = stat $filename;
unless (! $seen{$dev, $ino}++) {
# Сделать что-то с $filename, поскольку это имя
# нам еще не встречалось
}
}

Комментарий

Ключ %seen образуется объединением номеров устройства ($dev) и индексного узла ($шо) каждого файла. Для одного файла номера устройства и индексного узла совпадут, поэтому им будут соответствовать одинаковые ключи. Если вы хотите вести список всех файлов с одинаковыми именами, то вместо подсчета экземпляров сохраните имя файла в анонимном массиве:
foreach $filename (@files) { (
$dev, $ino) = stat $filename;
push( @{ $seen{$dev,$ino} }, $filename);
}
foreach $devino (sort keys %seen) {
($dev, $lno) = split(/$;/o, $devino):
if (@{$seen{$devino}} > 1) {
# @{$seen{$devino}} - список имен одного файла
}
}
Переменная $; содержит строку-разделитель и использует старый синтаксис эмуляции многомерных массивов, $hash{$x, $y, $z}. Хэш остается одномерным, однако он имеет составной ключ. В действительности ключ представляет собой join($; =>$x, $y, $z). Функция split снова разделяет составляющие. Хотя много уровневый хэш можно использовать и напрямую, здесь в этом нет необходимос ти и дешевле будет обойтись без него.

Смотри также: Описание переменной $; в perlvar(1); описание функции stat в perlfunc(1).

9.5. Обработка всех файлов каталога

Проблема

Требуется выполнить некоторые действия с каждым файлом данного каталога.

Решение

Откройте каталог функцией opendir и последовательно читайте имена файлов функцией readdir:
opendir(DIR, $dirname) or die "can't opendir $dirname: $!";
while (defined($file = readdir(dir))) {
# Сделать что-то с "$dirname/$file" } closedir(DIR);

Комментарий

Функции opendir, readdir и closediг работают с каталогами по аналогии с функ циями open, read и close, работающими с файлами. В обоих случаях используются манипуляторы, однако манипуляторы каталогов, используемые opendir и дру гими функциями этого семейства, отличаются от файловых манипуляторов функ ции open и других. В частности, для манипулятора каталога нельзя использо вать оператор о. В скалярном контексте readdi r возвращает следующее имя файла в каталоге, пока не будет достигнут конец каталога - в этом случае возвращается undef. В списко вом контексте возвращаются остальные имена файлов каталога или пустой список, если файлов больше нет. Как объяснялось во введении, имена файлов, возвраща емые readdir, не содержат имя каталога. При работе с именами, полученными от readdir, необходимо либо заранее перейти в нужный каталог, либо вручную при соединить его к имени. Ручное присоединение может выглядеть так:
$dir = "/usr/local/bin";
print "Text files in $dir are:\n";
opendir(BIN, $dir) or die "Can't open $dir: $!";
while( defined ($file = readdir bin) ) { print "$file\n" if -t "$dir/$file";
}
closedir(BIN);

Мы проверяем $file с помощью defined, поскольку простое условие while ($file = readdir bin) проверяет истинность, а не определенность. Хотя наш цикл завершается после перебора всех файлов, возвращаемых readdir, он также завершится преждевременно при наличии файла с именем "О". Функция readdir также возвращает специальные каталоги "." (текущий каталог) и ". ." (родительский каталог). Обычно они пропускаются фрагментом сле дующего вида:
while ( defined ($file = readdir bin) ) {
next if $file ="' /~\.\.?$/; # Пропустить . и ..
# ...
}


Манипуляторы каталогов, как и файловые манипуляторы, существуют на уров не пакетов. Более того, локальный манипулятор каталога можно получить двумя способами: с помощью local *DIRHANDLE или модуля (см. рецепт 7.16). В данном случае нужен модуль DirHandle. Следующий фрагмент использует DirHandle для получения отсортированного списка обычных файлов, которые не являются скрытыми (имена которых не начинаются с "."):
use DirHandle;
sub plainfiles { my $dir = shift;
my $dh = dirhandle->new($dir) or die "can't opendir $dir: $!";
return sort #Отсортировать имена
grep { -f } # Выбрать "обычные" файлы
map { "$dir/$_" } # Построить полные пути
grep { !/"\./ } # Отфильтровать скрытые файлы
$dh->read(); # Прочитать все элементы
}


Метод read модуля DirHandle работает так же, как и readdir, и возвращает ос тальные имена файлов. Нижний вызов grep оставляет лишь те имена, которые не начинаются с точки. Вызов тар преобразует имена файлов, полученные от read, в полные, а верхний вызов grep отфильтровывает каталоги, ссылки и т. д. Получен ный список сортируется и возвращается. В дополнение к readdir также существуют функции rewinddir (перемещает ма нипулятор каталога к началу списка файлов), seekdir (переходит к конкретному смещению в списке) и telldir (определяет смещение от начала списка).

Смотри также: Описание функций closedir, opendir, readdir, rewinddir, seekdir и telldir в perlfunc(l); документация по стандартному модулю DirHandle.

9.6. Получение списка файлов по шаблону

Проблема

Требуется получить список файлов по шаблону, аналогичному конструкциям *.* (MS-DOS) и *.h(UNIX).

Решение

Семантика командного интерпретатора С shell системы UNIX поддерживается в Perl с помощью ключевого слова glob и оператора о:

@list = <*.с>;
@list = glob("*.c");/
Для ручного извлечения имен файлов можно воспользоваться функцией readdir:
opendir(DIR, $path);
@files = grep { /\.c$/ } readdir(dir);
closedir(DIR);

Модуль File::KGlob от CPAN получает список файлов без ограничений длины:
use File::KGlob;
@files = glob("*.c");

Комментарий

Встроенная функция Perl glob и запись <ШАБЛОН> (не путать с записью <МАНИПУ-ЛЯТОР>!) в настоящее время на большинстве платформ используют внешнюю про грамму для получения списка файлов. В UNIX это программа csh1, а в Windows - dosglob.exe. На Macintosh и в VMS это реализуется на внутреннем уровне, без вне шних программ. Предполагается, что шаблоны обеспечивают семантику С shell во всех системах, отличных от UNIX, и улучшают переносимость. Из-за исполь зования интерпретатора в UNIX такое решение не подходит для сценариев с ат рибутом setuid. Чтобы справиться с затруднениями, можно реализовать собственный механизм отбора с применением встроенного оператора opendir или модуля File::KGlob от CPAN - в обоих случаях внешние программы не используются. File::KGlob обес печивает семантику отбора по типу интерпретаторов UNIX, тогда как opendir по зволяет отбирать файлы с помощью регулярных выражений Perl. В простейшем решении с opendir список, возвращаемый readdir, фильтруется с помощью grep:
(afiles = grep { /\.[ch]$/i } readdir(dh);

Обычно при наличии установленного интерпретатора tcsh Perl использует его, поскольку он надежнее. Если не установлен ни один из этих интерпретаторов, используется /bin/sh>. То же самое можно сделать и с помощью модуля DirHandle:
use DirHandle;
$dh = dirhandle->new($path) or die "Can't open $path : $!\n";
@files = grep { /\.[ch]$/i } $dh->read();

Как обычно, возвращаемые имена файлов не содержат каталога. При исполь зовании имени каталог приходится присоединять вручную:
opendir(DH, $dir) or die "Couldn't open $dir for reading: $!";
@files =();
while( defined ($file = readdir(dh)) ) { next unless /\.[ch]$/i;
my $filename = "$dir/$file";
push(@files, $filename) if -T $file;
В следующем примере чтение каталога и фильтрация для повышения эффек тивности объединяются с преобразованием Шварца (см. главу 4 Массивы ). В массив @dirs заносится отсортированный список подкаталогов, имена которых представляют собой числа:
@dirs = map { $_->[1] } # Извлечение имен
sort { $a->[0] <=> $b->[0] } # Числовая сортировка имен
grep { -d $_->[1] } # Каталоги
mар { [ $_, "$path/$_" 1 } # Сформировать (имя, путь)
grep { /"\d+$/ } # Только числа
readdir(DIR); # Все файлы

В рецепте 4.14 показано, как читать подобные странные конструкции. Как обычно, форматирование и документирование кода заметно упрощает его чтение и понимание.

Смотри также: Описание функций closedir, opendir, readdir, rewinddir, seekdir ntelldirB perlfunc(1), документация по стандартному модулю DirHandle; раздел I/O Operators perlop(1); рецепты 6.9; 9.7.

9.7. Рекурсивная обработка всех файлов каталога

Проблема

Требуется выполнить некоторую операцию с каждым файлом и подкаталогом некоторого каталога.

Решение

Воспользуйтесь стандартным модулем File::Find.
use File::Find;
sub process_file {
#Делаем то, что хотели
} find(\&process_file, @DIRLIST);

Комментарий

Модуль File::Find обеспечивает удобные средства рекурсивной обработки файлов. Просмотр каталога и рекурсия организуются без вашего участия. Достаточно пе редать find ссылку на функцию и список каталогов. Для каждого файла в этих каталогах find вызовет заданную функцию. Перед вызовом функции find переходит в указанный каталог, имя которого по отношению к начальному каталогу хранится в переменной $File: :Find: :dir. Пе ременной $_ присваивается базовое имя файла, а полный путь к этому файлу на ходится в переменной $File: :Find: :name. Ваша программа может присвоить $File: :Find: : prune истинное значение, чтобы функция find не спускалась в толь ко что просмотренный каталог. Использование File::Find демонстрируется следующим простым примером. Мы передаем find анонимную подпрограмму, которая выводит имя каждого об наруженного файла и добавляет к именам каталогов /:
@ARGV = qw(.) unless @argv;
use File::Find;
find sub { print $File: :Find: :name, -d && '/'. "\n" }, @ARGV;

Для вывода / после имен каталогов используется оператор проверки -d, кото рый при отрицательном результате возвращает пустую строку ' '. Следующая программа выводит суммарный размер всего содержимого ката лога. Она передает find анонимную подпрограмму для накопления текущей сум мы всех рассмотренных ей файлов. Сюда входят не только обычные файлы, но и все типы индексных узлов, включая размеры каталогов и символических ссылок. После выхода из функции find программа выводит накопленную сумму.
use File::Find;
@ARGV = (' . ') unless @argv; .
my $sum = 0;
find sub { $sum += -s }, @argv;
print "@ARGV contains $sum bytes\n";

Следующий фрагмент ищет самый большой файл в нескольких каталогах:
use File::Find;
@ARGV = ( . ) unless @argv;
my ($saved_size, $saved_name) = (-1, '');
sub biggest {
return unless -f && -s _ > $saved_size;
$saved_size = -s _;
$saved_name = $file::find::name;
}
find(\&biggest, @ARGV);
print "Biggest file $saved_name in OARGV is $saved_size bytes lona.\n":

Переменные $saved_size и $saved_name используются для хранения имени и размера самого большого файла. Если мы находим файл, размер которого пре вышает размер самого большого из просмотренного до настоящего момента, сохраненное имя и размер заменяются новыми значениями. После завершения работы find выводится имя и размер самого большого файла в весьма подроб ном виде. Вероятно, более практичная программа ограничится выводом имени файла, его размера или и того и другого. На этот раз мы воспользовались име нованной функцией вместо анонимной, поскольку она получилась относительно большой. Программу нетрудно изменить так, чтобы она находила файл, который изме нялся последним:
use File::Find;
@ARGV = ('.') unless @argv;
my ($age, $name);
sub youngest {
return if defined $age && Sage > -M;
Sage = (stat(_))[9];
$name = $file::find::name;
}
find(\&youngest, @ARGV);
print "$name " , scalar(localtime($age)) , "\n";
Модуль File::Find не экспортирует имя переменной $name, поэтому на нее сле дует ссылаться по полному имени. Пример 9.2 демонстрирует скорее работу с про странствами имен, нежели рекурсивный перебор в каталогах. Он делает перемен ную $name текущего пакета синонимом переменной File::Find (в сущности, именно на этом основана работа модуля Exporter). Затем мы объявляем собственную версию find с прототипом, обеспечивающим более удобный вызов.
Пример 9.2. fdirs
#!/usr/bin/perl -lw
# fdirs - поиск всех каталогов
@ARGV = qw(.) unless @argv;
use File::Find ();
sub find(&@>) { &File: :Find: :find } name = *file::find::name;
find { print $name if -d } @ARGV;
Наша версия find вызывает File::Find, импортирование которой предотвраща ется включением пустого списка () в команду use. Вместо записи вида:
find sub { print $File::Find::name if -d }, @ARGV;
можно написать более приятное
find { print $name if -d } @ARGV;


Смотри также: Man-страница /zW(l); рецепт 9.6; документация по стандартным модулям File::Find и Exporter.

9.8. Удаление каталога вместе с содержимым

Проблема

Требуется рекурсивно удалить ветвь дерева каталога без применения тг -г.

Решение

Воспользуйтесь функцией finddepth модуля File::Find (см. пример 9.3). Пример 9.3. rmtreel
#!/usr/bin/perl
# rmtreel - удаление ветви дерева каталогов (по аналогии с rm -r)
use File::Find qw(finddepth);
die "usage: $0 dir ..\n" unless @>ARGV;
name = *file::find::name;
finddepth \&zap, @ARGV;
sub zap {
if (!-1 && -d _) {
print "rmdir $name\n";
rmdir($name) or warn "couldn't rmdir $name: $!";
} else {
print "unlink $name";
unlink($name) or warn "couldn't unlink $name: $!":
}
}

Или воспользуйтесь функцией rmtree модуля File::Path (см. пример 9.4). Пример 9.4. rmtree2
#!/usr/bin/perl
# rmtree2 - удаление ветви дерева каталогов (по аналогии с rm -г)
use File::Path:
die "usage: $0 dir ,.\n" unless @ARGV;
foreach $dir (@ARGV) {
rmtree($dir);
}


> Предупреждение -----------------------------
Эти программы удаляют целые ветви дерева каталогов. Применяйте крайне осторожно!

Комментарий

Модуль File::Find экспортирует функцию find, которая перебирает содержи мое каталога практически в случайном порядке следования файлов, и функцию finddepth, гарантирующую перебор всех внутренних файлов перед посещением самого каталога. Именно этот вариант поведения использован нами для удаления каталога вместе с содержимым. У нас есть две функции, rmdir и unlink. Функция unlink удаляет только фай лы, а rmdir - только пустые каталоги. Мы должны использовать finddepth, чтобы содержимое каталога заведомо удалялось раньше самого каталога. Перед тем как проверять, является ли файл каталогом, необходимо узнать, не является ли он символической ссылкой, -d возвращает t rue и для каталога, и для символической ссылки на каталог. Функции stat, 1st at и операторы провер ки (типа -d) используют системную функцию stat (2), которая возвращает всю информацию о файле, хранящуюся в индексном узле. Эти функции и операто ры сохраняют полученную информацию и позволяют выполнить дополнитель ные проверки того же файла с помощью специального манипулятора _. При этом удается избежать лишних вызовов системных функций, возвращающих старую информацию и замедляющих работу программы.

Смотри также: Описание функций unlink, rmdir, Istat и stat в perlfunc(\); документация по стандартному модулю File::Find; man-страницы rm(1) и stat{1) раздел perlfunc(1), посвященный операторам -X.

9.9. Переименование файлов

Проблема

Требуется переименовать файлы, входящие в некое множество.

Решение

Воспользуйтесь циклом f о reach и функцией rename:
foreach $file (@NAMES) { my $newname = $file;
# change, $file rename($file, $newname) or
warn "Couldn't rename $file to $newname: $!\n";
}

Комментарий

Программа вполне тривиальна. Функция rename получает два аргумента - старое и новое имя. Функция rename предоставляет интерфейс к системной функции переименования, которая обычно позволяет переименовывать файлы только в том случае, если старое и новое имена находятся в одной файловой системе. После небольших изменений программа превращается в универсальный сцена рий переименования вроде написанного Ларри Уоллом (см. пример 9.5). Пример 9.5. rename
#!/usr/bin/perl -w
# rename - переименование файлов от Ларри
$ор = shift or die "usage: rename expr [files]\n";
chomp(@ARGV = ) unless @ARGV;
for (@ARGV) { $was = $_;
eval Sop;
die $@ if $@;
rename($was,$_) unless $was en $ :
}

Первый аргумент сценария - код Perl, который изменяет имя файла, храня щееся в $_, и определяет алгоритм переименования. Вся черная работа поручает ся функции eval. Кроме того, сценарий пропускает вызов rename в том случае, если имя осталось прежним. Это позволяет просто использовать универсальные сим волы (rename EXPR *) вместо составления длинных списков имен. Приведем пять примеров вызова программы rename из командного интерпре татора:
% rename 's/\.orig$//' *.orig
% rename 'tr/A-Z/a-z/ unless /"Make/' *
% rename '$_ .= ".bad"' *.f
% rename 'print "$_: "; s/foo/bar/ if =~ /"y/i'
% find /tmp -name '*"" -print | rename 's/"(.+)'$/.#$1/'

Первая команда удаляет из имен файлов суффикс .orig. Вторая команда преобразует символы верхнего регистра в символы нижнего ре гистра. Поскольку вместо функции 1с используется прямая трансляция, такое преобразование не учитывает локальный контекст. Проблема решается следую щим образом:
% rename 'use locale; $_ = lc($_) unless/"make/'

Третья команда добавляет суффикс .bad к каждому файлу Fortran с суффик сом ". f" - давняя мечта многих программистов. Четвертая команда переименовывает файлы в диалоге с пользователем. Имя каж дого файла отправляется на стандартный вывод, а из стандартного ввода читает ся ответ. Если пользователь вводит строку, начинающуюся с "у" или "Y", то все экземпляры "foo" в имени файла заменяются на "bar". Пятая команда с помощью find ищет в /tmp файлы, имена которых заканчива ются тильдой. Файлы переименовываются так, чтобы они начинались с префик са . #. В сущности, мы переключаемся между двумя распространенными конвенци ями выбора имен файлов, содержащих резервные копии. В сценарии rename воплощена вся мощь философии UNIX, основанной на ути литах и фильтрах. Конечно, можно написать специальную команду для преобра зования символов в нижний регистр, однако ничуть не сложнее написать гибкую, универсальную утилиту с внутренним eval. Позволяя читать имена файлов из стан дартного ввода, мы избавляемся от необходимости рекурсивного перебора ката лога. Вместо этого мы используем функцию find, которая прекрасно справляется с этой задачей. Не стоит изобретать колесо, хотя модуль File::Find позволяет это сделать.

Смотри также: Описание функции rename в perlfunc(1); страницы руководства mv(\) и гепате{Т); документация по стандартному модулю File::Find.


Назад
Вперед