重複排除スクリプト 2号
概要
重複ファイルの発見から重複の除去までを全自動で行うため
指定のファイルリストの重複チェックを行い、
見つけた重複ファイルは自動的に「ハードリンク」するスクリプトを書いた
半年以上動作しているのでそこそこ安定しているはず
「ハードリンク」の説明
例えば内容が同じ"./A/B/hoge.avi"と"./C/D/puga.avi"を「ハードリンク」すると
それぞれのパスはそのままで、ファイルの内容が共有される。
ショートカット機能との違いは「どっちも本物」な事。
スクリプト
以下のスクリプトを dedup.py 等として保存する
#!/usr/bin/python # -*- coding: utf-8 -*- import sqlite3,sys,os,pickle,hashlib,base64 class HmDBO: con = None def __init__(self,dbpath): self.con = sqlite3.connect(dbpath) if not self.existFileInfoTable(): self.createFileInfoTable() def commit(self): self.con.commit() def close(self): self.con.close() self.con = None # #fileinfo # def existFileInfoTable(self): sql = u"""SELECT * FROM sqlite_master WHERE type='table' AND name='fileinfo';""" c = self.con.execute(sql) for o in c: return True return False def createFileInfoTable(self): sql = u""" create table fileinfo( hxpath varchar(64) primary key, b64path text, size integer, hash varchar(64), st_dev integer, st_ino integer );""" self.con.execute(sql) sql = u"""create index sizeindex on fileinfo(size);""" self.con.execute(sql) sql = u"""create index devindex on fileinfo(st_dev,st_ino);""" self.con.execute(sql) sql = u"""create index size2index on fileinfo(size,hash,st_dev);""" self.con.execute(sql) sql = u"""create index inoindex on fileinfo(st_ino);""" self.con.execute(sql) sql = u"""create index hashindex on fileinfo(hash,st_dev);""" self.con.execute(sql) def insertFileInfo(self,path,hash,st): sql = u"""insert into fileinfo values (?,?,?,?,?,?); """ hxpath = hashlib.sha256(path).hexdigest() b64path = base64.b64encode(path) self.con.execute(sql,(hxpath,b64path,st.st_size,hash,st.st_dev,st.st_ino)) def updateFileInfo(self,path,hash,st): sql = u"""update fileinfo set size = ?, hash = ?,st_dev = ?,st_ino = ? where hxpath = ?; """ hxpath = hashlib.sha256(path).hexdigest() self.con.execute(sql,(st.st_size,hash,st.st_dev,st.st_ino,hxpath)) def deleteFileInfo(self,path): sql = u"""delete from fileinfo where hxpath = ?;""" hxpath = hashlib.sha256(path).hexdigest() self.con.execute(sql,(hxpath,)) def selectFileInfo(self,path): sql = u"""select size,hash,st_dev,st_ino from fileinfo where hxpath=?;""" hxpath = hashlib.sha256(path).hexdigest() c = self.con.execute(sql,(hxpath,)) for o in c: return (o[0],o[1],o[2],o[3]) return (None,None,None,None) def searchHashCode(self,st): sql = u""" select b64path,hash from fileinfo where st_dev = ? and st_ino = ? and hash is not null ;"""; c = self.con.execute(sql,(st.st_dev,st.st_ino)) ret = list() for o in c: path = base64.b64decode(o[0]) hash = o[1] if os.path.isfile(path): return hash else: self.deleteFileInfo(path) return None def listupFileSize(self): sql = u""" select size from fileinfo group by size having count(size) > 1 and count(st_ino) > 1 ;""" c = self.con.execute(sql) for size in c: yield size[0] def searchFileFromSize(self,size): sql = u""" select b64path from fileinfo where size = ? ; """ c = self.con.execute(sql,(size,)) for o in c: yield base64.b64decode(o[0]) def searchFileDupHash(self): sql = u""" select hash,st_dev from fileinfo group by size,hash,st_dev having count(st_ino) > 1 ;""" c = self.con.execute(sql) for o in c: yield (o[0],o[1]) def searchFileFromHashOne(self,hash,st_dev): sql = u""" select b64path,st_ino from fileinfo where hash = ? and st_dev = ? ; """ c = self.con.execute(sql,(hash,st_dev)) for o in c: return (base64.b64decode(o[0]), o[1]) return None def searchDedupFiles(self,hash,st_dev,st_ino): sql = u""" select b64path from fileinfo where hash = ? and st_dev = ? and st_ino != ? ;""" c = self.con.execute(sql,(hash,st_dev,st_ino)) for o in c: yield base64.b64decode(o[0]) _registercount = 0 def calcSHA256(dbo,path,st): global _registercount uhash = dbo.searchHashCode(st) if uhash != None : return uhash sys.stderr.write('.') _registercount += 1 sha = hashlib.sha256() fp = open(path,'r') while True: cache = fp.read(65536) if not cache: break sha.update(cache) fp.close() return sha.hexdigest() def updateCache(dbo,path,calcHash): size,hash,st_dev,st_ino = dbo.selectFileInfo(path) st = os.stat(path) if size == None: if calcHash and hash == None: hash = calcSHA256(dbo,path,st) dbo.insertFileInfo(path,hash,st) else: dbo.insertFileInfo(path,None,st) else: if (calcHash and hash == None) or hash != None and (size != st.st_size or st_dev != st.st_dev or st_ino != st.st_ino): hash = calcSHA256(dbo,path,st) dbo.updateFileInfo(path,hash,st) return (st.st_size, hash, st.st_dev, st.st_ino) def releaseVer(): return True def hashmarge(dbpath): margecount=0 filecount=0 global _registercount _registercount=0 dbo = HmDBO(dbpath) #register size for rwln in iter(sys.stdin.readline,""): path = rwln.rstrip('\n') if os.path.isfile(path): sys.stderr.write('stat ' + path + '...') filecount += 1 #fileinfo updateCache(dbo,path,False) sys.stderr.write(' done\n') dbo.commit() #update hash updates = list() for size in dbo.listupFileSize(): for path in dbo.searchFileFromSize(size): updates.append(path) updates.sort() for path in updates: sys.stderr.write('read ' + path + '...') if os.path.isfile(path): updateCache(dbo,path,True) sys.stderr.write(' done\n') else: dbo.deleteFileInfo(path) sys.stderr.write(' not found\n') dbo.commit() #linking for hash,st_dev in dbo.searchFileDupHash(): (centerPath,c_ino) = dbo.searchFileFromHashOne(hash,st_dev) for tpath in dbo.searchDedupFiles(hash,st_dev,c_ino): sys.stderr.write('link ' + tpath + '...') if releaseVer(): os.remove(tpath) os.link(centerPath,tpath) updateCache(dbo,tpath,True) margecount += 1 sys.stderr.write(' done\n') dbo.commit() dbo.close() sys.stderr.write('files = ' + str(filecount) + '\n') sys.stderr.write('register = ' + str(_registercount) + '\n') sys.stderr.write('marges = ' + str(margecount) + '\n') def main(): if len(sys.argv) < 2: exit() elif len(sys.argv) == 2: hashmarge(sys.argv[1]) if __name__ == '__main__': main()
使い方
ファイルリストはパイプ経由で渡してやり、
DBファイルのパスをパラメータで指定してやる形で利用する
$ find <ターゲットディレクトリ> | dedup.py <dbファイルのパス>
動作概要
- 受け取ったファイルリストのファイルサイズをdbに格納
- db中の同一サイズのファイルを読み重複チェック
- 重複していたファイルをハードリンク
注意点
ファイル数が5万以下ならそこそこ使えますが、
ファイル数があまりにも多くなると
内容が異なるファイル同士でファイルサイズの被りが発生するせいで
処理が遅くなってしまいます。
os.stat()が未対応のため windowsでは動作しませんでした
ESXi と KVM でベンチマーク比較してみた
KVMはwindowsの仮想化がすごく苦手というイメージがあります、
そこでESXiに乗り換えればどの程度改善するかベンチマークしてみました。
テスト環境
- ホストPC NEC Express5800/S70
- ゲストOS windows server 2008r2 sp1 試用版
- ゲスト割当HDD 40GB
- ゲスト割当CPU 1core
- ゲスト割当MEM 4GB
- ゲストOSにはリモートデスクトップで接続
以上の条件で、CrystalMark 2004R3 を実行しました。
HDD 性能について
どちらもハイパーバイザ側でのディスクキャッシュが存在するため
正確な測定は難しいです。
※追記参照
参照
実機での数値はこちらのエントリを参考にしました。
http://damegamer.com/034
ubuntu 12.04 で lxc を使ってみた(natモード)
lxc は 12.04でびっくりするほど導入しやすくなってます、
今回はnatモードで動作するコンテナを作ってみました。
lxc インストール
$ sudo apt-get install lxc
ゲスト(コンテナ) インストール
以下形式です
sudo lxc-create -n <コンテナ名> -t <テンプレート名>
$ sudo lxc-create -n lxtest -t ubuntu
ネットワークの設定
lxcインストール時点で lxcbr0(例:10.0.3.0/24) が構築されています
ここに繋ぐ時のIPアドレス(例:10.0.3.10)を以下のファイルに追加してやります
/var/lib/lxc/<コンテナ名>/config
$ sudo vim /var/lib/lxc/lxtest/config lxc.network.ipv4 = 10.0.3.10 #この行を追加
起動する
以下形式です
sudo lxc-start -n <コンテナ名> -d
$ sudo lxc-start -n lxtest -d
本体起動時に自動起動させる
autostart させるために、シンボリックリンクを作成します
/var/lxc/auto/<コンテナ名>.conf 形式のリンクを作成します
$ sudo ln -s /var/lib/lxc/lxtest/config /var/lxc/auto/lxtest.conf
まとめ
仮想natの構築が自動で行われるため、非常に手軽に導入できるようになりました、
レンタルvps内での仮想化やノートPCにテスト環境作る時に便利そうです。
turnkey linuxをlxcで利用する
lxcでturnkey linuxを利用する方法が載ってました、
turnkey linuxのisoファイルをスクリプトでlxcコンテナに変換するみたいですね。
http://www.turnkeylinux.org/forum/general/20111016/script-create-turnkeylinux-lxc-containers
lxcなら最新カーネルでも利用できますし、いろいろ便利に使えそうです。