如何查找重复的目录

时间:2017-04-22 15:09:32

标签: bash perl

让我们创建一些测试目录树:

#!/bin/bash

top="./testdir"
[[ -e "$top" ]] && { echo "$top already exists!" >&2; exit 1; }

mkfile() { printf "%s\n" $(basename "$1") > "$1"; }

mkdir -p "$top"/d1/d1{1,2}
mkdir -p "$top"/d2/d1some/d12copy
mkfile "$top/d1/d12/a"
mkfile "$top/d1/d12/b"
mkfile "$top/d2/d1some/d12copy/a"
mkfile "$top/d2/d1some/d12copy/b"
mkfile "$top/d2/x"
mkfile "$top/z"

结构是:find testdir \( -type d -printf "%p/\n" , -type f -print \)

testdir/
testdir/d1/
testdir/d1/d11/
testdir/d1/d12/
testdir/d1/d12/a
testdir/d1/d12/b
testdir/d2/
testdir/d2/d1some/
testdir/d2/d1some/d12copy/
testdir/d2/d1some/d12copy/a
testdir/d2/d1some/d12copy/b
testdir/d2/x
testdir/z

我需要找到重复的目录,但我只需要考虑文件(例如我应该忽略没有文件的(子)目录)。因此,从上面的测试树中得到的结果是:

duplicate directories:
testdir/d1
testdir/d2/d1some

因为在两个(子)树中只有两个相同的文件ab。 (和几个目录,没有文件)。

当然,我可以md5deep -Zr .使用perl脚本(使用File::Find + Digest::MD5或使用Path::Tiny等)来遍历整棵树。 )并计算文件的md5-digests,但这对查找重复的目录没有帮助... :(

知道怎么做吗?老实说,我不知道。

修改

  • 我不需要工作code。 (我能自己编码)
  • 我“只是”需要一些想法“如何处理”问题的解决方案。 :)

EDIT2

背后的理由 - 为什么需要这样:由于错误的备份策略,我从大量外部硬盘驱动器复制了大约2.5 TB的数据。例如。多年来,整个$HOME目录被复制到(许多不同的)外部硬盘驱动器中。许多子目录具有相同的内容,但它们位于不同的路径中。所以,现在我试图消除相同内容的目录。

我需要通过目录执行此操作,因为这里有目录,其中包含一些重复文件,但不是全部。让我们说:

/some/path/project1/a
/some/path/project1/b

/some/path/project2/a
/some/path/project2/x

e.g。 a是一个重复的文件(不仅是名称,而且也是内容) - 但这两个项目都需要它。所以我想在两个目录中保留a - 即使它们是重复文件。因此,我寻找一个“逻辑”如何找到重复的目录。

2 个答案:

答案 0 :(得分:4)

一些关键点:

  • 如果我理解正确(从您的评论中,您说的话:"(另外,当我说相同的文件时,我的意思是相同的内容,而不是他们的名字)" ,您希望找到重复的目录,例如,其内容与其他目录中的内容完全相同,,无论文件名是什么
  • 为此您必须为文件计算一些校验和或摘要。相同的摘要=相同的文件。 (很有可能)。 :)正如您已经说过的那样,md5deep -Zr -of /top/dir是一个很好的起点。
  • 我添加了-of,因为对于这样的工作,你不想计算符号链接目标或其他特殊文件(如fifo)的内容 - 只是普通文件。
  • 计算2.5TB树中每个文件的md5,确实需要几个小时的工作,除非你有非常快的机器。 md5deep为每个cpu-core运行一个线程。因此,在运行时,您可以制作一些脚本。
  • 此外,请考虑将md5deep作为sudo运行,因为如果经过长时间运行后您会收到一些有关不可读文件的错误消息,那可能会令人沮丧,因为您忘记更改文件所有权......(只是一个注释):) :))

对于"如何":

  • 用于比较"目录"你需要计算一些"目录摘要",以便于比较和查找重复项。
  • 最重要的一点是实现以下要点:
    • 您可以排除目录,其中包含具有唯一摘要的文件。如果文件是唯一的,例如没有任何重复,这意味着毫无意义地检查它的目录。某些目录中的唯一文件意味着该目录也是唯一的。因此,脚本应该忽略具有唯一MD5摘要的文件的每个目录(来自md5deep的输出。)
    • 您不需要计算"目录摘要"来自文件本身。 (当你在your followup question中尝试时)。这足以计算"目录摘要"使用已计算的md5作为文件,必须确保您首先对它们进行排序!

e.g。例如,如果您的目录/path/to/some仅包含两个文件ab以及

if file "a" has md5 : 0cc175b9c0f1b6a831c399e269772661
and file "b" has md5: 92eb5ffee6ae2fec3ad71c777531578f

你可以计算"目录摘要"来自上述文件摘要,例如,使用你可以做的Digest::MD5

perl -MDigest::MD5=md5_hex -E 'say md5_hex(sort qw( 92eb5ffee6ae2fec3ad71c777531578f 0cc175b9c0f1b6a831c399e269772661))'

并将3bc22fb7aaebe9c8c5d7de312b876bb8作为您的"目录摘要"。排序是至关重要的(!),因为相同的命令,但没有排序:

