如何计算一个目录的MD5校验和?

152

我需要为一个特定类型的所有文件(*.py 例如)在一个目录及其所有子目录下计算摘要MD5校验和。

有什么最好的方法吗?


提供的解决方案很好,但这不完全是我需要的。我正在寻找一种解决方案,可以获得单个摘要校验和,该摘要校验和将唯一标识整个目录 - 包括其所有子目录的内容。


请查看这个这个以获取更详细的解释。 - luvieere
3
我认为这是一个超级用户的问题。 - Noldorin
12
注意,校验和并不能唯一地标识任何内容。 - Hosam Aly
1
为什么会有两个可能是“相同”的目录树,你想要唯一标识它们?文件的创建/修改/访问时间是否很重要?你真正需要的是版本控制吗? - jmucchiello
在我这种情况下真正重要的是整个目录树内容的相似性,也就是据我所知以下内容: 1)目录树下任何文件的内容未被更改 2)没有新文件添加到目录树中 3)没有文件被删除 - victorz
16个回答

171

在运行时创建一个tar归档文件并将其传输到md5sum进行处理:

tar c dir | md5sum

这将生成一个MD5哈希值,应该对您的文件和子目录设置是唯一的。没有任何文件会被创建到磁盘上。


25
仅凭一个校验和,你无法知道哪个文件不同。问题是关于一个目录的单个校验和。 - Hawken
25
ls -alR dir | md5sum。这样做甚至比压缩文件还要好,因为它只是读取文件而已。这条命令的独特之处在于内容包含了文件的修改时间和大小;) - Sid
17
@Daps0l - 我的命令中没有压缩。你需要加上 z 来使用 gzip,或者加上 j 来使用 bzip2。我两个都没加。 - ire_and_curses
10
请注意,这样做会将文件的时间戳和其他信息整合到校验和计算中,而不仅仅是文件内容。 - Michael Zilbermann
17
这很可爱,但实际上并不起作用。tar同一组文件两次或在两台不同的计算机上进行操作,并不能保证会得到完全相同的结果。 - fletom
显示剩余6条评论

165
find /path/to/dir/ -type f -name "*.py" -exec md5sum {} + | awk '{print $1}' | sort | md5sum

find 命令列出所有以 .py 结尾的文件。 对于每个 .py 文件,计算其 MD5 哈希值。使用 AWK 程序选择 MD5 哈希值(忽略可能不唯一的文件名)。 将 MD5 哈希值进行排序,并返回排序后的列表的 MD5 哈希值。

我通过复制测试目录进行了测试:

rsync -a ~/pybin/ ~/pybin2/

我重命名了~/pybin2中的一些文件。

find...md5sum命令返回两个目录相同的输出结果。

2bcf49a4d19ef9abd284311108d626f1  -
为了考虑文件布局(路径),使得如果一个文件被重命名或移动,校验和也会改变,这个命令可以简化为:
find /path/to/dir/ -type f -name "*.py" -exec md5sum {} + | md5sum

在 macOS 上使用 md5

find /path/to/dir/ -type f -name "*.py" -exec md5 {} + | md5

26
请注意,如果文件被重命名,将生成相同的校验和。因此,如果您将文件布局视为签名的一部分,则这并不真正适用于“唯一标识整个目录的校验和”。 - Valentin Milea
1
你可以稍微改变命令行,使用文件名(或者更好的是文件相对于“/path/to/dir/”路径)作为前缀来生成每一个文件的校验和,这样在最终计算校验和时就会将它们考虑进去。 - Michael Zilbermann
4
是的,它可以被改变,但我理解问题(特别是由于OP在问题下的评论),OP希望如果文件内容相同,则无论文件名甚至相对路径如何,任意两个目录都应被视为相等。 - unutbu
你的答案中是否有语法错误?我不得不用单引号将-name模式括起来才能使其正常工作。 - silvernightstar
@silvernightstar:对我来说(在Ubuntu/bash上),两种方式都可以,但你是正确的;我可能应该在它周围加上引号。 - unutbu
显示剩余10条评论

