簡體   English   中英

使用Perl清理具有一個或多個重復項的文件系統

[英]Using Perl to cleanup a filesystem with one or more duplicates

我有兩個磁盤,一個是臨時備份磁盤,到處都是重復的亂七八糟的東西,而筆記本電腦中的另一個磁盤也一樣。 我需要備份唯一文件並刪除重復項。 因此,我需要執行以下操作:

  • 查找所有非零大小的文件
  • 計算所有文件的MD5摘要
  • 查找文件名重復的文件
  • 將唯一文件與主副本和其他副本分開。

使用此腳本的輸出,我將:

  • 備份唯一文件和主文件
  • 刪除其他副本

唯一文件 =沒有其他副本

主副本 =第一個實例,如果存在其他副本,則可能匹配優先路徑

其他副本 =非原版

我創建了附加腳本,這對我來說似乎很有意義,但是:

文件總數!=唯一文件+主副本+其他副本

我有兩個問題:

  1. 我的邏輯錯誤在哪里?
  2. 有更有效的方法嗎?

我選擇了磁盤哈希,以便在處理大量文件列表時不會耗盡內存。

#!/usr/bin/perl

use strict;
use warnings;
use DB_File;
use File::Spec;
use Digest::MD5;

my $path_pref = '/usr/local/bin';
my $base = '/var/backup/test';

my $find = "$base/find.txt";
my $files = "$base/files.txt";

my $db_duplicate_file = "$base/duplicate.db";
my $db_duplicate_count_file = "$base/duplicate_count.db";
my $db_unique_file = "$base/unique.db";
my $db_master_copy_file = "$base/master_copy.db";
my $db_other_copy_file = "$base/other_copy.db";

open (FIND, "< $find");
open (FILES, "> $files");

print "Extracting non-zero files from:\n\t$find\n";
my $total_files = 0;
while (my $path = <FIND>) {
  chomp($path);
  next if ($path =~ /^\s*$/);
  if (-f $path && -s $path) {
    print FILES "$path\n";
    $total_files++;
    printf "\r$total_files";
  }
}

close(FIND);
close(FILES);
open (FILES, "< $files");

sub compare {
  my ($key1, $key2) = @_;
  $key1 cmp $key2;
}

$DB_BTREE->{'compare'} = \&compare;

my %duplicate_count = ();

tie %duplicate_count, "DB_File", $db_duplicate_count_file, O_RDWR|O_CREAT, 0666, $DB_BTREE
     or die "Cannot open $db_duplicate_count_file: $!\n";

my %unique = ();

tie %unique, "DB_File", $db_unique_file, O_RDWR|O_CREAT, 0666, $DB_BTREE
     or die "Cannot open $db_unique_file: $!\n";

my %master_copy = ();

tie %master_copy, "DB_File", $db_master_copy_file, O_RDWR|O_CREAT, 0666, $DB_BTREE
     or die "Cannot open $db_master_copy_file: $!\n";

my %other_copy = ();

tie %other_copy, "DB_File", $db_other_copy_file, O_RDWR|O_CREAT, 0666, $DB_BTREE
     or die "Cannot open $db_other_copy_file: $!\n";

print "\nFinding duplicate filenames and calculating their MD5 digests\n";

my $file_counter = 0;
my $percent_complete = 0;

while (my $path = <FILES>) {

  $file_counter++;

  # remove trailing whitespace
  chomp($path);

  # extract filename from path
  my ($vol,$dir,$filename) = File::Spec->splitpath($path);

  # calculate the file's MD5 digest
  open(FILE, $path) or die "Can't open $path: $!";
  binmode(FILE);
  my $md5digest = Digest::MD5->new->addfile(*FILE)->hexdigest;
  close(FILE);

  # filename not stored as duplicate
  if (!exists($duplicate_count{$filename})) {
    # assume unique
    $unique{$md5digest} = $path;
    # which implies 0 duplicates
    $duplicate_count{$filename} = 0;
  }
  # filename already found
  else {
    # delete unique record
    delete($unique{$md5digest});
    # second duplicate
    if ($duplicate_count{$filename}) {
      $duplicate_count{$filename}++;
    }
    # first duplicate
    else {
      $duplicate_count{$filename} = 1;
    }
    # the master copy is already assigned
    if (exists($master_copy{$md5digest})) {
      # the current path matches $path_pref, so becomes our new master copy
      if ($path =~ qq|^$path_pref|) {
        $master_copy{$md5digest} = $path;
      }
      else {
        # this one is a secondary copy
        $other_copy{$path} = $md5digest;
        # store with path as key, as there are duplicate digests
      }
    }
    # assume this is the master copy
    else {
      $master_copy{$md5digest} = $path;
    }
  }
  $percent_complete = int(($file_counter/$total_files)*100);
  printf("\rProgress: $percent_complete %%");
}

