Next Previous Contents

Unix Review Column 17 -- Нахождение скрытых исполняемых файлов

Randal Schwartz

Ноябрь 1997

Перевод Anton Petrusevich <casus@mail.ru> и Alex Ott <ott@phtd.tpu.edu.ru>

В последние несколько месяцев я с возбуждением наблюдал за великолепными изображениями, которые нам давал проект Mars Pathfinder. Даже более интересным является связь Perl с этим проектом: Larry Wall провел несколько лет в JPL (координаторы проекта Pathfinder), и много начальной работы над Perl было проведено там. Вы вероятно даже можете заключить пари о том, что часть этих изображений была обработана и опубликована программами на Perl.

В честь этого случая, я начал думать ``pathfinder'', ``pathfinder'', и обнаружил типичную задачу, которая относится к другом типу пути, пути к вашим командам командного процессора (В действительности, в течении тестирования этой программы, я называл ее ``pathfinder'', что вполне подходит).

Если вы такой же как я, то вы делаете добавления в ваши пути поиска каждый раз когда кто-нибудь говорит вам, что ``добавьте этот каталог в ваш путь, чтобы воспользоваться этими утилитамиs''. Трудность заключается в том, что путь поиска является линейным. Если программа с именем ``clipper'' существует в каталоге, расположенном ранее в списке поиска, то ``clipper'', расположенных в следующих в списке каталогах, не будет виден, поскольку командный процессор использует принцип ``первым найден, первым запущен''.

Но как вы можете узнать, что программа спрятана таким способом. Командный процессор не может сообщить об этом. Но программа на Perl может сделать это!

Давайте глянем на задачу нахождения дубликатов в PATH. Сначала нам необходимо получить элементы пути поиска:

        my @path = split /:/, $ENV{PATH};

Здесь, PATH является переменной среды, которая доступна через специальный хэш %ENV, и которая затем разделяется по символам двоеточия. Результатом этого является список @path. Если существовали пустые элементы в PATH (означая, что необходим поиск в текущем каталоге), то они будут существовать как пустые строки.

Затем мы должны удалить все повторяющиеся каталоги из ваших путей. Как дубликаты оказываются там? Наиболее частым случаем является то, что у вас есть что-то подобное:

    set path = (/strange/tool/bin $path);

в некотором файле настройки. Но вы также можете получить дубликаты когда вы копируете один хороший файл .cshrc или .profile на другую систему. Например, мой любимый файл .cshrc содержит в путях поиска и /bin и /usr/bin, но на некоторых системах, эти каталоги являются одним и тем же каталогом (спасибо символическим ссылкам).

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

 
    for (@path) {
      ...
    }

Теперь, внутри тела этого цикла, $_ является одним из каталогов из оригинального пути поиска. Мы сначала должны удалить любые записи, которые не являются абсолютным путем. Это легко:

      next unless m#^/#;

Здесь, регулярное выражение ^/ запрашивает каждый элемент (в $_) для того, чтобы убедиться, что он начинается с /. Если вы чувствуете себя более уверено при использовании substr(), то вы можете сделать что-то подобное этому отрывку:

       next unless substr($_,0,1) eq "/";

Но для меня это означает набор большего количества текста. Я предпочитаю версию с использованием регулярного выражения.

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

      my ($dev,$ino) = stat;
      next unless defined $dev;

Если stat возвращает пустой список, то оригинальная запись в $_ не существует (или ее нельзя достичь). В этом случае значение $dev равно undef. Вызов stat возвращает список из 13 элементов, и мы игнорируем всё кроме двух первых элементов.

Отсюда мы конструируем строку $key, с пробелом между двумя числами. Настоящий формат не является важным... нам просто нужна любая пара числе, которая бы отличалась от любой другой пары чисел:

     my $key = "$dev $ino";

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

      if (exists $path_inodes{$key}) {
        print "warning: $_ is linked to $path_inodes{$key}\n";
        next;
      }

