Удаление файлов с дублированным содержимым из одного каталога [Perl или алгоритм] - PullRequest
7 голосов
/ 17 ноября 2009

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

Вот то, что я придумал, но я не знаю, работает ли это :), еще не пробовал.

Как бы вы это сделали? Perl или общий алгоритм.

use strict;
use warnings;

my @files = <"./files/*.txt">;

my $current = 0;

while( $current <= $#files ) {

    # read contents of $files[$current] into $contents1 scalar

    my $compareTo = $current + 1;
    while( $compareTo <= $#files ) {

        # read contents of $files[compareTo] into $contents2 scalar

        if( $contents1 eq $contents2 ) {
            splice(@files, $compareTo, 1);
            # delete $files[compareTo] here
        }
        else {
            $compareTo++;
        }
    }

    $current++;
}

Ответы [ 9 ]

8 голосов
/ 17 ноября 2009

Вот общий алгоритм (отредактированный для эффективности теперь, когда я стряхнул сонные снаряды - и я также исправил ошибку, о которой никто не сообщал) ...:)

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

Итак, когда мы md5sum каждого файла (см. Дайджест :: MD5 ) вычисляем их размеры, мы можем использовать хеш-таблицу, чтобы сделать наше сопоставление для нас, сохраняя совпадения вместе в массиве:

use strict;
use warnings;
use Digest::MD5 qw(md5_hex);

my %files_by_size;
foreach my $file (@ARGV)
{
    push @{$files_by_size{-s $file}}, $file;   # store filename in the bucket for this file size (in bytes)
}

Теперь нам нужно просто извлечь потенциальные дубликаты и проверить, одинаковы ли они (создав контрольную сумму для каждого, используя Digest :: MD5 ), используя ту же технику хеширования:

while (my ($size, $files) = each %files_by_size)
{
    next if @$files == 1;

    my %files_by_md5;
    foreach my $file (@$files_by_md5)
    {
        open my $filehandle, '<', $file or die "Can't open $file: $!";
        # enable slurp mode
        local $/;
        my $data = <$filehandle>;
        close $filehandle;

        my $md5 = md5_hex($data);
        push @{$files_by_md5{$md5}}, $file;       # store filename in the bucket for this MD5
    }

    while (my ($md5, $files) = each %files_by_md5)
    {
        next if @$files == 1;
        print "These files are equal: " . join(", ", @$files) . "\n";
    }
}

-fini

6 голосов
/ 17 ноября 2009

Perl, с модулем Digest :: MD5.

use Digest::MD5 ;
%seen = ();
while( <*> ){
    -d and next;
    $filename="$_"; 
    print "doing .. $filename\n";
    $md5 = getmd5($filename) ."\n";    
    if ( ! defined( $seen{$md5} ) ){
        $seen{$md5}="$filename";
    }else{
        print "Duplicate: $filename and $seen{$md5}\n";
    }
}
sub getmd5 {
    my $file = "$_";            
    open(FH,"<",$file) or die "Cannot open file: $!\n";
    binmode(FH);
    my $md5 = Digest::MD5->new;
    $md5->addfile(FH);
    close(FH);
    return $md5->hexdigest;
}

Если Perl не обязателен и вы работаете с * nix, вы можете использовать инструменты оболочки

find /path -type f -print0 | xargs -0 md5sum | \
    awk '($1 in seen){ print "duplicate: "$2" and "seen[$1] } \
         ( ! ($1 in  seen ) ) { seen[$1]=$2 }'
6 голосов
/ 17 ноября 2009
md5sum *.txt | perl -ne '
   chomp; 
   ($sum, $file) = split(" "); 
   push @{$files{$sum}}, $file; 
   END {
      foreach (keys %files) { 
         shift @{$files{$_}}; 
         unlink @{$files{$_}} if @{$files{$_}};
      }
   }
'
1 голос
/ 17 ноября 2009

Perl является своего рода излишним для этого:

md5sum * | sort | uniq -w 32 -D | cut -b 35- | tr '\n' '\0' | xargs -0 rm

