web-dev-qa-db-ja.com

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

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

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

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

以下では、_md5_を使用して、現在のディレクトリ以下のすべてのファイルのMD5ダイジェストを生成します。

_find . -type f -exec md5 {} +
_

BSD _md5_ユーティリティがない場合は、_md5sum --tag_を_md5_に置き換えます。

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

_#!/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つのディレクトリが取得され、_md5.1_(または_md5.2_が指している場所)に_/tmp_および_$TMPDIR_というファイルが各ディレクトリに1つずつ生成されます。これらのファイルは、MD5ダイジェストでソートされます。

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

_MD5 (<path>) = <MD5 digest>
_

ファイルごとにそのような行が1つあります。

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

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

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

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

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

これをawkに直接渡して、2つのパスを解析することができます。

_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は、安全のためにあります。一度実行して何が起こるかを確認し、それが正しいことをしていると確信できる場合は、それを削除して再度実行します。

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

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

2
Kusalananda

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

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

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

2
VPfB