perl -MDigest::MD5=md5_hex -E 'say md5_hex(qw( 92eb5ffee6ae2fec3ad71c777531578f 0cc175b9c0f1b6a831c399e269772661))'

生成:3a13f2408f269db87ef0110a90e168ae

注意,即使上述摘要不是文件的摘要,但它们对于具有不同文件的每个目录都是唯一的,并且对于相同的文件将是相同的。 (因为相同的文件,具有相同的md5文件摘要)。排序可确保您始终以相同的顺序计算摘要,例如如果其他目录将包含两个文件

file "aaa" has md5 : 92eb5ffee6ae2fec3ad71c777531578f
file "bbb" has md5 : 0cc175b9c0f1b6a831c399e269772661

使用上述sort and md5,您将再次获得:3bc22fb7aaebe9c8c5d7de312b876bb8 - 例如包含与上述相同文件的目录...

因此,通过这种方式,您可以计算一些"目录摘要"对于您拥有的每个目录,并且可以确保如果您获得另一个目录摘要3bc22fb7aaebe9c8c5d7de312b876bb8这意味着:此目录具有上述两个文件ab(即使它们的名称不同)。

这种方法很快,因为你将计算"目录摘要"只有小的32字节字符串,所以你避免了过多的文件摘要 - caclulations。

最后一部分现在很简单。您的最终数据应采用以下形式:

3a13f2408f269db87ef0110a90e168ae /some/directory
16ea2389b5e62bc66b873e27072b0d20 /another/directory
3a13f2408f269db87ef0110a90e168ae /path/to/other/directory

因此,很容易得到:

/some/directory/path/to/other/directory相同,因为它们具有相同的"目录摘要"。

嗯......以上所有只是几行perl脚本。可能会更快地直接在这里写perl脚本作为上面的长文本答案 - 但是,你说 - 你不想要代码...... :) :)

答案 1 :(得分:3)

遍历可以识别您描述的重复项目录。我认为这是:如果目录中的所有文件都等于另一个文件的所有文件,那么它们的路径是重复的。

查找每个目录中的所有文件,并形成一个包含其名称的字符串。你可以用逗号连接名称,比如说(或者其他一些肯定不是任何名字的序列)。这是要比较的。在此字符串前面添加路径,以便识别目录。

比较可以通过填充散列来实现比较,其中键是具有文件名的字符串并且路径是它们的值。一旦发现密钥已经存在,您就可以检查文件的内容,并将路径添加到重复列表中。

不必实际形成带路径的字符串,因为您可以在遍历期间构建哈希和欺骗列表。如果需要,首先拥有完整列表允许其他类型的会计。

这完全是非常少的代码。

一个例子。假设你有

dir1/subdir1/{a,b}  # duplicates (files 'a' and 'b' are considered equal)
dir2/subdir2/{a,b}

proj1/subproj1/{a,b,X}  # NOT duplicates, since there are different files
proj2/subproj2/{a,b,Y}

以上处方会给你字符串

'dir1/subdir1/a,b',
'dir2/subdir2/a,b',
'proj1/subproj1/a,b,X',
'proj2/subproj2/a,b,Y';

其中(子)字符串'a,b'dir1/subdir1dir2/subdir2标识为重复。

我看不出如何避免遍历来构建一个占所有文件的系统。

上述步骤是第一步,不处理包含子目录的目录。

考虑

   dirA/          dirB/
a b sdA/       a X sdB/
    c d            c d

此处路径dirA/sdA/dirB/sdB/与问题说明重复,但整个dirA/dirB/是不同的。这个问题没有在问题中显示,但我希望它会引起人们的兴趣。

可以为此修改第一部分的程序。遍历目录,在每一步形成路径组件。获取每个子目录中的所有文件(如果我们没有完成)。将逗号分隔的文件列表附加到路径组件(/sdA/)。所以上面的表示是

'dirA/sdA,a,b/c,d',  'dirB/sdB,a,X/c,d'

对于已经存在的每个文件列表子字符串(c,d),我们可以逐个组件地检查其现有的路径。现在使用像c,d这样的键的哈希将不会执行,因为此示例对于不同的层次结构具有相同的文件列表,但是需要修改的(或其他)数据结构。

最后,可能会有更多与sdA平行的子目录(比如sdA2)。我们只关心它自己的路径,但路径a,b的那个组件中的并行文件(dirA/sdaA2,a,b/除外)。因此请记住所有底层文件列表(c,d)及其路径,如果文件列表相同且路径长度相同,请检查其路径是否有a,b个文件列表相等在每个路径组件中。

我不知道这对你来说是否是一个可行的解决方案,但我希望“近似重复”很少见 - 备份要么重复,要么不重复。因此,在复杂的蔓延层次结构中可能没有太多需要处理更多边缘情况。这个程序至少应该是一个有用的预选机制,这将大大减少进一步工作的需要。

这假设相同的文件名很可能表示相同的文件。其中一部分是我的期望,如果一个文件甚至只是重命名它仍然不能被视为重复。如果不是这样,这种方法将不起作用,并且需要answer by jm666的某些内容。