54

ire_and_curses提出的使用tar c <dir>的建议有一些问题:

  • tar按照文件系统中存储的顺序处理目录条目,并且没有办法改变这个顺序。如果你在不同的位置拥有“相同”的目录,这将会产生完全不同的结果,我不知道如何解决这个问题(tar无法按特定顺序“排序”其输入文件)。
  • 通常我关心的是组ID和所有者ID号码是否相同,而不仅仅是它们的字符串表示是否相同。这与例如rsync -a --delete所做的相一致:它会同步几乎所有内容(除了xattrs和acls),但会根据它们的ID而不是字符串表示来同步所有者和组。因此,如果您同步到一个不一定拥有相同用户/组的不同系统,应该在tar中添加--numeric-owner标志。
  • tar将包括正在检查的目录本身的文件名,这只是一个需要注意的问题。

只要第一个问题没有解决方案(或者你确定它对你没有影响),我就不会使用这种方法。

提议的基于find的解决方案也不好,因为它们只包括文件,而不包括目录,如果你想保留空目录,则会成为一个问题。

最后,大多数建议的解决方案在排序上不一致,因为在系统间可能存在不同的排序规则。

这是我想出来的解决方案:

dir=<mydir>; (find "$dir" -type f -exec md5sum {} +; find "$dir" -type d) | LC_ALL=C sort | md5sum

关于这个解决方案的注意事项:

  • LC_ALL=C 用于确保不同系统之间具有可靠的排序顺序。
  • 这并不能区分一个名为“named\nwithanewline”的目录和两个名为“named”和“withanewline”的目录,但是出现这种情况的几率似乎非常小。通常可以通过在 find 命令中加上 -print0 标志来修复此问题,但由于这里还有其他事情需要处理,我只能看到会使命令变得更加复杂而不值得的解决方案。

附注:我的一台机器使用了一个有限的 busybox find ,它不支持 -exec-print0 标志,并且还会添加“/”来表示目录,而 findutils find 似乎没有这样做,因此对于这台机器,我需要运行:

dir=<mydir>; (find "$dir" -type f | while read f; do md5sum "$f"; done; find "$dir" -type d | sed 's#/$##') | LC_ALL=C sort | md5sum
幸运的是,我的文件/目录名称中没有换行符,因此在那个系统上这不是一个问题。

1
非常有趣!您是在说不同文件系统类型之间或同一文件系统内部的顺序可能会有所不同吗? - ire_and_curses
2
两者的不同取决于每个目录条目的顺序。据我所知,目录条目(在文件系统中)只是按照“在目录中创建文件”的顺序创建的。一个简单的例子:$ mkdir a; touch a/file-1; touch a/file-2 $ mkdir b; touch b/file-2; touch b/file-1 $ (cd a; tar -c . | md5sum) fb29e7af140aeea5a2647974f7cdec77 - $ (cd b; tar -c . | md5sum) a3a39358158a87059b9f111ccffa1023 - - Dieter_be
我宁愿用普通的xargs替换while循环,以便可以使用-P进行并行处理。这还需要对第二列进行额外的排序步骤,因为并行md5sum没有可重复的顺序。find“$dir”-type f-print0 | xargs -P 6-r0 md5sum | sort-k2 - motzmann

18

如果你只关心文件而不是空目录,那么这个方法非常适用:

find /path -type f | sort -u | xargs cat | md5sum

为什么需要使用 cat 命令?它能处理文件名中带有空格的文件吗? - Peter Mortensen
好的,tesujimath 似乎已经离开了 ("上次出现超过2年前"). 或许有其他人可以加入讨论? - Peter Mortensen
2
如果您不使用“cat”命令将文件内容传递给“md5sum”,则输入将是“find”命令的输出,该输出是文件名(和路径)列表,而不是这些文件的内容。 - Abid H. Mujtaba
注意:我考虑过省略 sort -u,但我们需要它,否则文件的顺序可能不同,因此校验和也会不同。 - Putnik
使用find /path -type f -print0 | sort -u -z | xargs --null cat | md5sum命令来处理带有空格的文件名。 - Victor Klos

