【问题标题】:Linux: compute a single hash for a given folder & contents?Linux:计算给定文件夹和内容的单个哈希?
【发布时间】:2010-10-07 10:04:42
【问题描述】:

肯定有一种方法可以轻松做到这一点!

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

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

我想做类似的事情

sha1sum /folder/of/stuff > singlehashvalue

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

【问题讨论】:

  • “整个内容”是指目录中所有文件的逻辑数据还是到达根哈希时的数据以及元数据?由于您的用例的选择标准非常广泛,因此我试图在我的回答中解决一些实际的问题。

标签: linux bash hash


【解决方案1】:

一种可能的方法是:

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 量翻倍,第一个查找所有文件名并对内容进行校验和,第二个查找所有文件和目录名称,打印名称和模式。然后将对“文件名和校验和”列表,然后是“具有权限的名称和目录”进行校验和,以获得较小的校验和。

【讨论】:

  • 别忘了设置 LC_ALL=POSIX,这样各种工具都会创建独立于语言环境的输出。
  • 我找到了猫 | sha1sum 比 sha1sum 快得多 | sha1sum。 YMMV,在你的系统上尝试这些: time find path/to/folder -type f -print0 |排序-z | xargs -0 sha1sum |沙1和;时间查找路径/到/文件夹-type f -print0 |排序-z | xargs -0 猫 | sha1sum
  • @RichardBronosky - 假设我们有两个文件,A 和 B。A 包含“foo”,B 包含“bar was here”。使用您的方法,我们将无法将其与两个文件 C 和 D 分开,其中 C 包含“foobar”,D 包含“在这里”。通过单独散列每个文件,然后散列所有“文件名散列”对,我们可以看到差异。
  • 要使这项工作与目录路径无关(即当您要比较两个不同文件夹的哈希值时),您需要使用相对路径并更改到适当的目录,因为路径是包含在最终哈希中:find ./folder -type f -print0 | sort -z | xargs -0 sha1sum | sha1sum
  • @robbles 这是正确的,为什么我没有在 path/to/folder 位上添加初始 /
【解决方案2】:
  • 使用文件系统入侵检测工具,如aide

  • 对目录的 tar 球进行哈希处理:

    tar cvf - /path/to/folder | sha1sum

  • 自己编写代码,例如 vatine's oneliner:

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

【讨论】:

  • +1 用于 tar 解决方案。这是最快的,但去掉 v. 冗长只会减慢速度。
  • 请注意,当您比较文件时,tar 解决方案假定文件的顺序相同。它们是否取决于在进行比较时文件所在的文件系统。
  • git hash 不适用于此目的,因为文件内容只是其输入的一部分。即使对于分支的初始提交,哈希也会受到提交消息和提交元数据的影响,例如提交时间。如果多次提交同一个目录结构,每次都会得到不同的hash,因此产生的hash不适合仅通过发送hash来判断两个目录是否是彼此的精确副本。
  • @Zoltan 如果您使用树哈希而不是提交哈希,那么 git 哈希非常好。
  • @hobbs 答案最初是“提交哈希”,当然不适合这个目的。树哈希听起来像是一个更好的候选者,但仍然可能存在隐藏的陷阱。我想到的一个是在某些文件上设置可执行位会更改树哈希。在承诺避免这种情况之前,您必须发出git config --local core.fileMode false。不知道还有没有类似的注意事项。
【解决方案3】:

如果您只是想检查文件夹中的某些内容是否发生了变化,我推荐这个:

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

它只会给你一个 ls 输出的哈希值,其中包含文件夹、子文件夹、它们的文件、它们的时间戳、大小和权限。几乎所有你需要确定的东西是否发生了变化。

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

【讨论】:

  • 鉴于解决方案的简单性,我不确定为什么没有更多的赞成票。谁能解释为什么这不起作用?
  • 我认为这并不理想,因为生成的哈希将基于文件所有者、日期格式设置等。
  • ls 命令可以自定义输出任何你想要的。您可以将 -l 替换为 -gG 以省略组和所有者。您可以使用 --time-style 选项更改日期格式。基本上查看 ls 手册页,看看有什么适合您的需求。
  • @DaveC 因为它几乎没用。如果要比较文件名,直接比较即可。它们没那么大。
  • @Navin 从问题中不清楚是否需要散列文件内容或检测树中的变化。每种情况都有其用途。例如,在内核树中存储 45K 文件名不如单个散列实用。 ls -lAgGR --block-size=1 --time-style=+%s | sha1sum 非常适合我
【解决方案4】:

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

【讨论】:

  • 如果你想在另一台机器上复制校验和,tar 可能不是一个好的选择,因为格式似乎有歧义的空间并且存在于许多版本中,所以另一台机器上的 tar 可能从相同的文件产生不同的输出。
  • slowdog 的有效关注点尽管如此,但如果您关心文件内容、权限等而不是修改时间,您可以添加 --mtime 选项,如下所示:tar -c /path/to/folder --mtime="1970-01-01" | sha1sum
  • @S.Lott 如果目录很大,我的意思是如果目录很大,压缩它并在上面获取 md5 需要更多时间
【解决方案5】:

到目前为止,最快的方法仍然是使用 tar。并且通过几个额外的参数我们也可以摆脱元数据造成的差异。

要使用 tar 对目录进行哈希处理,需要在 tar 期间确保对路径进行排序,否则总是不同。

tar -C <root-dir> -cf - --sort=name <dir> | sha256sum

忽略时间

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

忽略所有权

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

忽略一些文件

使用--exclude=PATTERN

