
md5sum 程式不提供目錄的校驗和。我想要取得目錄全部內容(包括子目錄中的檔案)的單一 MD5 校驗和。也就是說,由所有檔案組成的一個組合校驗和。有沒有辦法做到這一點?
答案1
正確的方法取決於您提出問題的確切原因:
選項 1:僅比較數據
如果您只需要樹的檔案內容的雜湊值,則可以這樣做:
$ find -s somedir -type f -exec md5sum {} \; | md5sum
首先以可預測的順序單獨匯總所有文件內容,然後傳遞文件名列表和MD5 哈希值以對其進行哈希處理,從而給出一個值,該值僅在樹中某個文件的內容發生更改時才發生變化。
不幸的是,find -s
僅適用於 BSD find(1),用於 macOS、FreeBSD、NetBSD 和 OpenBSD。為了在 GNU 或 SUS find(1) 系統上獲得類似的東西,你需要一些更醜的東西:
$ find somedir -type f -exec md5sum {} \; | sort -k 2 | md5sum
我們find -s
透過加對sort
.該-k 2
位元告訴它跳過 MD5 雜湊,因此它只對檔案名稱進行排序,這些檔案名稱位於欄位 2 到行尾的sort
計算中。
此版本的命令有一個弱點,即如果您有任何包含換行符的檔案名,它很容易變得混亂,因為它看起來像是多行呼叫sort
。該find -s
變體沒有這個問題,因為樹遍歷和排序發生在同一個程序中find
。
無論哪種情況,排序都是必要的,以避免誤報:最常見的 Unix/Linux 檔案系統不會以穩定、可預測的順序維護目錄清單。您可能沒有透過使用ls
等方式意識到這一點,它會默默地為您對目錄內容進行排序。不以某種方式對其輸出進行排序的呼叫find
將導致輸出中的行順序與底層文件系統返回它們的任何順序相匹配,如果作為輸入提供給它的文件順序發生變化,這將導致該命令給出更改的哈希值,即使資料保持相同。
您可能會問上面的-k 2
GNUsort
指令中的位是否是必要的。鑑於只要內容沒有更改,檔案資料的雜湊值就足以代表檔案名,因此如果我們刪除此選項,我們將不會得到誤報,從而允許我們在 GNU 和 BSD 上使用相同的命令sort
。然而,要認識到,如果存在哈希衝突,檔案名稱的確切順序與不執行該操作可能給出的部分順序不匹配的可能性很小(MD5 為 1:2 128-k 2
)。但請記住,如果如此小的不匹配機會對您的應用程式很重要,那麼整個方法對您來說可能是不可能的。
您可能需要將md5sum
命令更改為md5
或其他一些雜湊函數。如果您選擇另一個雜湊函數並且您的系統需要第二種形式的命令,則可能需要sort
相應地調整命令。另一個陷阱是一些資料求和程式完全不寫出檔名,一個典型的例子是舊的 Unixsum
程式。
此方法效率有些低,需要呼叫md5sum
N+1 次,其中 N 是樹中的檔案數量,但這是避免雜湊檔案和目錄元資料的必要成本。
選項 2:比較數據和元數據
如果您需要能夠檢測到這一點任何事物樹中的內容已更改,而不僅僅是文件內容,請要求tar
為您打包目錄內容,然後將其發送到md5sum
:
$ tar -cf - somedir | md5sum
因為tar
還可以看到文件權限、所有權等,所以這也將檢測這些內容的更改,而不僅僅是文件內容的更改。
這種方法要快得多,因為它只對樹進行一次遍歷,並且只執行一次雜湊程式。
與find
上面的基於方法一樣,tar
將按照底層檔案系統傳回檔案名稱的順序處理檔案名稱。很可能在您的應用程式中,您可以確定不會導致這種情況發生。我可以想到至少三種不同的使用模式,其中可能存在這種情況。 (我不會列出它們,因為我們正在進入未指定的行為領域。這裡的每個檔案系統都可能不同,甚至從一個版本的作業系統到下一個版本也是如此。)
如果您發現自己出現誤報,我建議您使用以下find | cpio
選項吉爾斯的回答。
答案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 提醒我這一點)。echo
將這兩個部分分開(如果沒有這個,您可以建立一些空目錄,其名稱看起來像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
如果您的目標只是尋找兩個目錄之間的差異,請考慮使用 diff。
嘗試這個:
diff -qr dir1 dir2
答案4
使用checksumdir
:
$ pip install checksumdir
$ checksumdir -a md5 assets/js
981ac0bc890de594a9f2f40e00f13872
$ checksumdir -a sha1 assets/js
88cd20f115e31a1e1ae381f7291d0c8cd3b92fad
快點和更輕鬆比其他 bash 解決方案。