Linux:为给定文件夹和内容计算单个哈希值?

154

肯定有一种简单的方法来做到这一点!

我已经尝试过Linux命令行应用程序,例如sha1summd5sum,但它们似乎只能计算单个文件的哈希值,并输出每个文件的一个哈希值列表。

我需要为整个文件夹的所有内容(而不仅仅是文件名)生成一个单一的哈希值。

我想做类似于以下的操作:

sha1sum /folder/of/stuff > singlehashvalue

编辑:为了澄清,我的文件分布在目录树的多个层级中,它们并不都位于同一根文件夹中。


1
“整个内容”是指目录中所有文件的逻辑数据还是在到达根哈希时其数据和元数据一起?由于您的用例选择标准相当广泛,我已尝试在我的答案中解决一些实际问题。 - six-k
20个回答

191

一种可能的方法是:

sha1sum path/to/folder/* | sha1sum

如果有整个目录树,最好使用find和xargs。 一个可能的命令是:

find path/to/folder -type f -print0 | sort -z | xargs -0 sha1sum | sha1sum

最后,如果您还需要考虑权限和空目录:

(find path/to/folder -type f -print0  | sort -z | xargs -0 sha1sum;
 find path/to/folder \( -type f -o -type d \) -print0 | sort -z | \
   xargs -0 stat -c '%n %a') \
| sha1sum
< p >参数< code > stat 会导致它打印文件名,后跟它的八进制权限。两个查找将依次运行,导致磁盘IO量增加一倍,第一个查找所有文件名并对内容进行校验,第二个查找所有文件和目录名称,打印名称和模式。
“文件名和校验和”列表,后跟“名称和目录,带有权限”,然后将被校验,以获得较小的校验和。


3
请不要忘记设置LC_ALL=POSIX,这样各种工具生成的输出就会与语言环境无关。 - David Schmitt
3
我发现“cat | sha1sum”比“sha1sum | sha1sum”要快得多。你可以在系统上试一下这两个命令:time find path/to/folder -type f -print0 | sort -z | xargs -0 sha1sum | sha1sum; time find path/to/folder -type f -print0 | sort -z | xargs -0 cat | sha1sum。结果可能因人而异(YMMV)。 - Bruno Bronosky
6
假设我们有两个文件A和B。A中包含"foo",B中包含"bar was here"。使用您的方法,我们将无法将其与包含"foobar"和"was here"的两个文件C和D分开。通过对每个文件进行单独哈希,然后对所有"文件名哈希"对进行哈希,我们可以看到它们之间的差异。 - Vatine
2
为了使其在不考虑目录路径的情况下工作(即当您想要比较两个不同文件夹的哈希值时),您需要使用相对路径并更改到适当的目录,因为路径包含在最终哈希中:find ./folder -type f -print0 | sort -z | xargs -0 sha1sum | sha1sum - robbles
3
@robbles说的对,这也是我在path/to/folder这一部分没有放置初始的/的原因。 - Vatine
显示剩余20条评论

47
  • 使用类似aide的文件系统入侵检测工具。

  • 对目录进行tar打包并计算哈希值:

    tar cvf - /path/to/folder | sha1sum

  • 自己编写代码,例如vatine的一行代码

    find /path/to/folder -type f -print0 | sort -z | xargs -0 sha1sum | sha1sum


7
+1针对焦油解决方案的建议很好。这是最快的方法,但要省略 "v"。冗长的详细说明只会减慢速度。 - Bruno Bronosky
9
请注意,tar解压缩假定在比较文件时它们的顺序相同。这取决于文件系统中文件所处的顺序。 - nos
7
Git哈希值不适合此目的,因为文件内容只是其输入的一部分。即使是分支的初始提交,哈希值也受到提交消息和提交元数据(例如提交时间)的影响。如果您多次提交相同的目录结构,则每次都会获得不同的哈希值,因此仅通过发送哈希值来确定两个目录是否完全相同并不可行。 - Zoltan
1
@Zoltan,如果您使用树哈希而不是提交哈希,则git哈希值完全正常。 - hobbs
3
最近版本的GNU tar可以使用 --sort=name 来强制按名称排序。 - Andrew Klaassen
显示剩余2条评论

21

如果你只想检查文件夹中是否有内容更改,我推荐使用这个:

ls -alR --full-time /folder/of/stuff | sha1sum

它只会给您ls输出的哈希值,其中包含文件夹、子文件夹、它们的文件、时间戳、大小和权限。几乎所有您需要确定是否发生更改的内容。

请注意,该命令不会为每个文件生成哈希值,但这就是为什么它应该比使用find更快的原因。


3
考虑到这个解决方案的简单性,我不确定为什么它没有更多的赞。有人能解释一下为什么这个方法不能很好地工作吗? - Dave C
1
我想这并不理想,因为生成的哈希将基于文件所有者、日期格式设置等。 - Ryota
1
ls 命令可以自定义输出任何你想要的内容。你可以将 -l 替换为 -gG 来省略组和所有者信息。而且,你可以使用 --time-style 选项来更改日期格式。基本上,查看 ls 的 man 手册并找到适合你需求的选项即可。 - Shumoapp
@DaveC 因为它几乎没有用处。如果你想比较文件名,直接比较它们就可以了。它们并不是很大。 - Navin
7
从问题中无法确定是需要对文件内容进行哈希处理还是检测树中的更改。每种情况都有其用途。例如,在内核树中存储45K个文件名比单个哈希值不实用。使用“ls -lAgGR --block-size=1 --time-style=+%s | sha1sum”命令可以很好地完成哈希操作。 - yashma
请注意,即使对于备受推崇的rsync,按默认设置比较时间戳和文件大小就足够了。 - Torsten Bronger

20
到目前为止,最快的方法仍然是使用tar。而且通过使用几个额外的参数,我们还可以消除元数据引起的差异。
要使用GNU tar对目录进行哈希,需要确保在tar过程中对路径进行排序,否则结果总是不同的。
tar -C <root-dir> -cf - --sort=name <dir> | sha256sum

忽略时间

如果您不关心访问时间或修改时间,也可以使用类似 --mtime='UTC 2019-01-01' 的方式,确保所有时间戳相同。

忽略所有权

通常我们需要添加 --group=0 --owner=0 --numeric-owner 来统一所有者的元数据。

忽略某些文件

使用 --exclude=PATTERN

忽略权限

强烈建议您始终比较权限。

如果您真的不想比较权限,请使用:

--mode=777

这将强制所有文件权限为777。
示例:
$ echo a > test1/a.txt
$ echo b > test1/b.txt
$ tar -C ./ -cf - --sort=name test1 | sha256sum
e159ca984835cf4e1c9c7e939b7069d39b2fd2aa90460877f68f624458b1c95c  -
$ tar -C ./ -cf - --sort=name --mode=777 test1 | sha256sum
ef84fe411fb49bcf7967715b7854075004f1c7a7e4a57d2f3742afa4a54c40de  -
$ chmod 444 test1/a.txt
$ tar -C ./ -cf - --sort=name --mode=777 test1 | sha256sum
ef84fe411fb49bcf7967715b7854075004f1c7a7e4a57d2f3742afa4a54c40de  -
$ tar -C ./ -cf - --sort=name test1 | sha256sum
9b91430d954abb8a361b01de30f0995fb94a511c8fe1f7177ddcd475c85c65ff  -

有些tar命令没有--sort选项,确保你使用的是GNU tar。

2
这是涉及GNU tar的最佳答案,因为它确保文件内容和目录结构得到一致比较。 - Andrew Klaassen
1
警告:并非所有版本的tar都有--sort :-( - krupan
如果tar归档文件中不包含权限信息,那么这将是一种绝佳的方法:如果您有具有不同权限的相同文件/目录,则此类比较将失败。 - lvd
实际上,在大多数情况下,比较权限是必需的。您不希望任何人搞乱您的权限设置。如果您真的不想检查权限,您可以始终使用 --mode=777。@lvd - Wang
感谢您的快速反应,@王!现在您的方法看起来非常完整。 - lvd
为什么这个回答没有被标记为正确答案? - undefined

16

你可以使用 tar -c /path/to/folder | sha1sum 命令。


24
如果您想在另一台机器上复制该校验和,tar可能不是一个好选择,因为该格式似乎存在歧义,并且存在许多版本,因此另一台机器上的tar可能会生成与相同文件不同的输出。 - slowdog
3
尽管slowdog的担忧是合理的,但如果您关心文件内容、权限等,而不关心修改时间,您可以添加--mtime选项,如下所示:tar -c /path/to/folder --mtime="1970-01-01" | sha1sum - Binary Phile
@S.Lott 如果目录的大小很大,也就是说如果目录的大小非常大,那么压缩它并对其进行md5处理将需要更多时间。 - Kasun Siyambalapitiya

7

如果这是一个Git仓库,你想忽略任何在.gitignore中的文件,你可以使用以下命令:

git ls-files <your_directory> | xargs sha256sum | cut -d" " -f1 | sha256sum | cut -d" " -f1

这对我来说运作良好。


1
对于许多应用程序而言,这种方法更为优越。仅对源代码文件进行哈希处理即可在更短的时间内获得足够唯一的哈希值。 - John McGehee
1
终于有些东西在不同的环境下是一致的。谢谢! - Sergey Lukin

7

一种强大而干净的方法

  • 首先,不要占用可用内存!将文件分块哈希,而不是将整个文件读入内存。
  • 针对不同的需求/目的采用不同的方法(以下所有内容或选择适用的部分):
    • 仅哈希目录树中所有条目的条目名
    • 哈希所有条目的文件内容(留下元数据,如inode号、ctime、atime、mtime、大小等,你懂的)
    • 对于符号链接,其内容是参考名称。可以哈希它,也可以选择跳过
    • 在哈希条目内容时是否跟随符号链接(解析后的名称)
    • 如果它是一个目录,那么它的内容只是目录条目。在递归遍历时,它们最终会被哈希,但该级别的目录条目名称应该哈希以标记此目录吗?在需要快速识别更改的哈希而无需深度遍历以哈希内容的用例中很有帮助。例如,文件名更改,但其余内容保持不变且都是相当大的文件
    • 处理大型文件(再次注意RAM)
    • 处理非常深的目录树(注意打开文件描述符)
    • 处理非标准的文件名
    • 如何处理套接字、管道/FIFO、块设备、字符设备?必须将它们也哈希吗?
    • 在遍历时不更新任何条目的访问时间,因为这将是一种副作用,并且会对某些用例产生反效果(直观?)。

这就是我在脑海中的想法,任何花费一些时间实际工作的人都会发现其他难点和边界情况。

这里有一个工具,占用内存非常少,可以解决大多数情况,可能有些粗糙但已经非常有帮助。

dtreetrawl的使用示例和输出。

Usage:
  dtreetrawl [OPTION...] "/trawl/me" [path2,...]

Help Options:
  -h, --help                Show help options

Application Options:
  -t, --terse               Produce a terse output; parsable.
  -j, --json                Output as JSON
  -d, --delim=:             Character or string delimiter/separator for terse output(default ':')
  -l, --max-level=N         Do not traverse tree beyond N level(s)
  --hash                    Enable hashing(default is MD5).
  -c, --checksum=md5        Valid hashing algorithms: md5, sha1, sha256, sha512.
  -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
  -s, --hash-symlink        Include symbolic links' referent name while calculating the root checksum
  -e, --hash-dirent         Include hash of directory entries while calculating root checksum

一段易于理解的输出:

...
... //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

1
你能否举一个简短的例子,获取一个文件夹的强大而干净的sha256哈希值,例如一个Windows文件夹,其中包含三个子目录和一些文件? - Ferit

4

另外一个实现这个目标的工具:

http://md5deep.sourceforge.net/

它的作用类似于 md5sum,但是还可以递归处理,同时有其他功能。

md5deep -r {direcotory}


1
虽然这个链接可能回答了问题,但最好在此处包含答案的基本部分并提供参考链接。仅有链接的答案如果链接页面发生更改可能会变得无效。 - Mamoun Benghezal

4
如果您只想处理文件内容而忽略文件名,则可以使用哈希算法,生成文件的哈希值。
cat $FILES | md5sum

确保在计算哈希时文件的顺序相同:

cat $(echo $FILES | sort) | md5sum

但是您的文件列表中不能包含目录。


3
将一个文件的结尾移动到其字母顺序后面的文件的开头不会影响哈希值,但是应该会产生影响。需要在哈希值中包括文件分隔符或文件长度。 - Jason Stangroome

3

将多进程和进度条添加到kvantour的答案

速度提升约30倍(取决于CPU)

100%|██████████████████████████████████| 31378/31378 [03:03<00:00, 171.43file/s]

# to hash without permissions
find . -type f -print0 | sort -z | xargs -P $(nproc --all) -0 sha1sum | tqdm --unit file --total $(find . -type f | wc -l) | sort | awk '{ print $1 }' | sha1sum

# to hash permissions
(find . -type f -print0  | sort -z | xargs -P $(nproc --all) -0 sha1sum | sort | awk '{ print $1 }'; 
  find . \( -type f -o -type d \) -print0 | sort -z | xargs -P $(nproc --all) -0 stat -c '%n %a') | \
  sort | sha1sum | awk '{ print $1 }'

请确保已安装tqdm,可通过pip install tqdm或查看文档进行安装。

awk将删除文件路径,以便如果父目录或路径不同,则不会影响哈希值。


1
这需要在最后的sha1sum之前进行排序,以获得一致的结果(除非tqdm处理了?我没有使用tqdm进行测试)。 - krupan
没错,我刚刚添加了这个,没有看到你的评论,现在我希望我先看到你的评论。 - FarisHijazi

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