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


95

当然,必须有一种方法可以轻松地做到这一点!

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

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

我想做类似的事情

sha1sum /folder/of/stuff > singlehashvalue

编辑:澄清一下,我的文件在目录树中处于多个级别,它们并不都位于同一根文件夹中。


1
“全部内容”是指目录中所有文件的逻辑数据,还是指向根哈希的元数据?由于您的用例的选择标准相当广泛,因此我在回答中尝试了一些实际的选择。
六ķ

Answers:


123

一种可能的方式是:

sha1sum路径/到/文件夹/ * | sha1sum

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

查找路径/到/文件夹-type f -print0 | 排序-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

的参数stat将导致它打印文件名,然后是其八进制权限。这两个查找将一个接一个地运行,从而导致磁盘IO数量翻倍,第一个查找所有文件名并校验和内容,第二个查找所有文件和目录名,打印名称和方式。然后,将对“文件名和校验和”列表以及“具有权限的名称和目录”列表进行校验和,以得到较小的校验和。


2
并且不要忘记设置LC_ALL = POSIX,因此各种工具都会创建与语言环境无关的输出。
David Schmitt,2009年

2
我发现猫| sha1sum要比sha1sum快得多| sha1sum。YMMV,请在您的系统上尝试以下每种方法:time find path / to / folder -type f -print0 | 排序-z | xargs -0 sha1sum | sha1sum; 时间查找路径/到/文件夹-type f -print0 | 排序-z | xargs -0猫| sha1sum
Bruno Bronosky 2011年

5
@RichardBronosky-假设我们有两个文件,A和B。A包含“ foo”,B包含“ bar was here”。使用您的方法,我们将无法将其与两个文件C和D分开,其中C包含“ foobar”,D包含“在这里”。通过分别散列每个文件,然后散列所有“文件名散列”对,我们可以看到区别。
Vatine 2012年

2
要使该工作与目录路径无关(例如,当您要比较两个不同文件夹的哈希值时),您需要使用相对路径并更改为适当的目录,因为这些路径包括在最终的哈希中:find ./folder -type f -print0 | sort -z | xargs -0 sha1sum | sha1sum
乱码

3
@robbles这是正确的,为什么我没有把最初/path/to/folder位。
Vatine

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

  • 哈希目录的tar球:

    tar cvf - /path/to/folder | sha1sum

  • 自己编写一些东西,例如vatine的oneliner

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


3
+1为tar解决方案。那是最快的,但是放弃v。冗长只会减慢它的速度。
布鲁诺·布罗诺斯基

6
请注意,当您比较文件时,tar解决方案假定文件的顺序相同。它们是否将取决于进行比较时文件所驻留的文件系统。
Nos 2013年

5
git哈希不适用于此目的,因为文件内容只是其输入的一部分。即使对于分支的初始提交,哈希也受提交消息和提交元数据的影响,例如提交时间。如果您多次提交相同的目录结构,则每次将获得不同的哈希值,因此生成的哈希值不适合仅通过发送哈希值来确定两个目录是否为彼此的精确副本。
Zoltan '18年

1
@Zoltan,如果您使用树形哈希而不是提交哈希,则git哈希非常好。
霍布斯

@hobbs答案最初表示为“提交哈希”,这当然不适合此目的。树形哈希听起来更像是一个更好的候选者,但是仍然可能存在隐藏的陷阱。我想到的是在某些文件上设置可执行位会更改树的哈希值。您必须先发布git config --local core.fileMode false才能避免这种情况。我不知道是否还有其他类似警告。
Zoltan

14

你可以做 tar -c /path/to/folder | sha1sum


16
如果要在另一台计算机上复制该校验和,则tar可能不是一个好选择,因为该格式似乎有歧义的余地并且存在于许多版本中,因此另一台计算机上的tar可能会从同一文件产生不同的输出。
slowdog 2011年

2
慢狗的有效顾虑,尽管您只关心文件内容,权限等,而不关心修改时间,但是可以添加如下--mtime选项:tar -c /path/to/folder --mtime="1970-01-01" | sha1sum
Binary Phile

@ S.Lott如果目录大小​​很大,我的意思是如果目录大小​​太大,则将其压缩并在其中放置md5将花费更多时间
Kasun Siyambalapitiya

13

如果您只想检查文件夹中的某些内容是否发生了变化,我建议您这样做:

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

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

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


1
考虑到解决方案的简单性,我不确定为什么没有更多的支持。谁能解释为什么这行不通?
Dave C

