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

154

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

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

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

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

sha1sum /folder/of/stuff > singlehashvalue

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


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

3
你可以尝试使用 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.

这并没有回答问题。一旦您拥有足够的声望,您将能够评论任何帖子;相反,提供不需要询问者澄清的答案。- 来自审核 - Ejdrien

3
快速摘要:如何对整个文件夹的内容进行哈希,或比较两个文件夹是否相等
# 1. How to get a sha256 hash over all file contents in a folder, including
# hashing over the relative file paths within that folder to check the
# filenames themselves (get this bash function below).
sha256sum_dir "path/to/folder"

# 2. How to quickly compare two folders (get the `diff_dir` bash function below)
diff_dir "path/to/folder1" "path/to/folder2"
# OR:
diff -r -q "path/to/folder1" "path/to/folder2"

“一行代码”

请使用以下这个命令,而不是主答案,以获取整个文件夹中所有非目录文件内容的单个哈希值,无论该文件夹位于何处:

这是一个“一行代码”的命令。复制并粘贴整个命令以一次性运行它:

# This one works, but don't use it, because its hash output does NOT
# match that of my `sha256sum_dir` function. I recommend you use
# the "1-liner" just below, therefore, instead.

time ( \
    starting_dir="$(pwd)" \
    && target_dir="path/to/folder" \
    && cd "$target_dir" \
    && find . -not -type d -print0 | sort -zV \
    | xargs -0 sha256sum | sha256sum; \
    cd "$starting_dir"
)

然而,这会产生与我下面介绍的sha256sum_dir bash函数生成的哈希值略有不同。因此,如果要使输出哈希值与我的sha256sum_dir函数的输出完全匹配,请执行以下操作:
# Use this one, as its output matches that of my `sha256sum_dir`
# function exactly.

all_hashes_str="$( \
    starting_dir="$(pwd)" \
    && target_dir="path/to/folder" \
    && cd "$target_dir" \
    && find . -not -type d -print0 | sort -zV | xargs -0 sha256sum \
    )"; \
    cd "$starting_dir"; \
    printf "%s" "$all_hashes_str" | sha256sum

中译英:

要了解为什么主要答案在不同位置的相同文件夹中不会产生相同的哈希,请参见下文。

[我偏爱的方法] 这里是我写的一些 bash 函数:sha256sum_dirdiff_dir

将以下函数放入您的 ~/.bashrc 文件或您的 ~/.bash_aliases 文件中,假设您的 ~/.bashrc 文件像这样引用 ~/.bash_aliases 文件:

if [ -f ~/.bash_aliases ]; then
    . ~/.bash_aliases
fi

