ディレクトリの内容の MD5 サムを 1 つの合計として取得するにはどうすればよいですか?

ディレクトリの内容の MD5 サムを 1 つの合計として取得するにはどうすればよいですか?

md5sum プログラムはディレクトリのチェックサムを提供しません。サブディレクトリ内のファイルを含むディレクトリの内容全体に対して単一の MD5 チェックサムを取得したいと考えています。つまり、すべてのファイルから作成された 1 つの結合チェックサムです。これを実行する方法はありますか?

答え1

正しい方法は、なぜ質問するのかによって異なります。

オプション 1: データのみを比較する

ツリーのファイル内容のハッシュだけが必要な場合は、次のようにします。

$ find -s somedir -type f -exec md5sum {} \; | md5sum

これは、まずすべてのファイルの内容を予測可能な順序で個別に要約し、次にファイル名と MD5 ハッシュのリストを渡してハッシュし、ツリー内のいずれかのファイルのコンテンツが変更された場合にのみ変更される単一の値を提供します。

残念ながら、find -smacOS、FreeBSD、NetBSD、OpenBSD で使用される BSD find(1) でのみ動作します。GNU または SUS find(1) を搭載したシステムで同等のものを取得するには、少し醜いものが必要です。

$ find somedir -type f -exec md5sum {} \; | sort -k 2 | md5sum

find -sへの呼び出しを追加することで、BSD の動作を模倣しましたsort-k 2ビットは MD5 ハッシュをスキップするように指示するため、フィールド 2 から行末までのファイル名のみが のsort計算によってソートされます。

このバージョンのコマンドには弱点があり、改行を含むファイル名があると、呼び出しが複数行のように見えるため混乱する傾向がありますsort。このfind -sバリアントでは、ツリーのトラバーサルとソートが同じプログラム内で行われるため、この問題はありませんfind

いずれの場合も、誤検出を避けるためにソートが必要です。最も一般的な Unix/Linux ファイルシステムは、ディレクトリ一覧を安定した予測可能な順序で維持しません。lsディレクトリの内容を暗黙的にソートする などを使用すると、このことに気付かないかもしれません。find何らかの方法で出力をソートせずに を呼び出すと、出力の行の順序は、基盤となるファイルシステムが返す順序と一致します。そのため、入力として与えられたファイルの順序が変わると、データが同一で​​あっても、このコマンドは変更されたハッシュ値を返します。

-k 2上記のGNU コマンドのビットは必要かどうか疑問に思うかもしれませんsort。ファイル データのハッシュは、内容が変更されていない限りファイル名の適切なプロキシであるため、このオプションを削除すれば誤検知は発生せず、GNU と BSD の両方で同じコマンドを使用できます。ただし、ハッシュの衝突が発生した場合、ファイル名の正確な順序が、このオプションを使用しない場合に得られる部分的な順序と一致しないsort可能性がわずかにあります (MD5 では 1:2 128-k 2 )。ただし、このようなわずかな不一致の可能性がアプリケーションにとって問題になる場合は、このアプローチ全体がおそらく問題外であることに留意してください。

コマンドを または他のハッシュ関数md5sumに変更する必要があるかもしれません。別のハッシュ関数を選択し、システムでコマンドの 2 番目の形式が必要な場合は、それに応じてコマンドを調整する必要があります。もう 1 つの落とし穴は、一部のデータ合計プログラムがファイル名をまったく書き出さないことです。その代表的な例が古い Unixプログラムです。md5sortsum

この方法は、md5sumツリー内のファイルの数を N とすると N+1 回呼び出されるため、やや非効率的ですが、ファイルとディレクトリのメタデータのハッシュを回避するために必要なコストです。

オプション2: データを比較するそしてメタデータ

それを検出する必要がある場合何でもツリー内のファイルの内容だけでなくディレクトリの内容も変更されている場合は、tarディレクトリの内容をパックして次の宛先に送信してくださいmd5sum:

$ tar -cf - somedir | md5sum

ファイルの権限や所有権なども確認するためtar、ファイルの内容の変更だけでなく、それらの変更も検出されます。

この方法は、ツリーを 1 回だけ通過し、ハッシュ プログラムを 1 回だけ実行するので、かなり高速です。

find上記のベース メソッドと同様に、tarは、基盤となるファイル システムが返す順序でファイル名を処理します。アプリケーションでは、このような事態は発生しないことが確実です。このような状況が発生する可能性のある使用パターンが少なくとも 3 つ考えられます。(動作が不明確になるため、ここではリストしません。各ファイル システムは、OS のバージョンによっても異なる場合があります。)

誤検知が発生している場合はfind | cpioジルの答え

答え2

チェックサムは、ファイルを文字列として決定論的かつ明確に表現する必要があります。決定論的とは、同じファイルを同じ場所に配置した場合、同じ結果が得られることを意味します。明確とは、2 つの異なるファイル セットが異なる表現を持つことを意味します。

