2009年10月17日土曜日

重複ファイルを表示「difflatten」

ファイル名などには意味がなく、内容だけに意味があるファイルの集合に、同じルールで適当に集められたファイルをマージする話。(製作動機は後述)

概要

普通、ファイルが同じかどうかはdiffコマンドで調べることができる。
$ diff a.jpg b.jpg
しかし、最初に述べたような状況の場合、diff a.jpg storage/などとはできない。私は「echo storage/* | xargs diff a.jpg」と考えたがこれはstorage以下にディレクトリがあるとうまくいかない。また、一気に多数のファイルの重複を確認しようとした場合、ちょっと頭をひねらなければならない。

そこで、「ディレクトリAとディレクトリBの中のファイルで同じものがあれば、そのファイル名を出力する」スクリプトを書いた。外出っぽいけどちょっと見つからなかったので。

うちのWebページ
で配布してます。

以下のように実行すると、「A = B」のような行が出てくる。これは、二つのファイルが同じであることを示している。

$ difflatten.rb src/ dst/
src/1880.jpg = dst/images/sky/sunset14.jpeg
src/994.jpg = dst/images/material/takuan.jpg


これを応用すれば、以下のようにするとdst/以下のファイルでsrc/以下にもあるファイルは(dst側から)すべて削除できる。

$ difflatten.rb src/ dst/ | awk -F' = ' '{print $2}' | xargs rm


仕組み

今回はRubyで実装し、ライブラリは標準のものしか使っていない。
まず、src以下を再帰的に走査して、すべてのファイルのサイズを取得し、dst以下を走査して、srcと同じサイズのものがあれば、ダイジェスト(SHA512)で二つを比較している。当初はダイジェストのみで比較していたが、srcに500個、dstに2000個程度のファイルを入れて試したところ37秒もかかってしまった。そこで、まずサイズを比較することで、ダイジェストの計算回数を最小限に抑え、実行時間を0.2秒にまで縮めることに成功した。実際には、dstに同じファイルが2つ無い限り、ダイジェストより直接比較した方が速いのだが、そこまで速度的に不利にはならないと思われる。

動機とか

最近、というかずっと画像収集をしているのだが、あまり時間も裂けないのでとりあえずzipで落としてきて、「うーん」と思った奴は適宜削除、というスタンスをとっていた。しかし結構同じ画像が入っていることが多かったので、自分のコレクションに入れる前に重複を確認するスクリプトを作って既に持っている画像を弾き出そうと考えた。
もちろん、加工されるとハッシュ値が変わるので、本当は画像の類似度などを考慮する必要があるが、補助的には使えると思う。
今回は「削除する」ではなく「重複を表示する」だけに止めたので、いろんな使い方ができるかもしれない。

ちなみに、動作にはruby(作者環境1.8)がインストールされていること、diffなのに同じ物を表示してるじゃん!とか言わないこと、引数を必ず2個与えることが要求されます。

1 件のコメント: