ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП |
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы. |
КаталогиВведениеДля полноценного понимания работы с каталогами необходимо понимать механизмы, заложенные в ее основу. Наш материал ориентирован на файловую систему 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, перечислены в следующей таблице.
Стандартный модуль File::stat предоставляет именованный интерфейс к этим значениям. Он переопределяет функцию stat, поэтому вместо массива, описанного выше, функция возвращает объект с методами для получения каждого атрибута: use File::stat; $inode = stat("/usr/bin/vi"); $ctime = $inode->ctime; $size = $inode->size; Кроме того, в Perl предусмотрен набор операторов, вызывающих функцию sta: и возвращающих лишь один атрибут. Эти операторы совокупно называются «операторами -X», поскольку их имена состоят из дефиса, за которым следует один символ. Они построены по образцу операторов test командного интерпретатора.
Функция stat и операторы -X кэшируют значения, полученные при вызове системной функции stat(2). Если stat или оператор -X вызывается для специального файлового манипулятора _ (один символ подчеркивания), то вместо повторного вызова stat будет использована информация, хранящаяся в кэше. Это позволяет проверять различные атрибуты файла без многократного вызова stat(2) или возникновения опасности перехвата: open( F, "< $filename" )
РезюмеИмена файлов хранятся в каталогах отдельно от размера, атрибутов защиты и прочих метаданных, хранящихся в индексном узле. Функция stat возвращает информацию индексного узла (метаданные). Функции opendir, readdir и их спутники обеспечивают доступ к именам файлов в каталоге с помощью манипулятора каталога. Манипулятор каталога похож на файловый манипулятор, но не идентичен ему. В частности, для манипулятора каталога нельзя вызвать о. Права доступа к каталогу определяют, можете ли вы прочитать или записать список имен файлов. Права доступа к файлу определяют, можете ли вы изменить метаданные или содержимое файла. В индексном узле хранятся три атрибута времени. Ни один из них не определяет время создания файла.9.1. Получение и установка атрибутов времениПроблемаТребуется получить или изменить время последней модификации (записи или изменения) или обращения (чтения) для файла.РешениеФункция stat получает атрибуты времени, а функция utime устанавливает их зна-чения. Обе функции являются встроенными в Perl:($READTIME, $WRITETIME) = (stat($filename))[8,9];
КомментарийКак говорилось во введении, в традиционной файловой системе 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: $!": [> Смотри также -------------------------------
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). > Смотри также -------------------------------
9.3. Копирование или перемещение файлаПроблемаНеобходимо скопировать файл, однако в Perl не существует встроенной команды копирования.РешениеВоспользуйтесь функцией copy стандартного модуля File::Copy:use File::Copy; copy($oldfile, $newfile); Если для каталога не был установлен бит запрета 010000, который разрешает
удаление только владельцу В общих каталогах тина/tmp по соображениям безопасности
обычно используется режим 01777. То же самое делается и вручную:
КомментарийМодуль 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. > Смотри также -------------------------------
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 снова разделяет составляющие. Хотя многоуровневый хэш можно использовать и напрямую, здесь в этом нет необходимости и дешевле будет обойтись без него. > Смотри также -------------------------------
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. Следующий 4^рагмент использует DirHandle
для получения отсортированного списка обычных файлов, которые не являются
«скрытыми» (имена которых не начинаются с "."):
Метод read модуля DirHandle работает так же, как и readdir, и возвращает остальные имена файлов. Нижний вызов grep оставляет лишь те имена, которые не начинаются с точки. Вызов тар преобразует имена файлов, полученные от read, в полные, а верхний вызов grep отфильтровывает каталоги, ссылки и т. д. Полученный список сортируется и возвращается. В дополнение к readdir также существуют функции rewinddir (перемещает манипулятор каталога к началу списка файлов), seekdir (переходит к конкретному смещению в списке) и telldir (определяет смещение от начала списка). > Смотри также --------------------------------
9.6. Получение списка файлов по шаблонуПроблемаТребуется получить список файлов по шаблону, аналогичному конструкциям *.* (MS-DOS) и *.h(UNIX).РешениеСемантика командного интерпретатора С shell системы UNIX поддерживается в Perl с помощью ключевого слова glob и оператора о:@list = <*.с>;
КомментарийВстроенная функция 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 показано, как читать подобные странные конструкции. Как обычно, форматирование и документирование кода заметно упрощает его чтение и понимание. > Смотри также -------------------------------
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; > Смотри также -------------------------------
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), которая возвращает всю информацию о файле, хранящуюся в индексном узле. Эти функции и операторы сохраняют полученную информацию и позволяют выполнить дополнительные проверки того же файла с помощью специального манипулятора _. При этом удается избежать лишних вызовов системных функций, возвращающих старую информацию и замедляющих работу программы.> Смотри также -------------------------------
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 = 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 % 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 позволяет это сделать. > Смотри также --------------------------------
|