重複排除スクリプト 1号

概要

ファイル数がめちゃくちゃ多い場合にも対応する
よりシンプルな重複排除スクリプトについて。

動作概要
  1. 渡されたファイルをキャッシュから探し、無ければ内容を読む。
  2. 内容が同じファイルがあったら「ハードリンク」する
  3. 1 にもどる
hashmarge.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.existFileTable():
      self.createFileTable()
    if not self.existCacheTable():
      self.createCacheTable()

  def commit(self):
    self.con.commit()

  def close(self):
    self.con.close()
    self.con = None

#
#filehash
#
  def existFileTable(self):
    sql = u"""SELECT * FROM sqlite_master WHERE type='table' AND name='filehash';"""
    c = self.con.execute(sql)
    for o in c:
      return True
    return False
  
  def createFileTable(self):
    sql = u"""create table filehash(path varchar(64) primary key, hash varchar(64));"""
    self.con.execute(sql)

  def insertFile(self,path,hash):
    sql = u"""insert into filehash values (?,?); """
    hxpath = hashlib.sha256(path).hexdigest()
    self.con.execute(sql,(hxpath,hash))
  
  def selectFile(self,path):
    sql = u"""select hash from filehash where path=?;"""
    hxpath = hashlib.sha256(path).hexdigest()
    c = self.con.execute(sql,(hxpath,))
    for o in c:
      return o[0]
    return None

#
#linkcache
#
  def existCacheTable(self):
    sql = u"""SELECT * FROM sqlite_master WHERE type='table' AND name='linkcache';"""
    c = self.con.execute(sql)
    for o in c:
      return True
    return False

  def createCacheTable(self):
    sql = u"""create table linkcache(hash varchar(64) primary key, path text);"""
    self.con.execute(sql)

  def insertCache(self,hash,path):
    sql = u"""insert into linkcache values (?,?)"""
    b64path = base64.b64encode(path)
    self.con.execute(sql,(hash,b64path))

  def selectCache(self,hash):
    sql = u"""select path from linkcache where hash=?;"""
    c = self.con.execute(sql,(hash,))
    for o in c:
      return base64.b64decode(o[0])
    return None

  def deleteCache(self,hash):
    sql = u"""delete from linkcache where hash=?;"""
    self.con.execute(sql,(hash,))

def calcSHA256(path):
  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 hashmarge(dbpath):
  filecount=0
  margecount=0
  registercount=0
  
  dbo = HmDBO(dbpath)

  for rwln in iter(sys.stdin.readline,""):
    path = rwln.rstrip('\n')
    if os.path.isfile(path):
      sys.stderr.write(path + '...')
      filecount += 1
      #filehash
      hash = dbo.selectFile(path)
      if hash == None:
        registercount += 1
        sys.stderr.write('register ')
        hash = calcSHA256(path)
        dbo.insertFile(path,hash)
      #linkcache
      cache = dbo.selectCache(hash)
      if cache == None:
        dbo.insertCache(hash,path)
      elif os.path.isfile(cache) and not os.path.samefile(cache, path):
        margecount += 1
        sys.stderr.write('marge ')
        os.remove(path)
        os.link(cache, path)
          

      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 rebuild(olddb,newdb):
  old = HmDBO(olddb)
  new = HmDBO(newdb)
  for rwln in iter(sys.stdin.readline,""):
    sys.stderr.write(rwln)
    path = rwln.rstrip('\n')
    if os.path.isfile(path):
      hash = old.selectFile(path)
      if hash <> None:
        new.insertFile(path,hash)
        cache = new.selectCache(hash)
        if cache == None:
          new.insertCache(hash, path)

  new.commit()
  old.close()
  new.close()

def main():
  if len(sys.argv) < 2:
    exit()
  elif len(sys.argv) == 2:
    hashmarge(sys.argv[1])
  elif len(sys.argv) == 3:
    rebuild(sys.argv[1], sys.argv[2])

if __name__ == '__main__':
  main()
使い方

前回と一緒

$ find <対象ディレクトリ> | hashmarge.py <dbファイル>
あとがき

前回のエントリで公開したスクリプトと比較して
こちらはファイル数が異常に多くても問題なく動作することが強み

シンプルなのでおそらくwindows移植も可能かも?
samefile()が未対応のため windowsでは動作しませんでした

なんとかしてみる

windowsでsamefile()する方法をググったら出てきた
Tim Golden's Python Stuff: See if two files are the same file

なるほど。

これでwindows対応版を作ればよさそうだ