close(FILES);    

# Write out data to text files for debugging

open (UNIQUE, "> $base/unique.txt");
open (UNIQUE_MD5, "> $base/unique_md5.txt");

print "\n\nUnique files: ",scalar keys %unique,"\n";

foreach my $key (keys %unique) {
  print UNIQUE "$key\t", $unique{$key}, "\n";
  print UNIQUE_MD5 "$key\n";
}

close UNIQUE;
close UNIQUE_MD5;

open (MASTER, "> $base/master_copy.txt");
open (MASTER_MD5, "> $base/master_copy_md5.txt");

print "Master copies: ",scalar keys %master_copy,"\n";

foreach my $key (keys %master_copy) {
  print MASTER "$key\t", $master_copy{$key}, "\n";
  print MASTER_MD5 "$key\n";
}

close MASTER;
close MASTER_MD5;

open (OTHER, "> $base/other_copy.txt");
open (OTHER_MD5, "> $base/other_copy_md5.txt");

print "Other copies: ",scalar keys %other_copy,"\n";

foreach my $key (keys %other_copy) {
  print OTHER $other_copy{$key}, "\t$key\n";
  print OTHER_MD5 "$other_copy{$key}\n";
}

close OTHER;
close OTHER_MD5;

print "\n";

untie %duplicate_count;
untie %unique;
untie %master_copy;
untie %other_copy;

print "\n";

查看算法,我想我明白了您為什么泄漏文件。 第一次遇到文件副本時,將其標記為“唯一”:

if (!exists($duplicate_count{$filename})) {
   # assume unique
   $unique{$md5digest} = $path;
   # which implies 0 duplicates
   $duplicate_count{$filename} = 0;
}

下次,您刪除該唯一記錄,而不存儲路徑:

 # delete unique record
delete($unique{$md5digest});

因此,無論$ unique {$ md5digest}中的文件路徑是什么,您都會丟失它,並且不會將其包含在unique + other + master中。

您將需要以下內容:

if(my $original_path = delete $unique{$md5digest}) {
    // Where should this one go?
}

另外,正如我在上面的評論中提到的那樣, IO :: File確實可以清理此代碼。

這並不是對程序更大邏輯的真正回應,但是您應該每次都檢查open錯誤(而在我們這樣做時,為什么不使用帶有詞法文件句柄和三個參數的更現代形式的open ):

open my $unique, '>', "$base/unique.txt"
  or die "Can't open $base/unique.txt for writing: $!";

如果您不想每次都明確詢問,也可以簽出autodie模塊。

一種明顯的優化方法是使用文件大小作為初始比較的基礎,並且僅將計算機MD5用於小於特定大小的文件,或者如果兩個相同大小的文件發生沖突。 磁盤上給定文件越大,MD5計算成本就越高,但是其確切大小與系統上另一個文件沖突的可能性也就越小。 這樣您可以節省很多運行時間。

您可能還想考慮對某些包含嵌入式元數據的文件進行更改,而這些文件可能會在不更改基礎數據的情況下進行更改,因此即使MD5不匹配,您也可以找到其他重復項。 我說的當然是MP3或其他具有元數據標簽的音樂文件,這些元數據標簽可能會由分類器或播放器程序更新,但否則包含相同的音頻位。

有關抽象解決方案的相關數據,請參見此處。

https://stackoverflow.com/questions/405628/what-is-the-best-method-to-remove-duplicate-image-files-from-your-computer

重要說明 ,盡管我們希望相信兩個具有相同MD5的文件都是同一文件,但這不一定是正確的。 如果您的數據對您來說意味着什么,將其分解為MD5告訴您相同文件的候選列表后,您需要線性瀏覽這些文件的一位以檢查它們是否確實相同。

這樣說,給定一個大小為1位的哈希函數(MD5是MD5),只有2種可能的組合。

0 1

如果您的哈希函數告訴您2個文件都返回“ 1”,則您不會認為它們是同一文件。

給定2位的散列,只有4種可能的組合,

 00  01 10 11 

2返回相同值的文件,您將不會認為它們是同一文件。

給定3位哈希,只有8種可能的組合

 000 001 010 011 
 100 101 110 111

2個文件返回相同的值,您將不會認為它們是同一文件。

這種模式不斷增加,以至於人們出於某種奇怪的原因開始將“機會”加入到方程式中。 即使在128位(MD5),2個文件共享相同散列值並不意味着他們其實都是同一個文件。 唯一知道的方法是比較每一位。

如果您從頭到尾閱讀它們,則會發生次要的優化 ,因為一旦發現不同的位,您就可以停止閱讀,但是要確認相同,則需要閱讀每一位。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM