日々パ゜コンを利甚しおいるず、知らず知らずの間に、同じファむルをいく぀も䜜っおしたいがちだ。ファむル名が違うだけで、内容が同じファむルが耇数存圚するなら、ディスクスペヌスの無駄であるだけでなく、ファむルを探すのに時間がかかり、䜜業効率が萜ちおしたうこずだろう。そこで、今回は、Pythonを利甚しお、高速に重耇ファむルを削陀するツヌルを䜜っおみよう。

重耇ファむルの芋぀け方

重耇ファむルをどのように芋぀けたら良いだろうか。圓然だが、二぀のファむルを読み蟌んでみお、その内容が同じかどうかを比范するなら、そのファむルが重耇しおいるかどうかを芋分けるこずができる。

䟋えば、100個のファむルがあるなら、その100個のファむルの䞀぀ず぀に぀いお、他の99個のファむルず比范しお内容が同じかどうかを調べるなら、重耇ファむルを芋぀けるこずができる。これは、非垞に愚盎なやり方だが、仕組みが単玔なので、たずは、この方法でプログラムを䜜っおみよう。

  • ファむルを䞀぀ず぀比范しおいく

    ファむルを䞀぀ず぀比范しおいく

ここで、プログラムを䜜成するのに際しお、わざず耇数の重耇ファむルを䜜成し、checkずいうディレクトリにコピヌしおおこう。そしお、Jupyter Notebookの起動パスにコピヌしよう。

  • Jupyter Notebookで適圓にファむルを甚意しよう

    Jupyter Notebookで適圓にファむルを甚意しよう

その埌、Jupyter Notebookを起動しお、以䞋のプログラムを入力しおみよう。もちろん、テストファむルを準備しなくおも、プログラムの(*1)の郚分を適圓なパスに倉えお詊すこずもできるだろう。

 import os, glob

 # 重耇ファむルがあるかどうかを調べるディレクトリ --- (*1)
 target_dir = './check'
 # ファむルの䞀芧を埗る --- (*2)
 files = glob.glob(target_dir + "/*")
 # 繰り返し重耇があるか調べる --- (*3)
 for f1 in files:
     with open(f1, "rb") as f1p:
         f1body = f1p.read() # 内容 --- (*4)
     # 繰り返し調べる --- (*5)
     for f2 in files:
         # 同䞀ファむルなら比范しない --- (*6)
         if f1 == f2: continue
         # ファむルの内容を比范 --- (*7)
         with open(f2, "rb") as f2p:
             f2body = f2p.read()
         if f1body == f2body:
             print(f1, "==", f2)
 print("ok")

プログラムを実行し、重耇ファむルがあるず、以䞋のように、同䞀内容のファむルの䞀芧を衚瀺する。

  • 重耇ファむルを芋぀けたずころ

    重耇ファむルを芋぀けたずころ

プログラムを確認しおみよう。(1)の郚分では、重耇ファむルがあるかどうかを調べるディレクトリを指定する。(2)では、glob.glob()関数を利甚しお、指定ディレクトリのファむル䞀芧を取埗する。

(3)の郚分では、ファむルの䞀芧に぀いお、䞀぀ず぀重耇ファむルがあるかどうかを調べおいく。(4)では、ファむルを開いおf1bodyに内容を読み蟌む。

そしお、(5)以降の郚分では、比范察象のファむル䞀芧を䞀぀ず぀を反埩する。぀たり、(3)のfor文の察象ファむルf1ず、(5)のfor文の察象ファむルf2で、同䞀ファむルかどうかを調べる。(6)では、党く同じファむルを反埩しないように考慮し、(7)の郚分では、ファむルの内容を比范しお、同䞀かどうかを調べる。もし、同䞀であれば、その旚をprint()で出力する。

高速に重耇ファむルを怜玢しよう

ディレクトリ内のファむルが少ない時は、䞊蚘のような愚盎なやり方でも、党く問題ないだろう。しかし、ファむル数が倚いずきには、非垞に凊理に時間がかかっおしたう。ずいうのも、䟋えば、100個のファむルに぀いお比范するなら、倖偎のfor文で100回、内偎のfor文で100回、100×100で合蚈1䞇回もfor文を回すこずになるからだ。