1
我认为这是不理想的,因为生成的哈希将基于文件所有者,日期格式设置等
。– Ryota

1
可以自定义ls命令以输出所需的任何内容。您可以将-l替换为-gG以省略组和所有者。您可以使用--time-style选项更改日期格式。基本上检查一下ls手册页,看看什么适合您的需求。
Shumoapp

@DaveC,因为它几乎没有用。如果要比较文件名,则直接比较它们。他们没有那么大。
纳文

7
@Navin从这个问题尚不清楚是否需要散列文件内容或检测树中的更改。每种情况都有其用途。例如,将45,000个文件名存储在内核树中比使用单个散列不那么实用。ls -lAgGR --block-size = 1 --time-style = +%s | sha1sum对我来说很棒
yashma '18

5

强大而干净的方法

  • 首先,不要浪费可用内存!散列文件而不是整个文件。
  • 针对不同需求/目的的不同方法(以下全部内容或选择适用的方法):
    • 仅散列目录树中所有条目的条目名称
    • 散列所有条目的文件内容(保留meta之类的内容,例如inode编号,ctime,atime,mtime,size等),您便会明白
    • 对于符号链接,其内容为引用名称。散列或选择跳过
    • 哈希条目内容时遵循或不遵循(解析名称)符号链接
    • 如果是目录,则其内容仅是目录条目。在递归遍历时,它们最终将被散列,但是是否应该对该级别的目录条目名称进行散列以标记该目录?在需要散列以快速识别更改而不必深入遍历以散列内容的用例中很有用。一个例子是文件的名称更改,但其余内容保持不变,并且都是相当大的文件
    • 妥善处理大文件(再次注意RAM)
    • 处理非常深的目录树(注意打开的文件描述符)
    • 处理非标准文件名
    • 如何处理套接字,管道/ FIFO,块设备,char设备等文件?还必须对它们进行哈希处理吗?
    • 在遍历时不要更新任何条目的访问时间,因为这在某些用例中会产生副作用并且会适得其反(直观?)。

这就是我的头等大事,任何花了一些时间从事这一工作的人实际上都会抓到其他陷阱和死角。

这是一个内存很少的工具,可以解决大多数情况,可能有些麻烦,但是很有帮助。

的示例用法和输出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

3

如果您只想散列文件的内容,而忽略文件名,则可以使用

cat $FILES | md5sum

计算哈希值时,请确保文件顺序相同:

cat $(echo $FILES | sort) | md5sum

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


2
将一个文件的末尾按字母顺序移动到紧随其后的文件的开头不会影响哈希,但应该会影响哈希。文件分隔符或文件长度将需要包含在哈希中。
杰森·斯坦格鲁姆

3

实现此目的的另一个工具:

http://md5deep.sourceforge.net/

听起来很像:类似于md5sum,但也具有递归功能以及其他功能。


1
尽管此链接可以回答问题,但最好在此处包括答案的基本部分,并提供链接以供参考。如果链接的页面发生更改,仅链接的答案可能会失效。
Mamoun Benghezal,2015年

3

如果这是一个git repo,而您想忽略中的任何文件.gitignore,则可能要使用此命令:

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

这对我来说很好。


非常感谢!:)
visortelle

对于许多应用程序,此方法是优越的。仅对源代码文件进行哈希处理可以在更少的时间内获得足够独特的哈希值。
John McGehee


1

尝试分两个步骤进行:

  1. 为文件夹中的所有文件创建带有哈希的文件
  2. 散列此文件

像这样:

# for FILE in `find /folder/of/stuff -type f | sort`; do sha1sum $FILE >> hashes; done
# sha1sum hashes

或一次完成所有操作:

# cat `find /folder/of/stuff -type f | sort` | sha1sum

for F in 'find ...' ...当名称中有空格时(现在一直如此),该功能将不起作用。
mivk 2012年


1

我编写了一个Groovy脚本来做到这一点:

import java.security.MessageDigest

public static String generateDigest(File file, String digest, int paddedLength){
    MessageDigest md = MessageDigest.getInstance(digest)
    md.reset()
    def files = []
    def directories = []

    if(file.isDirectory()){
        file.eachFileRecurse(){sf ->
            if(sf.isFile()){
                files.add(sf)
            }
            else{
                directories.add(file.toURI().relativize(sf.toURI()).toString())
            }
        }
    }
    else if(file.isFile()){
        files.add(file)
    }

    files.sort({a, b -> return a.getAbsolutePath() <=> b.getAbsolutePath()})
    directories.sort()

    files.each(){f ->
        println file.toURI().relativize(f.toURI()).toString()
        f.withInputStream(){is ->
            byte[] buffer = new byte[8192]
            int read = 0
            while((read = is.read(buffer)) > 0){
                md.update(buffer, 0, read)
            }
        }
    }

    directories.each(){d ->
        println d
        md.update(d.getBytes())
    }

    byte[] digestBytes = md.digest()
    BigInteger bigInt = new BigInteger(1, digestBytes)
    return bigInt.toString(16).padLeft(paddedLength, '0')
}

