从单个目录中删除具有重复内容的文件[Perl或算法]

时间:2009-11-17 07:01:05

标签: perl

我有一个包含大量文件的文件夹,其中一些文件具有完全相同的内容。我想删除包含重复内容的文件,这意味着如果找到两个或多个内容重复的文件,我想留下其中一个文件,并删除其他文件。

以下是我想出来的,但我不知道它是否有效:),还没试过。

你会怎么做? 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 个答案:

答案 0 :(得分:8)

这是一个通用的算法(为了效率而编辑我已经摆脱了困境 - 我还修复了一个没有人报告的错误)...... :)

如果我将每个文件的内容与其他文件的内容进行比较,那么它将需要永远(更不用说大量的内存)。相反,为什么我们不首先将相同的搜索应用于它们的大小,然后比较相同大小的文件的校验和。

那么当我们 md5sum每个文件(参见Digest::MD5计算它们的大小时,我们可以使用哈希表为我们进行匹配,将匹配存储在arrayrefs中: / p>

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

答案 1 :(得分:6)

md5sum *.txt | perl -ne '
   chomp; 
   ($sum, $file) = split(" "); 
   push @{$files{$sum}}, $file; 
   END {
      foreach (keys %files) { 
         shift @{$files{$_}}; 
         unlink @{$files{$_}} if @{$files{$_}};
      }
   }
'

答案 2 :(得分:6)

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,则可以使用shell工具

find /path -type f -print0 | xargs -0 md5sum | \
    awk '($1 in seen){ print "duplicate: "$2" and "seen[$1] } \
         ( ! ($1 in  seen ) ) { seen[$1]=$2 }'

答案 3 :(得分:1)

主题的变化:

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

无需保留列表,只需从列表中删除一个并删除其余列表;只需跟踪您之前看到的内容,并删除与已经看过的总和相匹配的任何文件。 2限制拆分是使用包含空格的文件名做正确的事。

此外,如果您不相信这一点,只需将unlink更改为print,它就会输出要删除的文件列表。您甚至可以将输出发送到文件,如果看起来不错,则最后rm $(cat to-delete.txt)

答案 4 :(得分:1)

Perl对此有点矫枉过正:

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

(如果您缺少其中一些实用程序或它们没有这些标记/功能, 安装GNU findutils和coreutils。)

答案 5 :(得分:0)

我建议您在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";
   }
 }'

答案 6 :(得分:0)

在这种情况下,bash脚本比perl更具表现力:

md5sum * |sort -k1|uniq -w32 -d|cut -f2 -d' '|xargs rm

答案 7 :(得分:0)

这是一种按大小首先过滤并按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;
}

答案 8 :(得分:-2)

您可能希望了解我如何查找重复文件并将其删除。虽然你必须根据自己的需要进行修改。

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