【讨论】:

  • 这是涉及 GNU tar 的最佳答案,因为它确保文件内容和目录结构的比较一致。
  • 警告:并非所有版本的 tar 都有 --sort :-(
【解决方案6】:

稳健且干净的方法

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

这就是我的想法,任何花了一些时间在这方面工作的人实际上都会发现其他陷阱和极端情况。

Here's a tool,内存非常少,解决了大多数情况,可能有点粗糙,但很有帮助。

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

人类友好输出的 sn-p:

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

【讨论】:

  • 你能举一个简单的例子来获得一个健壮和干净的文件夹 sha256,也许是一个包含三个子目录和几个文件的 Windows 文件夹?
【解决方案7】:

如果这是一个 git repo 并且你想忽略 .gitignore 中的任何文件,你可能想使用这个:

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

这对我来说效果很好。

【讨论】:

  • 对于许多应用程序来说,这种方法是优越的。仅对源代码文件进行散列运算可以在更短的时间内获得足够独特的散列。
  • 终于有了跨环境一致的东西。谢谢!!
【解决方案8】:

如果你只想散列文件的内容,忽略文件名,那么你可以使用

cat $FILES | md5sum

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

cat $(echo $FILES | sort) | md5sum

但是你的文件列表中不能有目录。

【讨论】:

  • 将一个文件的结尾移动到按字母顺序跟随它的文件的开头不会影响哈希,但应该。文件分隔符或文件长度需要包含在哈希中。
【解决方案9】:

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

http://md5deep.sourceforge.net/

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

md5deep -r {direcotory}

【讨论】:

  • 虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接的答案可能会失效。
【解决方案10】:

有一个python脚本:

http://code.activestate.com/recipes/576973-getting-the-sha-1-or-md5-hash-of-a-directory/

如果您更改文件的名称而不更改其字母顺序,则哈希脚本不会检测到它。但是,如果您更改文件的顺序或任何文件的内容,运行脚本会得到与以前不同的哈希值。

【讨论】:

    【解决方案11】:

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

    但不包括时间戳、目录所有权。

    如果文件相同,目标是在任何地方获得相同的总和。

    包括托管到其他机器上,不管是文件还是对它们的更改。

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

    它按文件生成哈希列表,然后将这些哈希连接成一个。

    这比 tar 方法快得多。

    为了在我们的哈希中获得更强的隐私,我们可以在同一个配方上使用 sha512sum

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

    使用 sha512sum 的任何地方的哈希值也是相同的,但没有已知的方法来反转它。

    【讨论】:

    • 这似乎比散列目录的公认答案简单得多。我发现接受的答案并不可靠。一个问题......哈希值是否有可能以不同的顺序出现? sha256sum /tmp/thd-agent/* | sort 是我正在尝试的可靠排序,然后只是散列。
    • 您好,看起来哈希默认是按字母顺序排列的。您所说的可靠订购是什么意思?您必须自己组织所有这些。例如使用关联数组,entry + hash。然后你按条目对这个数组进行排序,这给出了一个按排序顺序计算的哈希列表。我相信您可以使用 json 对象,并直接散列整个对象。
    • 如果我理解你的意思是它按字母顺序散列文件。这似乎是对的。上面接受的答案中的某些内容有时会给我间歇性的不同命令,所以我只是想确保这种情况不会再次发生。我会坚持把排序放在最后。似乎正在工作。这种方法与我看到的接受的答案的唯一问题是它不处理嵌套文件夹。就我而言,我没有任何文件夹,所以效果很好。
    • ls -r | sha256sum 呢?
    • @NVRM 试过了,它只是检查文件名更改,而不是文件内容
    【解决方案12】:

    kvantour's answer添加多处理和进度条

    大约快 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 }'
    

    确保已安装 tqdmpip install tqdm 或查看文档

    awk 将删除文件路径,这样如果父目录或路径不同,则不会影响哈希

    【讨论】:

    • 这需要一个 |在最后一个 sha1sum 之前排序以获得一致的结果(除非 tqdm 负责?我没有用 tqdm 测试)
    • 是的,我只是在没有看到您的推荐的情况下添加了它,现在我希望我以前看到过您的推荐。
    【解决方案13】:

    尝试分两步完成:

    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 ...' ... 在名称中有空格时不起作用(现在您总是这样做)。
    【解决方案14】:

    我会将单个文件的结果通过sort(以防止仅仅对文件重新排序以更改哈希值)传送到md5sumsha1sum,无论您选择哪个。

    【讨论】:

      【解决方案15】:

      我已经编写了一个 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
      

      【讨论】:

        【解决方案16】:

        您可以sha1sum 生成哈希值列表,然后再次sha1sum 该列表,这取决于您想要完成的具体内容。

        【讨论】:

          【解决方案17】:

          这是 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 不是您的理想之选,您可以传入 different hash function 作为第二个参数。

          【讨论】:

            【解决方案18】:

            您可以尝试hashdir,这是一个为此目的而编写的开源命令行工具。

            hashdir /folder/of/stuff

            它有几个有用的标志,允许您指定散列算法,打印所有子节点的散列,以及保存和验证散列。

            hashdir:
              A command-line utility to checksum directories and files.
            
            Usage:
              hashdir [options] [<item>...] [command]
            
            Arguments:
              <item>    Directory or file to hash/check
            
            Options:
              -t, --tree                                         Print directory tree
              -s, --save                                         Save the checksum to a file
              -i, --include-hidden-files                         Include hidden files
              -e, --skip-empty-dir                               Skip empty directories
              -a, --algorithm <md5|sha1|sha256|sha384|sha512>    The hash function to use [default: sha1]
              --version                                          Show version information
              -?, -h, --help                                     Show help and usage information
            
            Commands:
              check <item>    Verify that the specified hash file is valid.
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2021-06-19
              • 1970-01-01
              • 1970-01-01
              • 2017-05-17
              • 2016-06-23
              相关资源
              最近更新 更多