Next Previous Contents

Unix Review Column 36

Randal Schwartz

Февраль 2001

[предполагаемый заголовок: 'What is that, anyway?']

Итак, вы получили каталог полный самых разных вещей, или может быть целое дерево каталогов. Что кроется за каждым из имен? Обозначает ли оно каталог, символьную ссылку или простой файл? А если это файл, то текстовый он или двоичный? И если это двоичный файл, то чем он является -- изображением, исполняемым файлом или просто мусором?

В Perl имеется много встроенных операторов для легкого получения списков имен, а также для определения чем является заданное имя.

Hапример, давайте найдем все подкаталоги внутри текущего каталога:


  for my $name (glob '*') {
    next unless -d $name;
    print "one directory is $name\n";
  }

Оператор glob расширяется в список имен файлов не имеющих точки в начале, а оператор -d возвращает истинное значение для тех имен, которые являются каталогами.

А что если мы хотим выполнить эту операцию рекурсивно? нам необходимо отойти от использования только основ языка, но не слишком далеко. Включенный в базовую поставку модуль File::Find берет на себя все заботы по рекурсивной обработке каталогов. Давайте найдем все подкаталоги текущего каталога:


  use File::Find;
  find sub {
    return unless -d $_;
    print "one directory is $File::Find::name\n";
  }, ".";

Подпрограмма find принимает в качестве параметра ссылку на подпрограмму (часто называемую coderef), в данном случае представленную анонимной подпрограммой. Каждое из имен найденное ниже каталога . (указанного в последней строке данного кода) будет вызывать запуск указанной подпрограммы с установкой переменной $File::Find::name равной полному имени файла, а $_ присваивается базовое имя (со сменной текущего каталога на каталог в котором найден данный файл).

Если вы запустите этот код, то вы увидите, что каждый из каталогов показан два или более раз! Один раз как имя внутри его родительского каталога, и один раз как имя текущего каталога ., когда мы переходим в каталог, а возможно и больше раз, для каждого из подкаталогов содержащихся внутри текущего каталога. Как мы можем избежать такого поведения? Хорошо, просто будем отбрасывать в нашем подкаталоге вхождения для каталогов . и ..:


  use File::Find;
  find sub {
    return if $_ eq "." or $_ eq "..";
    return unless -d $_;
    print "one directory is $<A HREF="File::Find::name">File::Find::name\n";
  }, ".";

Вот. Мы будем перемещаться вперед от базового каталога, поскольку отбрасывание каталогов . и .. является полезной вещью.

А что делать с символьными ссылками? Можем ли мы найти их? Конечно! Это делается с помощью оператора -l:


  use File::Find;
  find sub {
    return if $_ eq "." or $_ eq "..";
    return unless -l $_;
    print "one symlink is $File::Find::name\n";
  }, ".";

Великолепно! Hо на что они указывают? Существует оператор readlink, как в следующем примере:


  use File::Find;
  find sub {
    return if $_ eq "." or $_ eq "..";
    return unless -l $_;
    my $dest = readlink($_);
    print "one symlink is $File::Find::name, pointing to $dest\n";
  }, ".";

Мы можем пропустить тест -l зная, что любой файл не являющийся символьной ссылкой автоматически возвращает неопределенное значение при вызове readlink, вот так:


  use File::Find;
  my @search = @ARGV;
  @search = qw(.) unless @search;
  find sub {
    return if $_ eq "." or $_ eq "..";
    return unless defined (my $dest = readlink($_));
    print "one symlink is $File::Find::name, pointing to $dest\n";
  }, @search;

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

Так, что нам осталось сделать? Мы можем обращать внимание и пропускать каталоги и символические ссылки. А что делать с файлами? Файлы содержат сами действия. И некоторые из них выглядят как текстовые, а некоторые как двоичные. Хотя эти строки являются расплывчатыми: вы можете согласиться, что XML в действительности являются просто текстовыми, а документы Microsoft Word являются текстом внутри двоичного файла.

Hо сначала вернемся к тому, в чем может помочь Perl. Давайте добавим оператор -T для нахождения этих текстовых файлов:


  use File::Find;
  my @search = @ARGV;
  @search = qw(.) unless @search;
  find sub {
    return if -d $_ or -l $_;
    return unless -T $_;
    print "One text file is $File::Find::name\n";
  }, @search;

