md5sum程序不提供目录的校验和。我想为目录的整个内容(包括子目录中的文件)获取一个MD5校验和。也就是说,从所有文件中得出一个组合的校验和。有没有办法做到这一点?
md5sum程序不提供目录的校验和。我想为目录的整个内容(包括子目录中的文件)获取一个MD5校验和。也就是说,从所有文件中得出一个组合的校验和。有没有办法做到这一点?
Answers:
正确的方法完全取决于您要问的原因:
如果您只需要哈希树文件内容的哈希值,那么就可以做到这一点:
$ find -s somedir -type f -exec md5sum {} \; | md5sum
这首先以可预测的顺序分别总结了所有文件内容,然后传递该文件名列表和MD5哈希值本身进行哈希处理,从而给出一个仅当树中文件之一的内容更改时才会更改的单个值。
不幸的是,find -s
仅适用于macOS,FreeBSD,NetBSD和OpenBSD中使用的BSD find(1)。为了在具有GNU或SUS find(1)的系统上获得可比的东西,您需要一些难看的东西:
$ find somedir -type f -exec md5sum {} \; | sort -k 2 | md5sum
我们已将find -s
呼叫替换为sort
。该-k 2
位告诉它跳过MD5哈希值,因此它仅通过sort
估算来对文件名进行排序,这些文件名通过行尾在字段2中。
该命令版本有一个缺点,就是如果其中包含带有换行符的文件名,则很容易混淆,因为它看起来像是要sort
调用的多行。该find -s
变体不存在这样的问题,因为树的遍历和排序在同一程序中发生的,find
。
在这两种情况下,都必须进行排序以避免误报:最常见的Unix / Linux文件系统并不以稳定,可预测的顺序维护目录列表。您可能不会通过使用ls
诸如此类等来实现这一点,后者会为您静默地对目录内容进行排序。find
没有-s
或sort
调用将以基础文件系统返回文件的顺序打印文件,如果作为输入提供给它的文件顺序发生更改,这将导致此命令给出更改的哈希值。
您可能需要将md5sum
命令更改为md5
或其他某种哈希函数。如果选择另一个哈希函数,并且需要系统的第二种形式的命令,则可能需要相应地调整sort
命令。另一个陷阱是,某些数据求和程序根本不会写出文件名,一个典型的例子是旧的Unix sum
程序。
该方法效率不高,调用md5sum
N + 1次,其中N是树中文件的数量,但这是避免哈希文件和目录元数据的必要成本。
如果您需要能够检测到树中的任何内容都发生了变化,而不仅仅是文件内容发生了变化,请要求tar
为您打包目录内容,然后将其发送至md5sum
:
$ tar -cf - somedir | md5sum
因为tar
还会看到文件许可权,所有权等,所以这还将检测到对这些内容的更改,而不仅仅是文件内容的更改。
该方法相当快,因为它只对树进行一次传递,并且仅运行一次哈希程序。
与find
上面的基于方法一样,tar
将按照基础文件系统返回它们的顺序来处理文件名。很有可能在您的应用程序中,您可以确定不会导致这种情况发生。我可以想到至少有三种不同的使用模式。(我不会列出它们,因为我们进入了未指定的行为领域。每个文件系统在这里都可以不同,甚至从一个版本的OS到另一个版本也可以。)
如果您发现自己误报了,建议您find | cpio
选择Gilles的答案中的选项。
find somedir -type f -exec sh -c "openssl dgst -sha1 -binary {} | xxd -p" \; | sort | openssl dgst -sha1
忽略所有文件名(应使用换行符)
校验和必须是文件的确定性和明确表示形式。确定性意味着,如果将相同的文件放在相同的位置,则会得到相同的结果。明确意味着两个不同的文件集具有不同的表示形式。
制作包含文件的存档是一个好的开始。这是明确的表示形式(显然,因为您可以通过提取存档来恢复文件)。它可能包含文件元数据,例如日期和所有权。但是,这还不太正确:档案文件模棱两可,因为其表示取决于文件的存储顺序,如果适用,还取决于压缩。
一种解决方案是在归档文件之前对文件名进行排序。如果文件名不包含换行符,则可以运行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()
LC_ALL=C sort
从不同的环境中进行检查...(+ 1 btw)
LC_ALL=C
如果要在多台计算机和OS上运行,则设置排序顺序至关重要。
cpio -o -
意思 cpio默认不使用stdin / out吗?GNU cpio 2.12产生cpio: Too many arguments
看看md5deep。md5deep的某些功能可能会让您感兴趣:
递归操作-md5deep能够递归检查整个目录树。即,为目录中的每个文件以及每个子目录中的每个文件计算MD5。
比较模式-md5deep可以接受一系列已知哈希并将它们与一组输入文件进行比较。该程序可以显示与已知哈希列表匹配的输入文件,也可以显示不匹配的输入文件。
...
.../foo: Is a directory
,有什么用?
md5deep -r -l -j0 . | md5sum
其中-r
是递归的,-l
表示“使用相对路径”,以便在尝试比较两个目录的内容时文件的绝对路径不会受到干扰,并且-j0
表示使用1个线程来防止由于以下原因导致的不确定性)到以不同顺序返回的各个md5sums)。
您可以递归地哈希每个文件,然后哈希结果文本:
> md5deep -r -l . | sort | md5sum
d43417958e47758c6405b5098f151074 *-
md5deep是必需的。
md5deep
使用hashdeep
,因为md5deep软件包只是hashdeep的过渡虚拟对象。
## Invoked from: /home/myuser/dev/
您当前的路径和## $ hashdeep -s -r -l ~/folder/
。这必须进行排序,因此如果您更改当前文件夹或命令行,则最终哈希将有所不同。
我需要一个仅检查文件名的版本,因为内容位于不同的目录中。
这个版本(Warren Young的答案)很有帮助,但是我的md5sum
输出版本是文件名(相对于我运行命令的路径),并且文件夹名称是不同的,因此即使单个文件的校验和匹配,最终的校验和也没有没错
为了解决这个问题,就我而言,我只需要从find
输出的每一行剥离文件名(使用,仅选择第一个单词,用空格隔开cut
):
find -s somedir -type f -exec md5sum {} \; | cut -d" " -f1 | md5sum
解决方案:
$ pip install checksumdir
$ checksumdir -a md5 assets/js
981ac0bc890de594a9f2f40e00f13872
$ checksumdir -a sha1 assets/js
88cd20f115e31a1e1ae381f7291d0c8cd3b92fad
比bash脚本更快速,更轻松的解决方案。
我将此代码段用于中等音量:
find . -xdev -type f -print0 | LC_COLLATE=C sort -z | xargs -0 cat | md5sum -
这是XXXL的:
find . -xdev -type f -print0 | LC_COLLATE=C sort -z | xargs -0 tail -qc100 | md5sum -
-xdev
标志吗?
man find
并阅读该精美手册;)
-xdev Don't descend directories on other filesystems.
作为此出色答案的补充,如果您发现自己想加快大型目录的校验和的计算,请尝试使用GNU Parallel:
find -s somedir -type f | parallel -k -n 100 md5 {} | md5
(这是使用带有的Mac md5
,根据需要替换。)
该-k
标志很重要,它指示parallel
维护顺序,否则即使文件都相同,总和也可以更改运行。-n 100
表示要md5
使用100个参数运行每个实例,这是您可以调整的参数,以获得最佳运行时间。另请参阅的-X
标志parallel
(尽管在我个人的情况下会导致错误。)
这个脚本经过了严格的测试,并支持许多操作,包括查找重复项,对数据和元数据进行比较,显示添加以及更改和删除,您可能会喜欢Fingerprint。
指纹现在不会为目录生成单个校验和,而是一个脚本文件,其中包含该目录中所有文件的校验和。
fingerprint analyze
这将index.fingerprint
在当前目录中生成,其中包括校验和,文件名和文件大小。默认情况下,它同时使用MD5
和SHA1.256
。
将来,我希望将对Merkle树的支持添加到Fingerprint中,这将为您提供单个顶级校验和。现在,您需要保留该文件以进行验证。
我既不想新的可执行文件,也不想笨拙的解决方案,所以这是我的看法:
#!/bin/sh
# md5dir.sh by Camilo Martin, 2014-10-01.
# Give this a parameter and it will calculate an md5 of the directory's contents.
# It only takes into account file contents and paths relative to the directory's root.
# This means that two dirs with different names and locations can hash equally.
if [[ ! -d "$1" ]]; then
echo "Usage: md5dir.sh <dir_name>"
exit
fi
d="$(tr '\\' / <<< "$1" | tr -s / | sed 's-/$--')"
c=$((${#d} + 35))
find "$d" -type f -exec md5sum {} \; | cut -c 1-33,$c- | sort | md5sum | cut -c 1-32
这就是我的头等大事,任何花了一些时间从事这一工作的人实际上都会抓到其他陷阱和死角。
这是一个工具(免责声明:我是它的贡献者)dtreetrawl,对内存的要求很低,可以解决大多数情况,可能有些麻烦,但是很有帮助。
Usage: dtreetrawl [OPTION...] "/trawl/me" [path2,...] Help Options: -h, --help Show help options Application Options: -t, --terse Produce a terse output; parsable. -d, --delim=: Character or string delimiter/separator for terse output(default ':') -l, --max-level=N Do not traverse tree beyond N level(s) --hash Hash the files to produce checksums(default is MD5). -c, --checksum=md5 Valid hashing algorithms: md5, sha1, sha256, sha512. -s, --hash-symlink Include symbolic links' referent name while calculating the root checksum -R, --only-root-hash Output only the root hash. Blank line if --hash is not set -N, --no-name-hash Exclude path name while calculating the root checksum -F, --no-content-hash Do not hash the contents of the file
人类友好输出示例:
... ... //clipped ... /home/lab/linux-4.14-rc8/CREDITS Base name : CREDITS Level : 1 Type : regular file Referent name : File size : 98443 bytes I-node number : 290850 No. directory entries : 0 Permission (octal) : 0644 Link count : 1 Ownership : UID=0, GID=0 Preferred I/O block size : 4096 bytes Blocks allocated : 200 Last status change : Tue, 21 Nov 17 21:28:18 +0530 Last file access : Thu, 28 Dec 17 00:53:27 +0530 Last file modification : Tue, 21 Nov 17 21:28:18 +0530 Hash : 9f0312d130016d103aa5fc9d16a2437e Stats for /home/lab/linux-4.14-rc8: Elapsed time : 1.305767 s Start time : Sun, 07 Jan 18 03:42:39 +0530 Root hash : 434e93111ad6f9335bb4954bc8f4eca4 Hash type : md5 Depth : 8 Total, size : 66850916 bytes entries : 12484 directories : 763 regular files : 11715 symlinks : 6 block devices : 0 char devices : 0 sockets : 0 FIFOs/pipes : 0
单独处理每个目录中的所有文件。
# Calculating
find dir1 | xargs md5sum > dir1.md5
find dir2 | xargs md5sum > dir2.md5
# Comparing (and showing the difference)
paste <(sort -k2 dir1.md5) <(sort -k2 dir2.md5) | awk '$1 != $3'
这个答案是对使用Tar输出对目录的内容进行哈希处理的方法的补充更新,这是沃伦·扬和吉尔斯在一段时间前的出色回答中提出的。
从那时起,至少openSUSE(从其版本12.2开始)将其默认GNU Tar格式从“ GNU tar 1.13.x格式”更改为(略)高级的“ POSIX 1003.1-2001(pax)格式”。也上游(GNU焦油的开发者),他们讨论来执行相同的迁移,例如参见上最后一个段落当前页的的GNU焦油手册:
GNU tar的默认格式是在编译时定义的。您可以通过运行
tar --help
并检查其输出的最后几行来对其进行检查。通常,将GNU tar配置为以gnu
格式创建档案,但是,将来的版本将切换到posix
。
(此页面还对GNU Tar可用的不同存档格式进行了很好的回顾。)
在我们的示例中,如果我们将目录的内容作为tar并散列结果,并且不采取特定措施,那么从GNU更改为POSIX格式将产生以下结果:
尽管目录内容相同,但生成的校验和将有所不同。
尽管目录内容相同,但如果使用默认的pax标头,则每次运行时产生的校验和将有所不同。
后者来自以下事实:POSIX(pax)格式包括扩展的pax标头,这些标头由默认为%d/PaxHeaders.%p/%f
GNU Tar中的格式字符串确定。在该字符串中,说明符%p
被生成的Tar进程的进程ID替换,当然,这在运行之间是不同的。有关详细信息,请参见GNU Tar手册的这一部分,尤其是本部分。
刚刚从2019-03-28开始,上游接受了一项提交,以缓解此问题。
因此,为了能够在给定的用例中继续使用GNU Tar,我可以推荐以下替代选项:
使用Tar选项--format=gnu
明确指示Tar以“旧”格式生成档案。这对于验证“旧”校验和是必需的。
使用更新的POSIX格式,但显式指定合适的pax标头,例如通过--pax-option="exthdr.name=%d/PaxHeaders/%f"
。但是,这破坏了与“旧”校验和的向后兼容性。
这是一个Bash代码片段,我经常使用它来计算目录内容(包括元数据)的校验和:
( export LC_ALL=C
find <paths> ! -type s -print0 |
sort -z |
tar cp --format=gnu --numeric-owner \
--atime-preserve \
--no-recursion --null --files-from - |
md5sum --binary; )
在此,<paths>
用校验和覆盖的所有目录的路径的列表以空格分隔。在其他答案中已经充分讨论了使用C语言环境,文件名的空字节分隔以及使用查找和排序来获取档案中文件的文件系统独立顺序的目的。
周围的括号将LC_ALL
设置保留在子外壳中。
此外,我使用表达式! -type s
with find
来避免如果套接字文件是目录内容的一部分而引起Tar发出的警告:GNU Tar不会存档套接字。如果您希望收到有关套接字跳过的通知,请不要使用该表达式。
我--numeric-owner
与Tar一起使用,以便以后甚至在并非所有文件所有者都知道的系统上也可以校验校验和。
--atime-preserve
如果<paths>
在只读安装的设备上有任何谎言,则最好省略Tar 的选项。否则,将警告您访问时间戳记Tar无法还原的每个文件。对于启用写<paths>
,我很好地使用此选项将访问时间戳保留在哈希目录中。
Tar选项--no-recursion
已经在Gilles提案中使用,可以防止Tar递归地下降到目录中,而是对从排序find
输出中得到的任何内容逐个文件地进行操作。
最后,我使用的不是真的md5sum
:我实际上使用sha256sum
。
find .
而不是find somedir
。这样,在提供不同的路径规范时,文件名是相同的。这可能很棘手:-)