如何比较两个tar包的内容

45

我想确定两个tar压缩包是否包含相同的文件,包括文件名和文件内容,但不包括日期、用户和组等元数据。

但是,有一些限制: 第一,我无法控制在创建tar文件时是否包含元数据,实际上,tar文件始终包含元数据,因此直接对比两个tar文件不起作用。 其次,由于一些tar文件非常大,我无法承受将它们解压到临时目录中并逐个对比其中的文件。(我知道如果我可以将file1.tar解压缩到file1/中,我可以通过在file/中调用“tar -dvf file2.tar”来比较它们。但通常我无法解压甚至一个文件)

你有什么想法如何比较这两个tar文件吗?如果可以在SHELL脚本中完成将更好。或者,有没有办法获取每个子文件的校验和,而不必实际解压tar文件?

谢谢


cksum打印tarball的CRC校验和和字节数。 - mechanical_meat
我同意亚当上面的评论,但我想补充一点,也许只是我个人的问题,但我会获取解压缩所需的磁盘空间。 - NoahD
1
我认为cksum不会起作用,因为在计算CRC时会考虑元数据。而且字节计数相等并不一定意味着文件内容相同。 - myjpa
12个回答

25

您也可以尝试使用 pkgdiff 来可视化包之间的差异(检测添加/删除/重命名文件和更改的内容,如果未更改则退出代码为零):

pkgdiff PKG-0.tgz PKG-1.tgz

enter image description here

enter image description here


12
你是否控制这些tar文件的创建?如果是,最好的技巧是在归档文件中创建一个MD5校验和并将其存储在文件中。然后,当您想要比较两个文件时,只需提取此校验和文件并进行比较。
如果你可以承受只解压一个tar文件,你可以使用tar的--diff选项来查找与其他tar文件内容的差异。
还有一种更加简单的方法,只需要比较文件名和大小。执行tar tvf列出每个文件的内容,并将输出存储在两个不同的文件中。然后切分除文件名和大小以外的所有内容。最好也对这两个文件进行排序。然后,只需在这两个列表之间进行文件差异比较即可。请记住,这种方案实际上并没有进行校验和。
$ tar tvfj pack1.tar.bz2
drwxr-xr-x user/group 0 2009-06-23 10:29:51 dir1/
-rw-r--r-- user/group 0 2009-06-23 10:29:50 dir1/file1
-rw-r--r-- user/group 0 2009-06-23 10:29:51 dir1/file2
drwxr-xr-x user/group 0 2009-06-23 10:29:59 dir2/
-rw-r--r-- user/group 0 2009-06-23 10:29:57 dir2/file1
-rw-r--r-- user/group 0 2009-06-23 10:29:59 dir2/file3
drwxr-xr-x user/group 0 2009-06-23 10:29:45 dir3/

生成按名称/大小排序的列表的命令

$ tar tvfj pack1.tar.bz2 | awk '{printf "%10s %s\n",$3,$6}' | sort -k 2
0 dir1/
0 dir1/file1
0 dir1/file2
0 dir2/
0 dir2/file1
0 dir2/file3
0 dir3/

您可以获取两个已排序的列表并进行比较。
如果满足您的需求,您还可以使用日期和时间列。