你可以在我的 ~/.bash_aliases 文件中找到下面的两个函数,它们都在我的 eRCaGuy_dotfiles 存储库中。
下面是 sha256sum_dir 函数,它获取目录中所有文件的总哈希值:
# Take the sha256sum of all files in an entire dir, and then sha256sum that
# entire output to obtain a _single_ sha256sum which represents the _entire_
# dir.
# See:
# 1. [my answer] https://dev59.com/kXM_5IYBdhLWcg3wn0lO#72070772
sha256sum_dir() {
    return_code="$RETURN_CODE_SUCCESS"
    if [ "$#" -eq 0 ]; then
        echo "ERROR: too few arguments."
        return_code="$RETURN_CODE_ERROR"
    fi
    # Print help string if requested
    if [ "$#" -eq 0 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
        # Help string
        echo "Obtain a sha256sum of all files in a directory."
        echo "Usage:  ${FUNCNAME[0]} [-h|--help] <dir>"
        return "$return_code"
    fi

    starting_dir="$(pwd)"
    target_dir="$1"
    cd "$target_dir"

    # See my answer: https://dev59.com/kXM_5IYBdhLWcg3wn0lO#72070772
    filenames="$(find . -not -type d | sort -V)"
    IFS=$'\n' read -r -d '' -a filenames_array <<< "$filenames"
    time all_hashes_str="$(sha256sum "${filenames_array[@]}")"
    cd "$starting_dir"

    echo ""
    echo "Note: you may now call:"
    echo "1. 'printf \"%s\n\" \"\$all_hashes_str\"' to view the individual" \
         "hashes of each file in the dir. Or:"
    echo "2. 'printf \"%s\" \"\$all_hashes_str\" | sha256sum' to see that" \
         "the hash of that output is what we are using as the final hash" \
         "for the entire dir."
    echo ""
    printf "%s" "$all_hashes_str" | sha256sum | awk '{ print $1 }'
    return "$?"
}
# Note: I prefix this with my initials to find my custom functions easier
alias gs_sha256sum_dir="sha256sum_dir"

假设您只想比较两个目录是否相同,您可以使用 diff -r -q "dir1" "dir2" 命令,我将其封装在这个 diff_dir 命令中。我从这里了解到使用 diff 命令比较整个文件夹的方法:如何在Linux中检查两个文件夹是否相同
# Compare dir1 against dir2 to see if they are equal or if they differ.
# See:
# 1. How to `diff` two dirs: https://dev59.com/HHRB5IYBdhLWcg3w9L3n#16404554
diff_dir() {
    return_code="$RETURN_CODE_SUCCESS"
    if [ "$#" -eq 0 ]; then
        echo "ERROR: too few arguments."
        return_code="$RETURN_CODE_ERROR"
    fi
    # Print help string if requested
    if [ "$#" -eq 0 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
        echo "Compare (diff) two directories to see if dir1 contains the same" \
             "content as dir2."
        echo "NB: the output will be **empty** if both directories match!"
        echo "Usage:  ${FUNCNAME[0]} [-h|--help] <dir1> <dir2>"
        return "$return_code"
    fi

    dir1="$1"
    dir2="$2"
    time diff -r -q "$dir1" "$dir2"
    return_code="$?"
    if [ "$return_code" -eq 0 ]; then
        echo -e "\nDirectories match!"
    fi

    # echo "$return_code"
    return "$return_code"
}
# Note: I prefix this with my initials to find my custom functions easier
alias gs_diff_dir="diff_dir"

以下是我在~/temp2目录上运行sha256sum_dir命令的输出结果(我会在下面描述这个目录,以便您可以重现并测试此命令)。您可以看到,在这种情况下,整个文件夹的哈希值为b86c66bcf2b033f65451e8c225425f315e618be961351992b7c7681c3822f6a3
$ gs_sha256sum_dir ~/temp2

real    0m0.007s
user    0m0.000s
sys 0m0.007s

Note: you may now call:
1. 'printf "%s\n" "$all_hashes_str"' to view the individual hashes of each 
file in the dir. Or:
2. 'printf "%s" "$all_hashes_str" | sha256sum' to see that the hash of that 
output is what we are using as the final hash for the entire dir.

b86c66bcf2b033f65451e8c225425f315e618be961351992b7c7681c3822f6a3

这里是 diff_dir 命令和输出,用于比较两个目录是否相等。这是检查刚刚将整个目录复制到我的 SD 卡是否正确工作的过程。当目录匹配时,输出会显示 Directories match!
$ gs_diff_dir "path/to/sd/card/tempdir" "/home/gabriel/tempdir"

real    0m0.113s
user    0m0.037s
sys 0m0.077s

Directories match!

为什么主答案在不同位置的相同文件夹中不能产生相同的哈希值

我尝试了这里最受欢迎的答案,但它并不能完全正确地工作。它需要一些微调。它之所以不能正常工作是因为哈希值基于感兴趣的文件夹的基本路径而改变!这意味着即使两个文件夹是完美匹配并且包含完全相同的内容,某个文件夹的完全复制品将具有与从中复制的文件夹不同的哈希值!这种情况有点让人失望,因为如果两个相同文件夹的哈希值不同,那么对该文件夹进行哈希处理就没有意义了!让我解释一下:

假设我有一个名为temp2的文件夹,在~/temp2下。它包含file1.txtfile2.txtfile3.txtfile1.txt包含字母a和一个回车,file2.txt包含字母b和一个回车,file3.txt包含字母c和一个回车。
如果我运行find /home/gabriel/temp2,我会得到:
$ find /home/gabriel/temp2
/home/gabriel/temp2
/home/gabriel/temp2/file3.txt
/home/gabriel/temp2/file1.txt
/home/gabriel/temp2/file2.txt

如果我按照主要答案所述的相同模式将其转发到sha256sum(而不是sha1sum),我会得到以下结果。请注意,它在每个哈希后面都有完整路径,这不是我们想要的:
$ find /home/gabriel/temp2 -type f -print0 | sort -z | xargs -0 sha256sum
87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7  /home/gabriel/temp2/file1.txt
0263829989b6fd954f72baaf2fc64bc2e2f01d692d4de72986ea808f6e99813f  /home/gabriel/temp2/file2.txt
a3a5e715f0cc574a73c3f9bebb6bc24f32ffd5b67b387244c2c909da779a1478  /home/gabriel/temp2/file3.txt

如果你将上述输出字符串输入到 sha256sum 中,它会使用文件哈希值的完整文件路径进行哈希,这不是我们想要的!文件哈希值可能在一个文件夹和该文件夹的副本中完全匹配,但是绝对路径并不完全匹配,因此它们将产生不同的最终哈希值,因为我们正在哈希完整文件路径作为我们单个最终哈希的一部分!
相反,我们想要的是每个哈希值旁边的相对文件路径。为此,您必须先 cd 进入您感兴趣的文件夹,然后再运行其中所有文件的哈希命令,如下所示:
cd "/home/gabriel/temp2" && find . -type f -print0 | sort -z | xargs -0 sha256sum

现在我明白了。请注意,文件路径现在都是相对路径,这正是我想要的!
$ cd "/home/gabriel/temp2" && find . -type f -print0 | sort -z | xargs -0 sha256sum
87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7  ./file1.txt
0263829989b6fd954f72baaf2fc64bc2e2f01d692d4de72986ea808f6e99813f  ./file2.txt
a3a5e715f0cc574a73c3f9bebb6bc24f32ffd5b67b387244c2c909da779a1478  ./file3.txt

好的。现在,如果我对整个输出字符串进行哈希处理,由于其中所有文件路径都是相对的,因此最终哈希将完全匹配一个文件夹及其副本!通过这种方式,我们可以哈希目录中的文件内容和文件名,以获取给定文件夹的不同哈希值,如果文件内容不同或文件名不同,或两者都不同。

2

下面是一个简单的Python 3变体,适用于小型文件(例如源码树或者其他每个文件容易放入内存中的地方),忽略空目录,基于其他解决方案的想法。

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不适合您的需要,您可以作为第二个参数传入不同的哈希函数

2
我需要检查整个目录的文件变化,但要排除时间戳、目录所有权等因素。目标是获得同一总和,如果文件相同,则在其他机器上托管,不考虑任何除文件外的因素或对它们的更改。
md5sum * | md5sum | cut -d' ' -f1

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

这比tar方法更快。

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

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

使用 sha512sum,哈希值也是相同的,但目前没有已知的方法可以反向解密。


这种方法看起来比哈希目录的已接受答案简单得多。我发现已接受的答案不太可靠。有一个问题...哈希值可能以不同的顺序出现吗?sha256sum /tmp/thd-agent/* | sort是我尝试获得可靠排序的方法,然后只需对其进行哈希处理即可。 - thinktt
嗨,看起来哈希默认按字母顺序排列。你所说的可靠排序是什么意思?你必须自己组织所有内容。例如使用关联数组、条目+哈希。然后,通过条目对这个数组进行排序,这会按照排序顺序给出计算哈希的列表。否则,我认为你可以使用JSON对象,直接对整个对象进行哈希处理。 - NVRM
如果我理解正确,它会按字母顺序对文件进行哈希处理。这似乎是正确的。上面接受的答案中有时会给我不同的顺序,所以我只是想确保这种情况不会再次发生。我将坚持在最后放置排序。看起来很有效。我看到这种方法与接受的答案相比唯一的问题是它无法处理嵌套文件夹。在我的情况下,我没有任何文件夹,所以这个方法非常适用。 - thinktt
ls -r | sha256sum 是什么意思? - NVRM
@NVRM 尝试了一下,它只检查文件名的更改,而不是文件内容。 - Gi0rgi0s

2

1

我会将每个文件的结果通过 sort(以防止仅仅重新排序文件就改变哈希值)传输到 md5sumsha1sum,具体使用哪一个取决于您的选择。


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

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

0
你可以使用sha1sum生成哈希值列表,然后再次对该列表进行sha1sum,具体取决于你想要实现什么。

0

如何哈希整个目录中的所有文件,包括文件名和内容

假设您正在尝试比较一个文件夹及其所有内容,以确保它从一台计算机正确地复制到另一台计算机,例如,您可以按照以下方式执行。 假设文件夹命名为mydir,在计算机1上的路径为/home/gabriel/mydir,在计算机2上的路径为/home/gabriel/dev/repos/mydir

# 1. First, cd to the dir in which the dir of interest is found. This is
# important! If you don't do this, then the paths output by find will differ
# between the two computers since the absolute paths to `mydir` differ. We are
# going to hash the paths too, not just the file contents, so this matters. 
cd /home/gabriel            # on computer 1
cd /home/gabriel/dev/repos  # on computer 2

# 2. hash all files inside `mydir`, then hash the list of all hashes and their
# respective file paths. This obtains one single final hash. Sorting is
# necessary by piping to `sort` to ensure we get a consistent file order in
# order to ensure a consistent final hash result.
find mydir -type f -exec sha256sum {} + | sort | sha256sum

# Optionally pipe that output to awk to filter in on just the hash (first field
# in the output)
find mydir -type f -exec sha256sum {} + | sort | sha256sum | awk '{print $1}'

就是这样!

为了学习的缘故,要查看文件哈希值的中间列表,只需运行此命令:

find mydir -type f -exec sha256sum {} + | sort

请注意,上述命令忽略空目录、文件权限、文件上次编辑的时间戳等。但在大多数情况下是可以的。
例子
这里是一个真实运行和实际输出的例子。我想确保我的eclipse-workspace文件夹从一台电脑正确地复制到另一台电脑。正如您所看到的,time 命令告诉我它用了11.790秒:
$ time find eclipse-workspace -type f -exec sha256sum {} + | sort | sha256sum
8f493478e7bb77f1d025cba31068c1f1c8e1eab436f8a3cf79d6e60abe2cd2e4  -

real    0m11.790s
user    0m11.372s
sys 0m0.432s

我关心的哈希值是:8f493478e7bb77f1d025cba31068c1f1c8e1eab436f8a3cf79d6e60abe2cd2e4

如果将其传输到awk并排除time,我会得到:

$ find eclipse-workspace -type f -exec sha256sum {} + | sort | sha256sum | awk '{print $1}'
8f493478e7bb77f1d025cba31068c1f1c8e1eab436f8a3cf79d6e60abe2cd2e4

请确保检查打印的stderr输出中的find错误,因为即使find失败,也会生成哈希。

考虑到eclipse-workspace目录包含6480个文件,仅用12秒对其进行哈希处理是令人印象深刻的,如下所示:

find eclipse-workspace -type f | wc -l

...并且文件大小为3.6 GB,如下所示:

du -sh eclipse-workspace

另请参阅

  1. 我在这里的另一个答案,其中我使用了上面的信息:如何在linux中检查两个文件夹是否相同

其他致谢:我曾与ChatGPT交流,学习了上述部分内容。然而,所有的工作和文本都是我编写、测试和验证的。


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