データとメタデータ

ファイルを含むアーカイブを作成することは良いスタートです。これは明確な表現です (当然、アーカイブを抽出すればファイルを復元できます)。日付や所有権などのファイル メタデータが含まれる場合があります。ただし、これはまだ正確ではありません。アーカイブは、その表現がファイルの保存順序、および該当する場合は圧縮に依存するため、曖昧です。

解決策としては、アーカイブする前にファイル名を並べ替えることです。ファイル名に改行が含まれていない場合は、 を実行してファイルfind | sort名を一覧表示し、この順序でアーカイブに追加できます。アーカイバにディレクトリを再帰的に処理しないように指示してください。POSIX pax、GNU tar、cpio の例を次に示します。

find | LC_ALL=C sort | pax -w -d | md5sum
find | LC_ALL=C sort | tar -cf - -T - --no-recursion | md5sum
find | LC_ALL=C sort | cpio -o | md5sum

名前と内容のみ、ローテクな方法

ファイル データのみを考慮し、メタデータは考慮しない場合は、ファイルの内容のみを含むアーカイブを作成できますが、そのための標準ツールはありません。ファイルの内容を含める代わりに、ファイルのハッシュを含めることができます。ファイル名に改行が含まれず、通常のファイルとディレクトリのみ (シンボリック リンクや特殊ファイルなし) である場合、これはかなり簡単ですが、いくつかの点に注意する必要があります。

{ export LC_ALL=C;
  find -type f -exec wc -c {} \; | sort; echo;
  find -type f -exec md5sum {} + | sort; echo;
  find . -type d | sort; find . -type d | sort | md5sum;
} | md5sum

チェックサムのリストに加えてディレクトリのリストも含めます。そうしないと空のディレクトリが見えなくなってしまうからです。ファイルリストはソートされます(特定の再現可能なロケールで - 私にそれを思い出させてくれたPeter.Oに感謝します)。はecho2つの部分を分離します(これがないと、名前が出力のように見える空のディレクトリがいくつか作成され、md5sum通常のファイルにもなり得ます)。また、ファイルサイズのリストも含めます。長さ拡張攻撃

ちなみに、MD5 は非推奨です。使用可能な場合は、SHA-2、または少なくとも SHA-1 の使用を検討してください。

名前とデータ、名前の改行をサポート

これは、ファイル名をヌル バイトで区切るために GNU ツールに依存する上記のコードのバリエーションです。これにより、ファイル名に改行を含めることができます。GNU ダイジェスト ユーティリティは出力で特殊文字を引用符で囲むため、あいまいな改行は発生しません。

{ export LC_ALL=C;
  du -0ab | sort -z; # file lengths, including directories (with length 0)
  echo | tr '\n' '\000'; # separator
  find -type f -exec sha256sum {} + | sort -z; # file hashes
  echo | tr '\n' '\000'; # separator
  echo "End of hashed data."; # End of input marker
} | sha256sum

より強固なアプローチ

以下は、ファイルの階層を記述するハッシュを作成する、最小限のテストが行​​われた Python スクリプトです。ディレクトリとファイルの内容を考慮し、シンボリック リンクやその他のファイルを無視し、読み取れないファイルがある場合は致命的なエラーを返します。

#! /usr/bin/env python
import hashlib, hmac, os, stat, sys
## Return the hash of the contents of the specified file, as a hex string
def file_hash(name):
    f = open(name)
    h = hashlib.sha256()
    while True:
        buf = f.read(16384)
        if len(buf) == 0: break
        h.update(buf)
    f.close()
    return h.hexdigest()
## Traverse the specified path and update the hash with a description of its
## name and contents
def traverse(h, path):
    rs = os.lstat(path)
    quoted_name = repr(path)
    if stat.S_ISDIR(rs.st_mode):
        h.update('dir ' + quoted_name + '\n')
        for entry in sorted(os.listdir(path)):
            traverse(h, os.path.join(path, entry))
    elif stat.S_ISREG(rs.st_mode):
        h.update('reg ' + quoted_name + ' ')
        h.update(str(rs.st_size) + ' ')
        h.update(file_hash(path) + '\n')
    else: pass # silently symlinks and other special files
h = hashlib.sha256()
for root in sys.argv[1:]: traverse(h, root)
h.update('end\n')
print h.hexdigest()

答え3

2 つのディレクトリ間の違いを見つけることだけが目的の場合は、 diff の使用を検討してください。

これを試して:

diff -qr dir1 dir2

答え4

使用checksumdir:

$ pip install checksumdir
$ checksumdir -a md5 assets/js
981ac0bc890de594a9f2f40e00f13872
$ checksumdir -a sha1 assets/js
88cd20f115e31a1e1ae381f7291d0c8cd3b92fad

もっと早くそしてより簡単に他の bash ソリューションよりも優れています。

関連情報