И это просто великолепно. Отображает список только текстовых файлов. Hо это не дает нам подробную информацию. Можем мы хотим отобразить список всех скриптов на языке Perl. Что нам может дать такую информацию? Хорошо, команда file из поставки Unix может посмотреть внутрь файла для определения чем этот файл является. Давайте будем запускать эту команду для каждого из файлов:


  use File::Find;
  my @search = @ARGV;
  @search = qw(.) unless @search;
  find sub {
    return if -d $_ or -l $_;
    my $file_said = `file $_`;
    if ($file_said =~ /perl/) {
      print "$File::Find::name: $file_said";
    }
  }, @search;

Давайте взглянем на этот код. Теперь мы выбираем имена файлов которые команда file считает, что они вероятно являются программами на Perl. Hо эта программа будет медленно работать на большом дереве файлов. Поскольку мы запускаем file для каждого из найденных файлов.

Существует несколько способов ускорения работы. Я мог бы сохранить все иена файлов и запустить file один раз в конце программы:


  use File::Find;
  my @search = @ARGV;
  @search = qw(.) unless @search;
  my @list;
  find sub {
    return if -d $_ or -l $_;
    push @list, $File::Find::name;
  }, @search;
  for (`file @list`) {
    if (/perl/) {
      print;
    }
  }

Да, это сильно увеличивает скорость, но мы не получаем результаты до окончания прохода по дереву файлов, и мы можем столкнуться с проблемой ограниченности длины командной строки, если мы передаем больше аргументов чем может обработать file.

Hо существует другой способ. Hа CPAN (в таких местах как search.cpan.org), мы можем найти модуль File::MMagic, который предположительно является модулем на Perl созданным на основе команды file, созданный для проекта PPT и вначале базировавшийся на коде написанном для Apache для реализации модуля mod_mime, для эмуляции стандартной команды file. И теперь на основе этого модуля я могу написать рекурсивную управляемую программу, похожую на file. Будет ли прекращено повторное использование кода? (Я надеюсь, что нет!)

Все что нам нужно из этого модуля -- это метод с именем checktype_filename, который возвращает назад MIME-тип (например text/plain или image/jpeg), и возможно дополнительную информацию после двоеточия. Так что давайте быстро найдем все скрипты на Perl. Сначала я смотрю, содержит ли строка слово ``executable'', за которым следует пробел, затем ищется слово оканчивающееся на ``perl'', за которым следует пробел, а за ним слово ``script''. Это простое регулярное выражение, так что мне просто надо добавить его в нужное место:


  use File::Find;
  use File::MMagic;
  my $mm = File::MMagic->new;
  my @search = @ARGV;
  @search = qw(.) unless @search;
  my @list;
  find sub {
    return if -d $_ or -l $_;
    my $type = $mm->checktype_filename($_);
    next unless $type =~ /executable \S+\/perl script/;
    print "$File::Find::name: $type\n";
  }, @search;

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

И последний вариант. Давайте найдем все изображения в дереве файлов и будем вызывать Image::Size (который можно найти на CPAN) для определения их размеров. Совсем немного исправлений:


  use File::Find;
  use File::MMagic;
  use Image::Size;
  my $mm = File::MMagic->new;
  my @search = @ARGV;
  @search = qw(.) unless @search;
  my @list;
  find sub {
    return if -d $_ or -l $_;
    my $type = $mm->checktype_filename($_);
    next unless $type =~ /^image\//;
    print "$File::Find::name: $type: ";
    my ($x, $y, $imgtype) = imgsize($_);
    if (defined $x) {
      print "$imgtype: $x x $y\n";
    } else {
      print "error: $imgtype\n";
    }
  }, @search;

И после того как это заработает, я мог бы убрать использование File::MMagic из своей программы, поскольку Image::Size может сообщить мне, что он не может быть вызван не для картинок, но вы знаете старый лозунг Perl: Есть более одного пути сделать это!

Так, что когда в следующий раз вас спросят ``что у вас есть?'', я надеюсь, вы сможете ответить на этот вопрос короткой программой на Perl. Увидимся в следующий раз, наслаждайтесь!


Next Previous Contents