Глава 9 Каталоги

Каталоги

Введение

Для полноценного понимания работы с каталогами необходимо понимать механизмы, заложенные в ее основу. Наш материал ориентирован на файловую систему UNIX, поскольку функции каталогов Perl разрабатывались для системных функций и особенностей именно этой сис Файловая система состоит из двух компонентов: набора блоков данных, где хранится содержимое файлов и каталогов, и индекса к этим блокам. Каждому объекту файловой системы, будь то обычный файл, каталог, ссылка или специальный файл (вроде файлов из каталога Каталог представляет собой файл специального формата, помеченный в индексном узле как каталог. Блоки данных каталога содержат множество пар. Каждая пара содержит имя объекта каталога и соответствующий ему индексный узел. Блоки данных каталога /usr/Ып могу
Имя      Индексный узел
bc        17
du        du 29
nvi        8
pine       55
vi       8
Подобную структуру имеют все каталоги, включая корневой (/). Чтобы прочитать файл /usr/bin/vi, операционная система читает индексный узел /, находит в его блоках данных информацию о /usr, читает индексный узел /usr, находит в его блоках данных информа Имена, хранящиеся в каталогах, не являются полными. Файл /usr/bin/vi хранится в каталоге /usr/bin под именем vi. Если открыть каталог /usr/bin и последовательно читать его элементы, вы увидите имена файлов (patch, login и vi) вместо полных имен /usr/bin/p Однако индексный узел - больше, чем просто указатель на блоки данных. Каждый индексный узел также содержит информацию о типе представляемого объекта (каталог, обычный файл и т. д.) и его размере, набор битов доступа, информацию о владельце и группе, время Одни операции с файлами изменяют содержимое блоков данных файла; другие ограничиваются изменением индексного узла. Например, при дополнении или усечении файла в его индексном узле изменяется информация о размере. Некоторые операции изменяют элемент катало В трех полях структуры индексного узла хранится время последнего обращения, изменения и модификации: atime, ctime и mtime. Поле atime обновляется при каждом чтении данных файла через указатель на его блоки данных. Поле mtime обновляется при каждом изменен При чтении файла изменяется только значение atime. Переименование файла не отражается на atime, ctime или mtime, поскольку изменяется лишь элемент каталога (хотя при этом меняются atime и 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 d Элементы списка, возвращаемые функцией 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 будет использ

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 получает манипулятор каталога. Внешне они похо­жи, но работают по-разному: в Имена файлов в каталоге не обязательно хранятся в алфавитном порядке. Что­бы получить алфавитный список файлов, прочитайте все содержимое каталога и отсортируйте его самостоятельно. Отделение информации каталога от информации индексного узла может быть связано с некоторыми странностями. Операции, изменяющие каталог, требуют права записи для каталога, но не для файла. Большинство операций, изменяющих содержимое файла, требует права за Хотя из-за подобных ситуаций файловая система на первый взгляд кажется нелогичной, в действительности они способствуют широте возможностей UNIX. Реализация ссылок (два имени, ссылающиеся на один файл) становится чрезвы­чайно простой - в двух элементах кат Ссылки делятся на два типа. Тип, описанный выше (два элемента каталога, в которых указан один номер индексного узла), называется прямой (или жесткой) ссылкой (hard link). Операционная система не может отличить первый элемент каталога, соответствующий файлу (созданный при создании файла), от всех по­следующих ссылок на него. Со ссылками другого типа - символическими ссылка­ми - дело обстоит совершенно и

Резюме

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

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

Проблема

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

Решение

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

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

Комментарий

Как говорилось во введении, в традиционной файловой системе UNIX с каждым индексным узлом связываются три атрибута времени. Любой пользователь мо­жет установить значения atime и mtime функцией 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: $!";

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

> Смотри также -------------------------------
Описание функций 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 и дру­гими функциями этого семейст В скалярном контексте readdi r возвращает следующее имя файла в каталоге, пока не будет достигнут конец каталога - в этом случае возвращается undef. В списко­вом контексте возвращаются остальные имена файлов каталога или пустой список, если файлов больше Ручное присоединение может выглядеть так:
$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). В данном случае нужен модуль Di
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, в полные, а верхний В дополнение к readdir также существуют функции rewinddir (перемещает ма­нипулятор каталога к началу списка файлов), seekdir (переходит к конкретному смещению в списке) и telldir (определяет смещение от начала списка).

> Смотри также --------------------------------
Описание 41ункций 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 и Чтобы справиться с затруднениями, можно реализовать собственный механизм отбора с применением встроенного оператора opendir или модуля File::KGlob от CPAN - в обоих случаях внешние программы не используются. File::KGlob обес­печивает семантику отбора по т В простейшем решении с 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: Использование File::Find демонстрируется следующим простым примером. Мы передаем find анонимную подпрограмму, которая выводит имя каждого об­наруженного файла и добавляет к именам каталогов /:
@ARGV = qw(.) unless @ARGV;
use File::Find;
find sub { print $File: :Find: :name, -d && '/'. "\n" }, @ARGV;

Для вывода / после имен каталогов используется оператор проверки -d, кото­рый при отрицательном результате возвращает пустую строку ' '. Следующая программа выводит суммарный размер всего содержимого ката­лога. Она передает 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 используются для хранения имени и размера самого большого файла. Если мы находим файл, размер которого пре­вышает размер самого большого из просмотренного до настоящего момента, сохраненное имя и размер заменяются Программу нетрудно изменить так, чтобы она находила файл, который изме­нялся последним:
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 текущего пак
Пример 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) используют сис

> Смотри также -------------------------------
Описание функций 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 из командного интерпре­татора:
% 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" в имени Пятая команда с помощью find ищет в /tmp файлы, имена которых заканчива­ются тильдой. Файлы переименовываются так, чтобы они начинались с префик­са . #. В сущности, мы переключаемся между двумя распространенными конвенци­ями выбора имен файлов, содержащих В сценарии rename воплощена вся мощь философии UNIX, основанной на ути­литах и фильтрах. Конечно, можно написать специальную команду для преобра­зования символов в нижний регистр, однако ничуть не сложнее написать гибкую, универсальную утилиту с внутренни

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


© copyright 2000 Soft group
Используются технологии uCoz