Ого, это как пережить 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;
}
}