(Если вам не хватает некоторых из этих утилит или у них нет этих флагов / функций, установить GNU findutils и coreutils.)

1 голос
/ 17 ноября 2009

Вариации на тему:

md5sum *.txt | perl -lne '
  my ($sum, $file) = split " ", $_, 2;
  unlink $file if $seen{$sum} ++;
'

Не нужно идти и хранить список, просто удалить один из списка и удалить остальные; просто отследите то, что вы видели раньше, и удалите любой файл, соответствующий сумме, которую вы уже видели. Разделение по двум пределам - это правильные действия с именами файлов, содержащими пробелы.

Кроме того, если вы не доверяете этому, просто измените слово unlink на print, и оно выведет список файлов, которые будут удалены. Вы даже можете передать этот вывод в файл, а затем, в конце концов, rm $(cat to-delete.txt), если он выглядит хорошо.

0 голосов
/ 17 ноября 2009

Вот способ фильтрации по размеру сначала и по md5 контрольной сумме второй:

#!/usr/bin/perl

use strict; use warnings;

use Digest::MD5 qw( md5_hex );
use File::Slurp;
use File::Spec::Functions qw( catfile rel2abs );
use Getopt::Std;

my %opts;

getopt('de', \%opts);
$opts{d} = '.' unless defined $opts{d};
$opts{d} = rel2abs $opts{d};

warn sprintf "Checking %s\n", $opts{d};

my $files = get_same_size_files( \%opts );

$files = get_same_md5_files( $files );

for my $size ( keys %$files ) {
    for my $digest ( keys %{ $files->{$size}} ) {
        print "$digest ($size)\n";
        print "$_\n" for @{ $files->{$size}->{$digest} };
        print "\n";
    }
}

sub get_same_md5_files {
    my ($files) = @_;

    my %out;

    for my $size ( keys %$files ) {
        my %md5;
        for my $file ( @{ $files->{$size}} ) {
            my $contents = read_file $file, {binmode => ':raw'};
            push @{ $md5{ md5_hex($contents) } }, $file;
        }
        for my $k ( keys %md5 ) {
            delete $md5{$k} unless @{ $md5{$k} } > 1;
        }
        $out{$size} = \%md5 if keys %md5;
    }
    return \%out;
}

sub get_same_size_files {
    my ($opts) = @_;

    my $checker = defined($opts->{e})
                ? sub { scalar ($_[0] =~ /\.$opts->{e}\z/) }
                : sub { 1 };

    my %sizes;
    my @files = grep { $checker->($_) } read_dir $opts->{d};

    for my $file ( @files ) {
        my $path = catfile $opts->{d}, $file;
        next unless -f $path;

        my $size = (stat $path)[7];
        push @{ $sizes{$size} }, $path;
    }

    for my $k (keys %sizes) {
        delete $sizes{$k} unless @{ $sizes{$k} } > 1;
    }

    return \%sizes;
}
0 голосов
/ 17 ноября 2009

bash-скрипт в этом случае более выразителен, чем perl:

md5sum * |sort -k1|uniq -w32 -d|cut -f2 -d' '|xargs rm
0 голосов
/ 17 ноября 2009

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

perl -MFile::Find -MDigest::MD5 -e '
my %m;
find(sub{
  if(-f&&-r){
   open(F,"<",$File::Find::name);
   binmode F;
   $d=Digest::MD5->new->addfile(F);
   if(exists($m{$d->hexdigest}){
     $m{$d->hexdigest}[5]++;
     push $m{$d->hexdigest}[0], $File::Find::name;
   }else{
     $m{$d->hexdigest} = [[$File::Find::name],0,0,0,0,1];
   }
   close F
 }},".");
 foreach $d (keys %m) {
   if ($m{$d}[5] > 1) {
     print "Probable duplicates: ".join(" , ",$m{$d}[0])."\n\n";
   }
 }'
0 голосов
/ 17 ноября 2009

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

http://priyank.co.in/remove-duplicate-files

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...