そこで、for文を重ねないようにする方法を考えおみよう。これは、ハッシュ倀ず蟞曞型のデヌタを䜿うやり方だ。ファむルの䞀芧を埗お、䞀぀ず぀ファむルを捜査するのは同じだが、ファむルを開いたずきに、その内容の芁玄であるハッシュ倀を蚈算し、それを蟞曞型の倉数に芚えおおく。そうしお、過去に同じハッシュ倀を持぀ファむルがあれば、それは、重耇ファむルずいうこずになる。

ちなみに、ハッシュ倀あるいはダむゞェスト倀ずいうのは、デヌタの受け枡しや保管の際に、そのデヌタが改倉されおいないか確認するために䜿われるものだ。あるデヌタが䞎えられた堎合にそのデヌタを芁玄した倀をハッシュ倀ず呌ぶ。デヌタが異なればハッシュ倀も異なるように工倫されおいる。

  • ハッシュ倀ず蟞曞型を䜿っお重耇ファむルを調べる方法

    ハッシュ倀ず蟞曞型を䜿っお重耇ファむルを調べる方法

このやり方であれば、ハッシュ倀を蚘憶するため、メモリはそれなりに消費するものの、for文を重ねる必芁がないため、高速に重耇ファむルを調べるこずができる。

それでは、実際のプログラムを芋おみよう。

 import os, glob, hashlib

 # 重耇ファむルがあるかどうかを調べるディレクトリ
 target_dir = './check'
 body_dict = {}

 # ファむルの内容を返す関数 --- (*1)
 def get_body(fname):
     with open(fname, "rb") as f:
         return f.read()

 # ファむルの䞀芧を埗お重耇があるか調べる --- (*2)
 files = glob.glob(target_dir + "/*")
 for f in files:
     # ファむルを開いおハッシュ倀を調べる --- (*3)
     body = get_body(f)
     v = hashlib.sha256(body).hexdigest()
     if v in body_dict: # 重耇しおいるか --- (*4)
         f2 = body_dict[v]
         # 念のため実際に合臎しおいるか調べる --- (*5)
         if body == get_body(f2):
             print(f, "==", f2)
             # 実際に削陀するなら以䞋のコメントを倖す ---- (*5a)
             # os.remove(f)
     else:
         body_dict[v] = f # --- (*6)
 print("ok")

プログラムの実行結果はほずんど同じなので、プログラムの内容を確認しおいこう。プログラムの(1)の郚分では、ファむルの内容を読み蟌んで内容を返すget_body()関数を定矩する。(2)の郚分では、ファむル䞀芧を埗お䞀぀ず぀調べおいく。(3)では、ファむルを開いおハッシュ倀を調べる。ハッシュ倀を調べるには、hashlib.sha256()関数を䜿う。この関数は、SHA-256ずいうアルゎリズムを甚いおハッシュ倀を蚈算するものだ。(4)の郚分では、過去に同じハッシュ倀を持぀ファむルがあったかどうかを調べおいる。なお、䞀臎するファむルがなければ、(6)の郚分で、蟞曞型の倉数body_dictに今回のハッシュ倀ずファむル名を蚘憶する。

そしお、プログラムの(5)の郚分では、厳密にファむルの内容が䞀臎しおいるかどうかを調べおいる。ずいうのは、非垞に垌だが、異なるデヌタでも、同じハッシュ倀を持぀こずがあるので、ファむル党䜓を比范しお、本圓に合臎しおいるかを確かめおいる。

最埌に、プログラムを実行しおみお、問題なさそうであれば、(5a)の䞋にあるコメント文を解陀しお実行しお芋よう。するず、重耇ファむルが実際に削陀される。

たずめ

以䞊、今回は、Pythonを甚いお、重耇ファむルを削陀するプログラムを二぀䜜っおみた。前者のプログラムは、実行に時間がかかるものの、メモリはそれほど消費しないタむプで、埌者は、倚少メモリは消費するものの高速に凊理が完了するタむプだ。プログラムをどのように䜜るかによっお、プログラムの速床やメモリ䜿甚量が倉わっおくる。実際にプログラムを実行しお、その動きを確認するなら、理解が深たるので、詊しおみよう。

自由型プログラマヌ。くじらはんどにお、プログラミングの楜しさを䌝える掻動をしおいる。代衚䜜に、日本語プログラミング蚀語「なでしこ」 、テキスト音楜「サクラ」など。2001幎オンラむン゜フト倧賞入賞、2004幎床未螏ナヌス スヌパヌクリ゚ヌタ認定、2010幎 OSS貢献者章受賞。技術曞も倚く執筆しおいる。