非常感谢,但我无法控制tarballs的创建:( - myjpa
很不幸,但你有一个Python解决方案。而且,它可以节省提取时的磁盘空间利用率。我的另外两个解决方案将作为启发式方法非常有用,当你需要速度时可以尝试使用。 - nik
实际上,如果您怀疑这两个存档文件很可能不同,那么为了快速得到结果,您可以使用我回答中提出的最后一个解决方案。因为这将始终捕获添加/删除的文件,并且如果文件更改其大小,则通常也会更改。 - nik
是的,我同意。这是一种快速的方法来检测文件数量/大小是否发生变化。 - myjpa
2
你也可以将两个这样的命令的输出直接传输到一个 diff 工具中,例如:meld <(tar tvfj ... | awk ...) <(tar tvfj ... | awk ...)。 - Raman

7

这是我的变体,它也检查了Unix权限:

仅在文件名小于200个字符时有效。

diff <(tar -tvf 1.tar | awk '{printf "%10s %200s %10s\n",$3,$6,$1}'|sort -k2) <(tar -tvf 2.tar|awk '{printf "%10s %200s %10s\n",$3,$6,$1}'|sort -k2)

加上一个解释会非常好。 - Rick

7

tarsum 几乎满足您的需求。将其输出通过 sort 进行排序以确保两者顺序相同,然后使用 diff 进行比较。这应该可以使您实现基本功能,并且很容易通过修改 Python 代码来将这些步骤整合到主程序中。


是的,我认为这很有帮助,代码非常简单明了。只不过我必须使用Python。 - myjpa
3
比较两个tarballs需要创建一对(文件,md5)条目列表,并计算两个列表之间的差异。这在纯shell中编写非常痛苦,而在Python或Perl中却很容易实现。这就是为什么你不太可能在这里找到一个纯shell答案 - 它正是激发创造这些语言的问题类型。如果你不想完全疯掉地编写这个东西,最好从tarsum(或tardiff Perl代码)开始,并根据你的具体需求进行自定义,而不是使用纯shell。 - Greg Smith
只是提供信息,链接中的最新tarsum在我的Mac上似乎有问题。(具有相容模式选项,但有些有问题,我不得不将其删除。) - Marcus
由于@Marcus的评论中提到的损坏包问题至今仍未得到解决,这使得很难为这个答案点赞。请注意,其他答案的点赞数为11。此外,这里没有截图可以看到你将要面对的情况。 - WinEunuuchs2Unix
小型的Python脚本tarsum已经发布在Github上https://github.com/mikemccabe/code/blob/master/tarsum,适用于Python3的版本在此Gist上:https://gist.github.com/sjmurdoch/5e089249bc465706f1ca32f195787ad8。后者在我的Xubuntu 19.10上完美运行。 - Stéphane Gourichon

6

编辑:请参考@StéphaneGourichon的评论。

我知道这是一个晚回复,但当我尝试实现同样的事情时,我遇到了这个线程。 我实施的解决方案将tar输出到stdout,并将其传输到您选择的任何哈希值:

tar -xOzf archive.tar.gz | sort | sha1sum

请注意,参数的顺序很重要,特别是O,它表示使用标准输出(stdout)。

1
该方法依赖于存档文件中的顺序。例如,两个连续的 Ubuntu 日常构建 tar 包可能具有相同的文件内容,而文件的顺序不同。 - youfu
4
tar -x0zf 命令会将整个归档文件的内容解压,然后 sort 命令将所有行按顺序排列,但这并不能解决“归档文件中的文件顺序”问题,反而因为混淆了行导致出现新的问题。两个归档文件可能由于行交换而不被发现。相反,应该获取文件列表,省略目录,对该列表进行排序,并告诉 tar 命令以完全按照那个顺序提取:tar -xOzf archive.tar.gz \tar -tzf archive.tar.gz | sed '//$/d' | sort` | sha1sum`。 - Roger Dueck
为什么 sha1sum archive.tar.gz 就不能直接工作呢? - Alexander Mills
2
在管道中使用“sort”实际上需要保留所有已解压缩的存档内容内存。如果存档太大,操作系统无法将其写入磁盘,则必定会失败。无论如何,如其他评论所指出的那样,这对机器来说是更多的工作量。我使用了@GregSmith的被接受答案中的tarsum,并对其感到非常满意。 - Stéphane Gourichon
@RogerDueck,你的解决方案有漏洞,它给出了与答案版本相同的结果,当一个tar.xz文件只有1个文件时,与第二个tar.xz相同,但第二个文件中有2个额外的非空文件。 - j riv
显示剩余2条评论

3

你是否在寻找 tardiff?它是一个“简单的Perl脚本”,用于比较两个tar包的内容并报告它们之间发现的任何差异。


3
从实现上看,它将文件的内容解压到临时目录中,因此它并没有完全解决他的问题 :/ - Charles Ma
此外,tardiff 报告错误,无法删除提取到 /tmp/tardiff-* 的内容,如果您在一个紧密的环境中工作,这会让情况变得更糟。 - Alastair
默认情况下,AIUI tardiff 仅检查文件名列表是否不同,而不检查文件本身是否不同。 - plugwash

3

还有diffoscope,它更通用,可以递归比较事物(包括各种格式)。

pip install diffoscope

2
我提议使用我用Go编写的gtarsum,这意味着它将是一个自主可执行文件(不需要Python或其他执行环境)。
go get github.com/VonC/gtarsum

它将读取一个tar文件,并且:
  • 按字母顺序对文件列表进行排序,
  • 计算每个文件内容的SHA256,
  • 将这些哈希连接成一个巨大的字符串,
  • 计算该字符串的SHA256。

结果是一个tar文件的“全局哈希”,基于文件列表和它们的内容。

它可以比较多个tar文件,并在它们相同时返回0,在它们不同时返回1。


可以省略连接步骤以查看不仅是不同而且有何不同吗?顺便说一句:我在这种情况下尝试了pkgdiff,并在比较包含裸git存储库的档案时遇到了问题。作为一个git专家,也许您知道是否有类似于git_diff_bares的工具 :) - grenix
@grenix 我没有实现详细的差异,因为那可能涉及到处理可能很大的列表来显示。比较两个裸仓库只需要至少比较它们分支的SHA1:不同的SHA,不同的仓库。 - VonC

