Как найти файл, который существует в разных каталогах по заданному пути в Perl - PullRequest
1 голос
/ 14 декабря 2011

Я ищу метод для поиска файла, который находится в нескольких каталогах по заданному пути.Другими словами, эти каталоги будут иметь файлы с одинаковым именем файла.Мой сценарий, похоже, имеет проблему иерархии при поиске правильного пути для поиска имени файла для обработки.У меня есть путь исправления в качестве входных данных, и сценарий должен будет искать путь и находить файлы оттуда, но мой сценарий, кажется, застрял на двух уровнях и обрабатывает их оттуда, а не просматривает последние каталоги уровня (в моем случае здесьон обрабатывает "ln" и "nn" и начинает обработку подпрограммы).

Путь ввода исправления: -

/nfs/disks/version_2.0/

Файлы, которые я хочу выполнить после обработки подпрограммойбудет существовать в нескольких каталогах, как показано ниже.По сути, я хотел проверить, существует ли file1.abc во всех каталогах temp1, temp2 и temp3 в каталоге ln.То же самое для file2.abc, если существует в temp1, temp2, temp3 в каталоге nn.

Файлы, которые я хотел проверить по полному пути, будут выглядеть так: -

/nfs/disks/version_2.0/dir_a/ln/temp1/file1.abc
/nfs/disks/version_2.0/dir_a/ln/temp2/file1.abc
/nfs/disks/version_2.0/dir_a/ln/temp3/file1.abc

/nfs/disks/version_2.0/dir_a/nn/temp1/file2.abc
/nfs/disks/version_2.0/dir_a/nn/temp2/file2.abc
/nfs/disks/version_2.0/dir_a/nn/temp3/file2.abc

Мой скрипткак показано ниже: -

#! /usr/bin/perl -w 
my $dir = '/nfs/fm/disks/version_2.0/' ;
opendir(TEMP, $dir) || die $! ;
foreach my $file (readdir(TEMP)) {
    next if ($file eq "." || $file eq "..") ;
    if (-d "$dir/$file") {
        my $d = "$dir/$file";   
        print "Directory:- $d\n" ;
        &getFile($d);
        &compare($file) ;
    }
}

Обратите внимание, что я поставил print "Directory:- $d\n" ; там для целей отладки, и он напечатал это: -

/nfs/disks/version_2.0/dir_a/
/nfs/disks/version_2.0/dir_b/

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

Может кто-нибудь помочь мне указать, где ошибка в моем скрипте?Спасибо!

Ответы [ 2 ]

0 голосов
/ 15 декабря 2011

Ого, это как пережить 1990-е!Код на Perl несколько эволюционировал, и вам действительно нужно изучить новый материал.Похоже, вы изучили Perl в версии 3.0 или 4.0.Вот несколько указателей:

  • Используйте use warnings; вместо -w в командной строке.
  • Используйте use strict;.Это потребует от вас предварительно объявить переменные, используя my, который поместит их в локальный блок или файл, если они не находятся в локальном блоке.Это помогает отлавливать множество ошибок.
  • Не ставьте & перед именами подпрограмм.
  • Используйте and, or и not вместо &&, || и !.
  • Узнайте о модулях Perl, которые могут сэкономить вам много времени и усилий.

Когда кто-то говорит обнаруживает дубликаты , я сразу думаю о хешах.Если вы используете хеш, основанный на имени вашего файла, вы можете легко увидеть, есть ли дубликаты файлов.

Конечно, хеш может иметь только одно значение для каждого ключа.К счастью, в Perl 5.x это значение может быть ссылкой на другую структуру данных.

Итак, я рекомендую вам использовать хеш, который содержит ссылку на список (массив на старом языке).Вы можете поместить каждый экземпляр файла в этот список.

Используя ваш пример, вы получите структуру данных, которая выглядит следующим образом:

