この記事は、Googleの大規模言語モデル Gemini (モデル: gemini-2.5-flash-lite gemini-2.5-pro) と協力して作成しました。「大量の重複ファイルをどうやって効率的に見つけるか?」という実践的な課題について、対話を通じて解決策を導き出し、最終的にそのプロセス全体をブログ記事としてAIに執筆させています。この記事は、AIを単なる文章生成ツールとしてではなく、問題解決のパートナーとして活用した一例となります。
はじめに
「PCに散らばった大量の写真、整理していたら『これって同じ写真?』と不安になったことはありませんか?」
フォルダを分けてバックアップを繰り返しているうちに、同じファイルが複数の場所にコピーされてディスクスペースを圧迫していることはよくあります。一つひと つ手作業で確認するのは、ファイルが数千、数万ともなると現実的ではありません。
この記事では、開発者やコマンドラインに慣れている方向けに、find, md5, awkという3つの強力なツールを組み合わせて、大量のファイルの中から重複しているものを高速に見つけ出す方法を解説します。
調査の3ステップ
今回の手法は、大きく分けて3つのステップで進めます。
- ファイルリストと「指紋」の作成: 調査対象のすべてのファイルについて、内容の「指紋」となるハッシュ値を計算し、一覧を作成します。
- 重複のチェック: 作成したリストを照合し、どのファイルが重複しているか、どれがオリジナルかを判定します。
- 結果の確認: 判定結果をファイルに出力し、内容を確認します。
それでは、各ステップで使ったコマンドとテクニックを詳しく見ていきましょう。
Step 1: 全ファイルの「指紋」を作る (find と md5)
最初のステップでは、調査対象となるすべてのファイルのリストアップと、それぞれの内容を表す「指紋」の生成を同時に行います。ここで使うのが find と md5 コマンドです。
find ./pictures -type f -exec md5 -r {} + > all_files.txt
find: 強力なファイル探索ツール
findは、指定した場所から条件に合うファイルやディレクトリを再帰的に(サブディレクトリの奥深くまで)探し出してくれます。
./pictures: ファイルを探し始める場所を指定します。-type f: 検索対象を「ファイル(file)」に限定します。これにより、ディレクトリなどが結果に含まれるのを防ぎます。-exec ... {} +: findで見つかったファイルに対し、後続のコマンドを実行するためのオプションです。- {} は見つかったファイル名に置き換わるプレースホルダー。
- 末尾の + は、このコマンドのパフォーマンスを劇的に向上させるための重要な指定です。findが見つけたファイル名を可能な限り多くまとめてから後続のコマンド(ここではmd5)に渡してくれるため、コマンドの実行回数が大幅に減り、処理が高速になります。
md5: ファイル内容の「指紋」を生成
md5は、ファイルの内容からハッシュ値(Message Digest 5)と呼ばれる短い文字列を生成します。
- ハッシュ値とは?: ファイルの中身を元に計算される、そのファイルを代表する「指紋」のようなものです。ファイルの内容が1文字でも違えばハッシュ値は全く別のものになり、逆に内容が全く同じであれば、たとえファイル名が違っても必ず同じハッシュ値が生成されます。
- なぜ使うのか?: ファイル同士を比較する際、数GBにもなる大きなファイルの中身を1バイトずつ比べるのは非常に時間がかかります。しかし、ハッシュ値(MD5では16バイトの固定長)同士を比べるだけなら一瞬です。この性質を利用して、高速にファイルの同一性を判定します。
-rオプション: 出力のフォーマットを ハッシュ値 ファイル名という順番にしてくれます。このシンプルな形式が、後のawkでの処理に非常に都合が良いのです。
このステップの結果、all_files.txt には以下のような行が大量に書き込まれます。
1 d41d8cd98f00b204e9800998ecf8427e pictures/photoA.jpg
2 9e107d9d372bb6826bd81d3542a419d6 pictures/photoB.jpg
3 ...
Step 2: 指紋リストを照合して重複を見つける (awk)
全ファイルの指紋リストが手に入ったら、次はいよいよ重複の判定です。ここではテキスト処理の達人、awkの出番です。
まず、比較したい2つのディレクトリ(dir_Aとdir_B)のハッシュリストをそれぞれ用意したとします。
1 # dir_Aのハッシュリスト
2 find ./dir_A -type f -exec md5 -r {} + > dir_A_hashes.txt
3
4 # dir_B(比較対象)のハッシュリスト
5 find ./dir_B -type f -exec md5 -r {} + > dir_B_hashes.txt
そして、この2つのファイルを比較するためのawkスクリプトがこちらです。
# compare.awk
FNR == NR {
# 1つ目のファイル(dir_B_hashes.txt)を処理
# ハッシュ値をキー、ファイルパスを値として連想配列に保存
hashes[$1] = $2
next
}
{
# 2つ目のファイル(dir_A_hashes.txt)を処理
# 現在の行のハッシュ($1)が連想配列に存在するかチェック
if ($1 in hashes) {
# 存在すれば重複 -> duplicates.txt に出力
print $2 " is a duplicate of " hashes[$1] >> "duplicates.txt"
} else {
# 存在しなければオリジナル -> originals.txt に出力
print $2 >> "originals.txt"
}
}
このスクリプトを以下のコマンドで実行します。
awk -f compare.awk dir_B_hashes.txt dir_A_hashes.txt
awkの魔法:FNR == NR と連想配列
awkスクリプトの何がすごいのか、2つの重要なテクニックを解説します。
-
FNR == NR: 2つのファイルを効率的に処理するためのawkの常套句です。awkはファイルを1行ずつ処理しますが、NRは全ファイルを通した行番号、FNRは現在のファイル内での行番号を保持しています。つまり、1つ目のファイルを処理している間だけFNRとNRは等しくなります。これを利用して、「1つ目のファイルはすべて記憶し、2つ目のファイルで答え合わせをする」という流れを作っています。 -
連想配列 (
hashes[...]): awkの非常に強力な機能で、他の言語ではハッシュマップや辞書とも呼ばれます。hashes[$1] = $2の部分では、1列目のハッシュ値を「キー(見出し)」、2列目のファイルパスを「値(内容)」として、hashesという名前の配列にどんどん格納していきます。2つ目のファイルを処理する際、if ($1 in hashes)と書くだけで、「今調べているファイルのハッシュが、先ほど記憶した配列の中にキーとして存在するか?」を一瞬で検索できます。これが、何万行もあるリスト同士を高速に照合できる秘密です。
Step 3: 結果の確認
awkの処理が終わると、カレントディレクトリに2つのファイルが生成されます。
duplicates.txt: dir_Aの中で、dir_Bにも存在した重複ファイルの一覧。originals.txt: dir_Aにしか存在しなかったオリジナルファイルの一覧。
wc -l コマンドを使えば、それぞれのファイル数を簡単に確認できます。
1 $ wc -l duplicates.txt originals.txt
2 526 duplicates.txt
3 2 originals.txt
head コマンドを使えば、各ファイルの中身を少しだけ覗き見ることができます。
1 $ head -n 5 duplicates.txt
2 dir_A/photo01.jpg is a duplicate of dir_B/IMG_1234.jpg
3 dir_A/photo02.jpg is a duplicate of dir_B/IMG_1235.jpg
4 ...
まとめ
今回は、find, md5, awkという基本的なコマンドラインツールを組み合わせることで、大量のファイルから重複ファイルを効率的に見つけ出す方法を紹介しました。
findでファイルを網羅的にリストアップし、md5でファイル内容の「指紋」をとり、awkの連想配列を使って高速に照合する。
この手法は、写真の整理だけでなく、バックアップデータの整理やプロジェクト内の不要なファイルの洗い出しなど、様々な場面で応用できます。ぜひ、コマンドラインを使いこなし、効率的なデータ管理を実践してみてください!
以上です。