1

由于以上解决方案都无法满足我的需求,所以我想提出一个新的解决方案。

该函数获取给定路径匹配的所有文件路径的md5哈希值的md5哈希值。如果哈希值相同,则文件层次结构和文件列表相同。

我知道它的性能不如其他解决方案,但它提供了我需要的确定性。

PATH_TO_CHECK="some/path"
for template in $(find build/ -name '*.tar'); do
    tar -xvf $template --to-command=md5sum | 
        grep $PATH_TO_CHECK -A 1 | 
        grep -v $PATH_TO_CHECK | 
        awk '{print $1}' | 
        md5sum | 
        awk "{print \"$template\",\$1}"
done

*注意:无效路径将不返回任何内容。


“给定路径下所有匹配的文件”:好主意。我将不得不将此功能添加到gtarsum中。 - VonC
@VonC FYI:从您的网站复制gtarsum导致一些Python无法识别的em破折号 =) - SgtPooki
1
"我的网站"? 你是指github.com吗? - VonC
1
我的错.. 不是 gtarsum,而是来自 http://www.guyrutenberg.com/2009/04/29/tarsum-02-a-read-only-version-of-tarsum/ 的 tarsum - SgtPooki

0

我有一个类似的问题,我用Python解决了它,这是代码。 ps:虽然这个代码用于比较两个zipball的内容,但它与tarball相似,希望我能帮到你。

import zipfile
import os,md5
import hashlib
import shutil

def decompressZip(zipName, dirName):
    try:
        zipFile = zipfile.ZipFile(zipName, "r")
        fileNames = zipFile.namelist()
        for file in fileNames:
            zipFile.extract(file, dirName)
        zipFile.close()
        return fileNames
    except Exception,e:
        raise Exception,e

def md5sum(filename):
    f = open(filename,"rb")
    md5obj = hashlib.md5()
    md5obj.update(f.read())
    hash = md5obj.hexdigest()
    f.close()
    return str(hash).upper()

if __name__ == "__main__":
    oldFileList = decompressZip("./old.zip", "./oldDir")
    newFileList = decompressZip("./new.zip", "./newDir")

    oldDict = dict()
    newDict = dict()

    for oldFile in oldFileList:
        tmpOldFile = "./oldDir/" + oldFile
        if not os.path.isdir(tmpOldFile):
            oldFileMD5 = md5sum(tmpOldFile)
            oldDict[oldFile] = oldFileMD5

    for newFile in newFileList:
        tmpNewFile = "./newDir/" + newFile
        if not os.path.isdir(tmpNewFile):
            newFileMD5 = md5sum(tmpNewFile)
            newDict[newFile] = newFileMD5

    additionList = list()
    modifyList = list()

    for key in newDict:
        if not oldDict.has_key(key):
            additionList.append(key)
        else:
            newMD5 = newDict[key]
            oldMD5 = oldDict[key]
            if not newMD5 == oldMD5:
            modifyList.append(key)

    print "new file lis:%s" % additionList
    print "modified file list:%s" % modifyList

    shutil.rmtree("./oldDir")
    shutil.rmtree("./newDir")

你可以重新编写deCompressZip函数,使用“tarfile”库。 - Jason Swift

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