それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

Ruby標準パッケージでディレクトリ比較を作ってrsyncモドキをする


rubyの標準パッケージの状態でディレクトリ内のファイルの比較を考えた。
rsync的にディレクトリ間のファイルの有無を調べたいと思います。

SRCディレクトリとDESTディレクトリのファイル名一覧を取出し、


それ使ってSRCとDESTのファイルの違いを探したいと思いました。

二つのディレクトリを比較する

require 'fileutils'
require 'kconv'

src = 'C:/Users/takuya/Desktop/src/'
dest = 'C:/Users/takuya/Desktop/dest/'

#ファイル名一覧を作る
Dir.chdir src
src_files = Dir.glob "./**/*"
Dir.chdir dest
dest_files = Dir.glob "./**/*"

#########################
#src にあってDestにないもの
#########################
puts "#src にあってDestにないもの"
puts (src_files - dest_files)

#########################
#dest にあってsrcにないもの
#########################
puts "#dest にあってsrcにないもの"
puts (dest_files -src_files)

#####################################
#両方のディレクトリにあるファイル
#####################################

#二つともにあるもの
puts "#二つともにあるもの"
puts (dest_files & src_files)

#二つにあるモノのウチ中身が違うもの
puts "#二つにあるモノのウチ中身が違うもの"
puts (dest_files & src_files).reject{|e|
  FileUtils.cmp File.expand_path(e,src) , File.expand_path(e,dest)
}

#二つにあるモノのウチ中身が違う一覧中で、destの方が古いもの
puts "#二つにあるモノのウチ中身が違う一覧中で、destの方が古いもの"
puts (dest_files & src_files).reject{|e|
  FileUtils.cmp( File.expand_path(e,src) , File.expand_path(e,dest) )}.select{|e|
  (File.mtime(File.expand_path(e,src)) > File.mtime( File.expand_path(e,dest)))
  }
#両方にあるファイル名で中身が違うもので src の方が古いもの
puts "#両方にあるファイル名で中身が違うもので src の方が古いもの"
puts (dest_files & src_files).reject{|e|
  FileUtils.cmp( File.expand_path(e,src) , File.expand_path(e,dest) )}.select{|e|
  (File.mtime(File.expand_path(e,src)) < File.mtime( File.expand_path(e,dest)))
  }

#同じファイルが、違う名前で重複していたらTrueを返す
require 'digest/md5'
require "pp"
puts "#同じファイルが、違う名前で重複していたらTrueを返す"
a Dir.glob("C:/Users/takuya/Pictures/100MEDIA/**/*").map{|e| 
      File.expand_path(e,dest) 
  }.inject({}){|a,e| 
      key  = Digest::MD5.hexdigest(File.open(e ,"rb").read);
      a[key] = a.fetch(key,[]).push e
      a
  }.select{|k,v|
        true if v.size > 1 
  }

p Hash[a].size > 0

比較した一覧のファイルをコピーする

このファイル一覧を使って、いろいろなコピーが出来そうですよね。バックアップや同期に使えるかも。

SRC → DESTの一方向同期ならすぐ出来るんじゃないか

rsync -av --delete っぽいこと
require 'fileutils'

src = 'C:/Users/takuya/Desktop/src/'
dest = 'C:/Users/takuya/Desktop/dest/'
Dir.chdir src
src_files = Dir.glob "./**/*"
Dir.chdir dest
dest_files = Dir.glob "./**/*"

#先に作った一覧をつかってコピーすればいい

files =[]

## src → dest へ一方向同期の場合

#両方にあるファイルのうち、中身が違っていて、dest側が古いファイル一覧
files = (dest_files & src_files).reject{|e|
  FileUtils.cmp( File.expand_path(e,src) , File.expand_path(e,dest) )}.select{|e|
  (File.mtime(File.expand_path(e,src)) > File.mtime( File.expand_path(e,dest)))
  }

#srcにあって destに無いもの
files = files + (src_files - dest_files)
#srcファイルをdestに上書き
files.each{ |e| FileUtils.copy( File.expand_path(e,src) , File.expand_path(e,dest) ) }

#rsyncの --delete と同様のことをするには
(dest_files - src_files).each{ |e| FileUtils.remove( File.expand_path(e,dest) ) }

これでrsyncっぽいコトがrubyで出来るようになるね。配列の扱いが賢いのでruby 書きやすい。

windows 版 rsync こと cwRsync や cygwin Rsync はファイルのオーナーシップやセキュリティー設定が書き換わるのでなんか好きになれない。どうやって回避して良いか分からなかったので、Rubyでやりました。

追記

ファイルの中身を比較するととっても遅いです。2MBのデジカメ写真の比較をした場合
1000ファイルで計測

更新日時だけをチェックした場合
      user     system      total        real
  0.031000   0.453000   0.484000 (  1.242000)
ファイルの中身を比較した場合
      user     system      total        real

 20.920000  13.150000  34.070000 (178.394000)


驚きの差です。インデックスが使える更新日時と直接ファイルを開いてロードするのとではやっぱり、大きく違いますね。

2011-07-06 追記

Windows のRuby は mtime がミリ秒を返さないので,ファイルの時刻比較がうまくいかないことがある。mtime はWindowsの制約なのでなんとも・・・・

ruby の mtimeで解決を図るにはWin32APIの力を借りるしかなさそう
http://d.hatena.ne.jp/electrolysis/20070726/1185409408