11

对我最有效的解决方案:

find "$path" -type f -print0 | sort -z | xargs -r0 md5sum | md5sum

它对我而言最好的原因:

  1. 处理包含空格的文件名
  2. 忽略文件系统元数据
  3. 检测文件是否已被重命名

其他答案存在的问题:

对于以下操作,文件系统元数据没有被忽略:

tar c - "$path" | md5sum

不处理包含空格的文件名,也不检测文件是否已被重命名:

find /path -type f | sort -u | xargs cat | md5sum

“-r0”选项是什么意思?我知道“-r”选项可以防止在输入不包含非空白字符时运行命令,但是“0”又代表什么呢? - Stack Underflow
如果路径中包含空格,请查看 find 命令的 -print0 选项。我们还有 xargs 命令的 -0 选项和 sort 命令的 -z 选项,它们会将空格替换为 null 字符。 - Tiago Lopo
1
哦,当然!你是在组合两个不同的选项“-r”和“-0”。我一直在想一个单独的“-r0”选项。谢谢。 - Stack Underflow
不使用-print0-z--null选项可以保持空格的完整性,而不会改变NULL字符的换行方式。 - Victor Klos
嗨 @VictorKlos,我们从不打开文件,只是将文件名传递给md5sum。 - Tiago Lopo

10

为了完整起见,这里有md5deep(1);由于*.py过滤器的要求不直接适用,但与find(1)一起使用应该可以很好地完成。


如果我只想计算一个目录的md5校验和,我应该使用哪些参数? - Gabriel Fair
1
它应该做什么?你能否在你的答案中详细解释一下(如果不加以解释,这就只是一个链接而已)?(但是请不要写“编辑:”,“更新:”或类似的词语——问题/答案应该看起来像今天刚写的。) - Peter Mortensen
Peter,我不能回答你的问题,因为我自己并没有多少使用它,而是在过去当我负责维护ALT Rescue镜像时选择将其包含在内;像那样的一个简单链接已经帮助过我很多次了...无论如何,感谢你的提问(我今天才看到)。 - Michael Shigorin

4
如果您希望生成整个目录的一个MD5哈希值,我会这样做:
cat *.py | md5sum

1
对于子目录,请使用类似于 cat **.py | md5sum 的命令 - Ramon

3

对所有文件进行校验,包括内容和它们的文件名

grep -ar -e . /your/dir | md5sum | cut -c-32

与上面相同,但仅包括*.py文件。
grep -ar -e . --include="*.py" /your/dir | md5sum | cut -c-32

你可以选择跟随符号链接,如果你需要的话。
grep -aR -e . /your/dir | md5sum | cut -c-32

您可以考虑与grep一起使用的其他选项

-s, --no-messages         suppress error messages
-D, --devices=ACTION      how to handle devices, FIFOs and sockets;
-Z, --null                print 0 byte after FILE name
-U, --binary              do not strip CR characters at EOL (MSDOS/Windows)

那么如何克服定义排序顺序的问题呢? - Peter Mortensen

2

GNU find

find /path -type f -name "*.py" -exec md5sum "{}" +;

最后一个标记应该是 ; 吗? - Dan Moulding

2
从技术上讲,您只需要运行ls -lR *.py | md5sum。除非您担心有人修改文件并将它们触摸回原始日期并永远不更改文件大小,否则来自ls的输出应告诉您文件是否已更改。我的unix-foo很弱,因此您可能需要一些更多的命令行参数才能打印出创建时间和修改时间。ls还会告诉您文件权限是否已更改(如果您不关心权限,则我相信有开关可以关闭它)。

4
有些情况下这可能适用,但通常你希望校验和仅反映内容而不涉及日期。例如,如果我通过“touch”命令更改文件的日期(但未更改其内容),则期望校验和保持不变。 - Todd Owen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接