%file_hash = {
    file1.abc => [
       /nfs/disks/version_2.0/dir_a/ln/temp1
       /nfs/disks/version_2.0/dir_a/ln/temp2
       /nfs/disks/version_2.0/dir_a/ln/temp3
    ],
    file2.abc => [
       /nfs/disks/version_2.0/dir_a/nn/temp1
       /nfs/disks/version_2.0/dir_a/nn/temp2
       /nfs/disks/version_2.0/dir_a/nn/temp3
   ];

И вот программа для выполненияit:

#! /usr/bin/env perl
#
use strict;
use warnings;
use feature qw(say);        #Can use `say` which is like `print "\n"`;

use File::Basename; #imports `dirname` and `basename` commands
use File::Find;             #Implements Unix `find` command.

use constant DIR => "/nfs/disks/version_2.0";

# Find all duplicates
my %file_hash;
find (\&wanted, DIR);

# Print out all the duplicates
foreach my $file_name (sort keys %file_hash) {
    if (scalar (@{$file_hash{$file_name}}) > 1) {
        say qq(Duplicate File: "$file_name");
        foreach my $dir_name (@{$file_hash{$file_name}}) {
            say "    $dir_name";
        }
    }
}

sub wanted {
    return if not -f $_;    

    if (not exists $file_hash{$_}) {
        $file_hash{$_} = [];
    }
    push @{$file_hash{$_}}, $File::Find::dir;
}

Вот несколько вещей о File::Find:

  • Работа происходит в подпрограмме wanted.
  • В $_это имя файла, и я могу использовать его, чтобы узнать, является ли это файл или каталог
  • $File::Find::Name это полное имя файла, включая путь.
  • $File::Find::dirэто имя каталога.

Если ссылка на массив не существует, я создаю ее с помощью $file_hash{$_} = [];.Это не обязательно, но я нахожу это утешительным, и это может предотвратить ошибки.Чтобы использовать $file_hash{$_} в качестве массива, мне нужно разыменовать его.Я делаю это, помещая перед ним @, так что это может быть @$file_hash{$_} или @{$file_hash{$_}}.

Как только все файлы найдены, я могу распечатать всю структуру.Единственное, что я делаю, это проверяю, чтобы убедиться, что в каждом массиве более одного члена.Если есть только один участник, то дубликатов не будет.


Ответ Грейс

Привет, Дэвид В., большое спасибо за объяснение и пример сценария.Извините, может быть, я не совсем ясно определил свою проблему.Я думаю, что не могу использовать хэш в моем поиске пути для структуры данных.Поскольку файл * .abc имеет несколько сотен определений и каждый из файлов * .abc даже имеет одно и то же имя файла, но он фактически отличается по содержанию в каждой структуре каталогов.

Например, file1.abc находится в "/nfs/disks/version_2.0/dir_a/ln/temp1" и не совпадает с файлом file1.abc в "/nfs/disks/version_2.0/ dir_a / ln / temp2 "и" /nfs/disks/version_2.0/dir_a/ln/temp3 ".Мое намерение состоит в том, чтобы собрать список файлов * .abc в каждой из структур каталогов (temp1, temp2 и temp3) и сравнить список имен файлов с masterlist.Не могли бы вы помочь пролить свет на то, как решить эту проблему?Благодарю.- Благодать вчера

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

foreach my $file_name (sort keys %file_hash) {
    if (scalar (@{$file_hash{$file_name}}) > 1) {
        #say qq(Duplicate File: "$file_name");
        foreach my $dir_name (@{$file_hash{$file_name}}) {
            #say "    $dir_name";
            open (my $fh, "<", "$dir_name/$file_name")
              or die qq(Can't open file "$dir_name/$file_name" for reading);
            # Process your file here...
            close $fh;
        }
    }
}

Если вы ищете только определенные файлы, вы можете изменить функцию wanted, чтобы пропустить ненужные файлы,Например, здесь я ищу только файлы, которые соответствуют шаблону file*.txt.Примечание. Я использую регулярное выражение /^file.*\.txt$/ для сопоставления имени файла.Как видите, это то же самое, что и предыдущая подпрограмма wanted.Единственным отличием является мой тест: я ищу что-то, что является файлом (-f) и имеет правильное имя (file*.txt):

sub wanted {
    return if not -f $_ and /^file.*\.txt$/;    

    if (not exists $file_hash{$_}) {
        $file_hash{$_} = [];
    }
    push @{$file_hash{$_}}, $File::Find::dir;
}

Если вы просматриваете содержимое файла, вы можете использовать MD5 хэш , чтобы определить, совпадает или нет содержимое файла.Это сокращает файл до простой строки длиной от 16 до 28 символов, которая может даже использоваться в качестве хеш-ключа вместо имени файла.Таким образом, файлы с совпадающими хэшами MD5 (и, следовательно, с соответствующим содержимым) будут находиться в одном и том же хеш-списке.

Вы говорите о «главном списке» файлов и, похоже, у вас есть идея, что этот главный списокдолжен соответствовать содержимому файла, который вы ищете.Итак, я делаю небольшой мод в моей программе.Сначала я беру этот основной список , о котором вы говорили, и генерирую суммы MD5 для каждого файла.Затем я посмотрю на все файлы в этом каталоге, но возьму только те, у которых есть соответствующий хэш MD5 ...

Кстати, здесь есть , а не был проверен.

#! /usr/bin/env perl
#
use strict;
use warnings;
use feature qw(say);        #Can use `say` which is like `print "\n"`;

use File::Find;             #Implements Unix `find` command.
use Digest::file qw(digest_file_hex);

use constant DIR         => "/nfs/disks/version_2.0";
use constant MASTER_LIST_DIR => "/some/directory";

# First, I'm going thorugh the MASTER_LIST_DIR directory
# and finding all of the master list files. I'm going to take
# the MD5 hash of those files, and store them in a Perl hash 
# that's keyed by the name of file file. Thus, when I find a 
# file with a matching name, I can compare the MD5 of that file
# and the master file. If they match, the files are the same. If
# not, they're different.

# In this example, I'm inlining the function I use to find the files
# instead of making it a separat function.

my %master_hash;
find (
    {
        %master_hash($_) = digest_file_hex($_, "MD5") if -f;
    },
    MASTER_LIST_DIR
);

# Now I have the MD5 of all the master files, I'm going to search my
# DIR directory for the files that have the same MD5 hash as the
# master list files did. If they do have the same MD5 hash, I'll
# print out their names as before.

my %file_hash;
find (\&wanted, DIR);

# Print out all the duplicates
foreach my $file_name (sort keys %file_hash) {
    if (scalar (@{$file_hash{$file_name}}) > 1) {
        say qq(Duplicate File: "$file_name");
        foreach my $dir_name (@{$file_hash{$file_name}}) {
            say "    $dir_name";
        }
    }
}

# The wanted function has been modified since the last example.
# Here, I'm only going to put files in the %file_hash if they

sub wanted {
    if (-f $_ and $file_hash{$_} = digest_file_hex($_, "MD5")) {
        $file_hash{$_} //= [];    #Using TLP's syntax hint
        push @{$file_hash{$_}}, $File::Find::dir;
    }
}
0 голосов
/ 14 декабря 2011

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

if (-d "$dir/$file") {
    my $d = "$dir/$file";   
    print "Directory:- $d\n" ;
    &getFile($d);
    &compare($file) ;
}

Я предполагаю, что &getFile($d) предназначен для входа в каталог (то есть рекурсивный шаг). Это отлично. Однако, похоже, что &compare($file) - это действие, которое вы хотите выполнить, когда объект, на который вы смотрите, не является каталогом. Следовательно, этот блок кода должен выглядеть примерно так:

if (-d "$dir/$file") {
    &getFile("$dir/$file");  # the recursive step, for directories inside of this one
} elsif( -f "$dir/$file" ){
    &compare("$dir/$file");  # the action on files inside of the current directory
}

Общий псевдокод должен выглядеть следующим образом:

sub myFind {
    my $dir = shift;
    foreach my $file( stat $dir ){
        next if $file -eq "." || $file -eq ".."
        my $obj = "$dir/$file";
        if( -d $obj ){
            myFind( $obj );
        } elsif( -f $obj ){
            doSomethingWithFile( $obj );
        }
    }
}
myFind( "/nfs/fm/disks/version_2.0" );

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

find /nfs/fm/disks/version_2.0 -type f -name "the-filename-you-want" -exec your_script.pl {} \;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...