Здесь, next вызывает окончание оставшейся обработки конкретного каталога, поскольку мы уже знаем, что он является дубликатом.

Если мы не нашли дубликатов, то мы просто добавляем данный каталог в список @clean_path, и также помещаем его в хэш %path_inodes. Ключом этого хэша является сконструированная нами переменная $key, а значением хэша -- оригинальный путь к каталогу. Мы не будем использовать его, но он был нужен во время отладки.

      $path_inodes{$key} = $_;
      push @clean_path, $_;

В результате этого в @clean_path будут содержать пути без дубликатов. Это важно, поскольку если есть дубликаты в путях, то мы на следующем этапе получим огромное количество неправильных данных. Теперь приступим к поиску повторяющихся имен в путях поиска.

Одним из способов является проход по путям с помощью цикла foreach:

    for my $dir (@clean_path) {
      ...
    }

Внутри цикла нам необходимо получить содержимое указанного каталога, $dir. Существует несколько способов сделать это, но давайте будем использовать DirHandle. Для его использования, нам необходимо загрузить правильный модуль.

        use DirHandle;

Далее, для нужного каталога создается DirHandle, и читается как список, вот каа здесь:

 
        my @files =
        DirHandle->new($dir)->read;

Здесь, результат вызова new для DirHandle немедленно вызывает функцию read, что приводит к выдаче полного списка имен в данном каталоге. Хорошим свойством этого кода является то, что DirHandle автоматически закрывается в конце этого выражения, поскольку мы не сохраняем его!

Hо это не совсем правильно... нам не нужны файлы ``.'' or ``..'', и для того, чтобы сделать отчет более красивым, нам надо отсортировать его. Это легко -- просто добавьте в выражение операции sort и grep:

        my @files =
        sort grep !/^\.\.?$/,
        DirHandle->new($dir)->read;

Здесь функции sort передаются только те элементы которые не соответствуют регулярному выражению (которое затрагивает только ``.'' и ``..'').

Теперь, настало время пройти по списку @files и выбрать файлы, которые мы уже видели в других каталогах. Этот кусок кода подобен определению дубликатов в коде для заполнения @clean_path:

        for my $file (@files) {
        if (exists $progs{$file}) {
          print "$file in $dir is shadowed by $progs{$file}\n";
          next;
        }
        $progs{$file} = $dir;
      }

Здесь каждое из имен файлов помещается в переменную $file. Если это имя уже существует в хэше %progs, то мы увидим его в предыдущем каталоге и сообщим об этом. Если ключ не существует, то мы запомним имя программы и каталог.

Так что вот все куски кода. Однако нам еще надо склеить их в общую программу, которая бы работала с использованием директивы use strict, и в результате получим следующий код:

    #!/usr/bin/perl -w
    use strict;

        my @path = split /:/, $ENV{PATH};
    my %path_inodes;
    my @clean_path;
        
        for (@path) {
      next unless m#^/#;
      my ($dev,$ino) = stat;
      next unless defined $dev;
      my $key = "$dev $ino";
      if (exists $path_inodes{$key}) {
        print "warning: $_ is linked to $path_inodes{$key}\n";
        next;
      }
      $path_inodes{$key} = $_;
      push @clean_path, $_;
    }

        my %progs;

        ## print "clean path is @clean_path\n";

        for my $dir (@clean_path) {
      use DirHandle;
      my @files =
        sort grep !/^\.\.?$/,
        DirHandle->new($dir)->read;
      ## print "$dir: @files\n";
      for my $file (@files) {
        if (exists $progs{$file}) {
          print "$file in $dir is shadowed by $progs{$file}\n";
          next;
        }
        $progs{$file} = $dir;
      }
    }

Поместите его в каталог, находящийся в путях поиска (Если хотите, назовите его ``pathfinder''), и запустите его, и вы увидите, все программы, которые вы никогда не сможете запустить, поскольку они скрыты. Hаслаждайтесь!


Next Previous Contents