println "\n${generateDigest(new File(args[0]), 'SHA-256', 64)}"

您可以自定义用法,以避免打印每个文件,更改消息摘要,删除目录哈希等。我已经针对NIST测试数据对其进行了测试,并且可以正常工作。 http://www.nsrl.nist.gov/testdata/

gary-macbook:Scripts garypaduana$ groovy dirHash.groovy /Users/garypaduana/.config
.DS_Store
configstore/bower-github.yml
configstore/insight-bower.json
configstore/update-notifier-bower.json
filezilla/filezilla.xml
filezilla/layout.xml
filezilla/lockfile
filezilla/queue.sqlite3
filezilla/recentservers.xml
filezilla/sitemanager.xml
gtk-2.0/gtkfilechooser.ini
a/
configstore/
filezilla/
gtk-2.0/
lftp/
menus/
menus/applications-merged/

79de5e583734ca40ff651a3d9a54d106b52e94f1f8c2cd7133ca3bbddc0c6758

1

我必须检查整个目录以进行文件更改。

但要排除时间戳,目录所有权。

目标是在文件相同的情况下,在任何地方都获得相同的总和。

包括托管到其他计算机中,除了文件以外的任何东西,或对其进行更改。

md5sum * | md5sum | cut -d' ' -f1

它按文件生成一个哈希列表,然后将这些哈希串联为一个。

这比tar方法快得多。

为了更好地保护我们的哈希,我们可以在同一食谱上使用sha512sum

sha512sum * | sha512sum | cut -d' ' -f1

使用sha512sum的哈希值在任何地方都是相同的,但是没有已知的方法可以将其反转。


这似乎比哈希表的可接受答案要简单得多。我没有找到可接受的答案。一个问题...哈希有可能以不同顺序出现吗?sha256sum /tmp/thd-agent/* | sort我正在尝试进行可靠的排序,然后对其进行哈希处理。
thinktt

嗨,看起来哈希默认情况下按字母顺序排列。可靠的订购是什么意思?您必须自己整理所有内容。例如,使用关联数组,entry + hash。然后,按条目对该数组进行排序,这将按排序顺序提供一系列计算的哈希值。我相信您可以否则使用json对象,并直接对整个对象进行哈希处理。
NVRM

据我了解,您是说按字母顺序对文件进行哈希处理。好像没错 上面接受的答案中的某些内容有时会给我间歇性的不同命令,因此我只是在尝试确保不再发生这种情况。我将坚持最后进行排序。似乎正在工作。这种方法与接受的答案的唯一问题是我不认为它与嵌套文件夹无关。就我而言,我没有任何文件夹,因此效果很好。
thinktt

那又如何ls -r | sha256sum呢?
NVRM

@NVRM尝试了一下,它只是检查文件名更改,而不是文件内容
Gi0rgi0s

0

您可以sha1sum生成哈希值列表,然后sha1sum再次生成该列表,这取决于您要完成的目标。


0

这是Python 3中的一个简单,简短的变体,适用于小型文件(例如,源树或类似的东西,其中每个文件都可以轻松地放入RAM中),并根据其他解决方案的想法忽略了空目录:

import os, hashlib

def hash_for_directory(path, hashfunc=hashlib.sha1):                                                                                            
    filenames = sorted(os.path.join(dp, fn) for dp, _, fns in os.walk(path) for fn in fns)         
    index = '\n'.join('{}={}'.format(os.path.relpath(fn, path), hashfunc(open(fn, 'rb').read()).hexdigest()) for fn in filenames)               
    return hashfunc(index.encode('utf-8')).hexdigest()                          

它是这样的:

  1. 递归查找目录中的所有文件并按名称排序
  2. 计算每个文件的哈希值(默认值:SHA-1)(将整个文件读入内存)
  3. 用“ filename = hash”行创建文本索引
  4. 将该索引重新编码为UTF-8字节字符串,并对其进行哈希处理

如果SHA-1不是您喜欢的茶,则可以传入另一个哈希函数作为第二个参数。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.