重複ファイルチェック
写真や動画のフォルダを整理したい。なるべくオリジナルを残そうとするあまり、
cp ./orig/photo.jpg ./flower.jpg
のように、リネームしてコピーすることが多い。そんなこんなで結構めちゃくちゃに重複が多い。
指定したディレクトリ以下にある同一バイナリを検出するRubyスクリプトを書いてみた。ちょっとコマンドラインで試したところ、GB単位になるとディスクI/Oの時間がバカにならないようなので、まずファイルサイズで同一バイナリと疑われるものをリストアップして、それらについてだけSHA1でハッシュを取って一致しているものだけを一覧表示するようにしてみた。
実行結果は以下。
% ls -lR .: 合計 8019 -rw-r--r-- 1 yarb yarb 2698 2009-08-02 14:48 code.txt -rw-r--r-- 1 yarb yarb 138 2009-08-02 15:37 find.rb -rwxr-xr-x 1 yarb yarb 1623 2009-08-02 17:39 findsame.rb -rw-r--r-- 1 yarb yarb 4095108 2009-08-02 15:02 intel.pdf -rw-r--r-- 1 yarb yarb 4095108 2009-08-02 15:02 intel2.pdf drwxr-xr-x 2 yarb yarb 128 2009-08-02 17:24 test ./test: 合計 4012 -rw-r--r-- 1 yarb yarb 2698 2009-08-02 17:24 code2.txt -rw-r--r-- 1 yarb yarb 2698 2009-08-02 16:09 fake.txt -rw-r--r-- 1 yarb yarb 4095108 2009-08-02 15:51 some.pdf % ./findsame.rb ./ /home/yarb/src/findsame/code.txt 2698 /home/yarb/src/findsame/test/fake.txt 2698 /home/yarb/src/findsame/intel.pdf 4095108 /home/yarb/src/findsame/intel2.pdf 4095108 /home/yarb/src/findsame/test/some.pdf 4095108 checked 8 files. % md5sum code.txt test/fake.txt test/code2.txt d8aa7a8ca3c39561f659afec5f10087a code.txt d8aa7a8ca3c39561f659afec5f10087a test/fake.txt 0174581b5f645602404de5a1aedd19cc test/code2.txt
上の例ではtest/code2.txtは、数十行のテキストファイルでcode.txtと1文字だけ変えてみたものでファイルサイズは同じ。期待通りの動作をしている。
スクリプトは以下。
#!/usr/local/bin/ruby # -*- coding: utf-8 -*- require 'find' require 'digest' class MyFile include Comparable attr_reader :sha1, :size def initialize(path, size) @path = path @size = size @sha1 = "" end def <=>(other) return @size - other.size end def calc_sha1 @sha1 = Digest::SHA1.hexdigest(File.read(@path)) end def to_s # @path+"\t"+@size.to_s+"\t"+@sha1+"\n" @path+"\t"+@size.to_s+"\n" end end def listup_files(dirs) list = [] dirs.each do |dir| unless (FileTest.directory?(dir)) raise("you can only specify directory names!") end Find.find(dir) do |f| if (File.ftype(f) == "file") list << MyFile.new(File.expand_path(f), File.size(f)) end end end list end def list_same_size(list) redun = [] tmp = 0.1 list.sort.each do |f| if f.size == tmp redun << f.size # we push all the files with the same size to redun end tmp = f.size end redun end def show_dup(redun, list) redun.uniq.each do |dup_size| sha1_list = [] num = 0 list.find_all {|f| f.size == dup_size}.each do |f| sha1_list << f.calc_sha1 num += 1 end if(num > sha1_list.uniq.size) then sha1_list.uniq.each do |sha1| next if list.find_all {|f| f.sha1 == sha1}.size == 1 list.find_all {|f| f.sha1 == sha1}.each do |f| print f end print "\n" end end end end ## main list = listup_files(ARGV) # can be multiple directories redun = list_same_size(list) show_dup(redun, list) print "checked "+list.size.to_s+" files.\n"
これだけ動かすのに2時間ぐらいかかった。Ruby力が足りていなくて、Comparableモジュールをincludeすべきところを、何を間違えたかEnumerableをincludeしたり、FileTestを発見するのに一手間かかったりと、予想したよりずっと時間がかかった。
実際に重複感が強いディレクトリの動画ファイルや写真ファイルはザクザクとリストアップできて、結構いい感じ。容量的には30GB中1GBとかで大したことないけど、無駄に分身がいっぱいあって、それらを引き継いでコピーし続けているようなイヤな感覚が除去できる。「オレのディスクはぐちゃぐちゃだ」という精神衛生上の問題が解決するかもしれない。