同一のファイルをハードリンクに変換する

同一のファイルをハードリンクに変換する

1 つのディレクトリの下のツリーにたくさんの音楽があり、品質を保つために、最初に入手した形式で保存されています。2 つ目のディレクトリ ツリーは構造が似ていますが、すべてのファイルは携帯電話で再生可能な非可逆圧縮形式で、メタデータは時々変更されています (たとえば、スペースを節約するために埋め込まれたカバーを削除するなど)。

音楽のかなりの部分については、2 つのインスタンスに違いがないことに気がつきました。一般的に、配布バージョンが mp3/ogg のみで利用可能で、カバーが埋め込まれていない場合です。ハード ドライブの容量は安いかもしれませんが、それを無駄にする理由にはなりません。スクリプトを作成する方法はありますか。

  1. 2つのディレクトリ内の同一ファイルを確認する
  2. 同一のファイルが見つかった場合は、一方を他方へのハードリンクに置き換えます。
  3. 例えば、時間的な都合で完全な差分を取得する時間をかけずに
  4. しかし、ハッシュを比較するだけの場合など、同一ではない 2 つのファイルのコピーを誤って削除してしまうリスクはありませんか?

答え1

次の例でmd5は、現在のディレクトリまたはその下にあるすべてのファイルの MD5 ダイジェストを生成します。

find . -type f -exec md5 {} +

BSD ユーティリティがない場合はmd5に置き換えてください。md5sum --tagmd5

ディレクトリに対してこれを行うための簡単なスクリプトを作成しましょう。

#!/bin/bash

tmpdir=${TMPDIR:-/tmp}

if (( $# != 2 )); then
    echo 'Expected two directories as arguments' >&2
    exit 1
fi

i=0
for dir in "$@"; do
    (( ++i ))
    find "$dir" -type f -exec md5 {} + | sort -t '=' -k2 -o "$tmpdir/md5.$i"
done

これは、コマンド ラインで 2 つのディレクトリを受け取り、(または が指している場所)の各ディレクトリごとに 1 つずつ、 および というファイルmd5.1を生成します。これらのファイルは、MD5 ダイジェストに基づいてソートされます。md5.2/tmp$TMPDIR

ファイルは次のようになります

MD5 (<path>) = <MD5 digest>

ファイルごとに 1 行ずつあります。

次に、同じスクリプトで、2 つのファイル間のチェックサムを比較します。

join -t '=' -1 2 -2 2 "$tmpdir"/md5.[12]

これは、チェックサムを結合フィールドとして使用して、2 つのファイル間のリレーショナル「結合」操作を実行します。2 つのフィールドで同じチェックサムを持つ行はすべて結合されて出力されます。

両方のファイルのチェックサムが同じ場合は、次のように出力されます。

<space><MD5 digest>=MD5 (<path1>) =MD5 (<path2>)

これを直接渡してawk2 つのパスを解析することもできます。

awk -F '[()]' 'BEGIN { OFS="\t" } { print $2, $4 }'

は、 と-F [()]に基づいて各行をフィールドに分割することを意味します。これを行うと、フィールド 2 と 4 のパスが残ります。()

これは次のように出力されます

<path1><tab><path2>

次に、タブで区切られたパスのペアを読み取り、正しいコマンドを発行してリンクを作成します。

while IFS=$'\t' read -r path1 path2; do
    echo ln -f "$path1" "$path2"
done

要約すれば:

#!/bin/bash

tmpdir=${TMPDIR:-/tmp}

if (( $# != 2 )); then
    echo 'Expected two directories as arguments' >&2
    exit 1
fi

i=0
for dir in "$@"; do
    (( ++i ))
    find "$dir" -type f -exec md5 {} + | sort -t '=' -k2 -o "$tmpdir/md5.$i"
done

join -t '=' -1 2 -2 2 "$tmpdir"/md5.[12] |
awk -F '\\)|\\(' 'BEGIN { OFS="\t" } { print $2, $4 }' |
while IFS=$'\t' read -r path1 path2; do
    echo ln -f "$path1" "$path2"
done

rm -f "$tmpdir"/md5.[12]

インechoループwhileは安全のためにあります。一度実行して何が起こるかを確認し、正しく動作していると確信できる場合はインループを削除して再度実行してください。

ハードリンクはパーティションをまたぐことはできないことに注意してください。つまり、両方のディレクトリが同じパーティションに存在する必要があります。2番重複が見つかった場合、ディレクトリは上書きされます。結果に満足するまで、元のファイルのバックアップをどこかに保存してください。

(ファイル名にまたは)またはタブが含まれている場合、このソリューションは正しく機能しないことに注意してください。

答え2

非常に類似したファイルの大規模なコレクションがない限り、ハッシュを計算して比較しても重複を見つけるプロセスは高速化されません。最も遅い操作はディスク読み取りです。ハッシュを計算することはファイル全体を読み取ることを意味し、最新の暗号的に強力なハッシュでは CPU を集中的に使用するタスクでもあります。

ファイルの長さが異なる場合にのみ、データを比較する必要があります。指定された長さのファイルが 1 つしかない場合、明らかに重複はありません。ファイルが 2 つある場合は、単純に比較する方がハッシュよりも常に効率的です。ファイルが 3 つ以上ある場合は、比較の回数が増えますが、最初のバイトまたはブロックが異なる可能性が高いため、ディスク I/O は依然として低く、繰り返しの読み取りがキャッシュから返されます。

そのため、長さ + パス名のリストを準備して再帰的なディレクトリ リストを作成し、リストを数値順に並べ替え、最後にペアで比較して同じ長さを共有するファイルのセットのみを処理することをお勧めします。2 つのファイルが一致する場合、1 つをハードリンクに置き換えることができます。

関連情報