65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 md5 和 Locate 查找重复文件

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2022年11月17日

CPOL

5分钟阅读

viewsIcon

5711

本技巧展示了如何使用 md5 和 locate 来查找重复文件。

引言

这是对 locate Unix 命令用法的一次后续跟进。在我之前的文章 在 MacOS Unix 上使用 Locate 数据库 中,我解释了名称数据库的内部工作原理以及访问它们的 locate 命令。本技巧现在将展示如何使用它们来识别重复文件。

下面的示例以及之前的文章都基于 MacOS High Sierra。我注意到较新的 MacOS 版本有一个较新的 locate 命令。

背景

这篇文章的背景是我最近找到了一个旧的 Compact Flash 卡,上面备份了我几年前的电脑硬盘文件,然后格式化了硬盘并连同电脑一起卖掉了。我当时正在寻找一种快速方法来检查这张 Compact Flash 卡上是否有我忘记复制到新电脑的文件。

Using the Code

正如我在引言部分提到的上一篇文章中所述,find 命令是用于填充 names 数据库的 locate.updatedb 命令的核心,然后 locate 命令会查询该数据库。

开箱即用,find 命令会打印包含完整路径的文件名,该文件名然后存储在 names 数据库中。有趣的是,此输出可以修改,从而可以增强存储在名称数据库中的信息,并且 locate 可以对其进行搜索。

为了演示这一点,我修改了我之前文章中 locate.updatedb 命令的增强版本,并通过 md5 指纹扩展了输出。

$ cat locate.updatedb.md5 
#!/bin/sh
#
# Copyright (c) September 1995 Wolfram Schneider <wosch@freebsd.org>. Berlin.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
# updatedb - update locate database for local mounted filesystems
#
# $FreeBSD: src/usr.bin/locate/locate/updatedb.sh,v 1.20 2005/11/12 12:45:08 grog Exp $
#
# Modified h_wiedey Sept/Oct 2020 for CodeProject Articles

: ${LOCATE_CONFIG="/etc/locate.rc"}
if [ -f "$LOCATE_CONFIG" -a -r "$LOCATE_CONFIG" ]; then
       . $LOCATE_CONFIG
fi
: ${FCODES:=/var/db/locate.database}    # the database

if [ "$(id -u)" = "0" ]; then
	rc=0
	export TMP_FCODES=`sudo -u nobody mktemp -t updatedb`
	chown nobody $TMP_FCODES
	tmpdb=`su -fm nobody -c "$0"` || rc=1
	if [ $rc = 0 ]; then
		install -m 0444 -o nobody -g wheel $TMP_FCODES $FCODES
	fi
	rm $TMP_FCODES
	exit $rc
fi

# The directory containing locate subprograms
: ${LIBEXECDIR:=/usr/libexec}; export LIBEXECDIR
: ${TMPDIR:=/tmp}; export TMPDIR
if ! TMPDIR=`mktemp -d $TMPDIR/locateXXXXXXXXXX`; then
	exit 1
fi

PATH=$LIBEXECDIR:/bin:/usr/bin:$PATH; export PATH

# 6497475
set -o noglob

: ${mklocatedb:=locate.mklocatedb}      # make locate database program
: ${TMP_FCODES=$FCODES}                 # the database
: ${SEARCHPATHS:="/"}                   # directories to be put in the database
: ${PRUNEPATHS:="/private/tmp /private/var/folders /private/var/tmp */Backups.backupdb"} # unwanted directories
: ${FILESYSTEMS:="hfs ufs apfs"}        # allowed filesystems
: ${find:=find}

case X"$SEARCHPATHS" in 
	X) echo "$0: empty variable SEARCHPATHS"; exit 1;; esac
case X"$FILESYSTEMS" in 
	X) echo "$0: empty variable FILESYSTEMS"; exit 1;; esac

# Make a list a paths to exclude in the locate run
excludes="! (" or=""
for fstype in $FILESYSTEMS
do
       excludes="$excludes $or -fstype $fstype"
       or="-or"
done
excludes="$excludes ) -prune"

case X"$PRUNEPATHS" in
	X) ;;
	*) for path in $PRUNEPATHS
           do 
		excludes="$excludes -or -path $path -prune"
	   done;;
esac

tmp=$TMPDIR/_updatedb$$
trap 'rm -f $tmp; rmdir $TMPDIR; exit' 0 1 2 3 5 10 15
# search locally
# echo "$find $SEARCHPATHS $excludes -or -exec md5 -r {} \;" && exit
if $find -s $SEARCHPATHS $excludes -or -exec md5 -r {} \; 2> /dev/null |
        $mklocatedb -presort > $tmp
then
	case X"`$find $tmp -size -257c -print`" in
		X) cat $tmp > $TMP_FCODES;;
		*) echo "updatedb: locate database $tmp is empty"
		   exit 1
	esac
fi

</wosch@freebsd.org>

您可以使用 diff 来检查修改。相关部分是第 92、93 行,其中使用 exec md5 代替 print

    $ diff /usr/libexec/locate.updatedb locate.updatedb.md5
29a30,37
> #
> # Modified h_wiedey Sept/Oct 2020 for CodeProject Articles
> 
> : ${LOCATE_CONFIG="/etc/locate.rc"}
> if [ -f "$LOCATE_CONFIG" -a -r "$LOCATE_CONFIG" ]; then
>        . $LOCATE_CONFIG
> fi
> : ${FCODES:=/var/db/locate.database}    # the database
33,34c41,42
< 	export FCODES=`sudo -u nobody mktemp -t updatedb`
< 	chown nobody $FCODES
---
> 	export TMP_FCODES=`sudo -u nobody mktemp -t updatedb`
> 	chown nobody $TMP_FCODES
37c45
< 		install -m 0444 -o nobody -g wheel $FCODES /var/db/locate.database
---
> 		install -m 0444 -o nobody -g wheel $TMP_FCODES $FCODES
39c47
< 	rm $FCODES
---
> 	rm $TMP_FCODES
42,45d49
< : ${LOCATE_CONFIG="/etc/locate.rc"}
< if [ -f "$LOCATE_CONFIG" -a -r "$LOCATE_CONFIG" ]; then
<        . $LOCATE_CONFIG
< fi
60c64
< : ${FCODES:=/var/db/locate.database}    # the database
---
> : ${TMP_FCODES=$FCODES}                 # the database
90d93
< 		
92,93c95,96
< # echo $find $SEARCHPATHS $excludes -or -print && exit
< if $find -s $SEARCHPATHS $excludes -or -print 2>/dev/null |
---
> # echo "$find $SEARCHPATHS $excludes -or -exec md5 -r {} \;" && exit
> if $find -s $SEARCHPATHS $excludes -or -exec md5 -r {} \; 2> /dev/null |
97c100
< 		X) cat $tmp > $FCODES;;
---
> 		X) cat $tmp > $TMP_FCODES;;

除了 locate.updatedb.md5 脚本,我还使用了以下两个配置文件(locate.Kingston.rc 是用于挂载在 /Volumes/Kingston 上的 Compact Flash 驱动器的配置文件,locate.Documents.rc 是用于我 Mac 上的文档存储库 $HOME/Documents 的配置文件)

$ cat locate.Kingston.rc
#
# Configuration for user home directory search
#
# temp directory
TMPDIR="/tmp"

# the actual database
FCODES="locate.Kingston.database"

# directories to be put in the database
# Make sure that there is no space in the directory names
SEARCHPATHS="/Volumes/Kingston"

# directories unwanted in output
PRUNEPATHS="/Volumes/Kingston/.Spotlight-V100 
            /Volumes/Kingston/.Trashes /Volumes/Kingston/Ignore"

# filesystems allowed. Beware: a non-listed filesystem will be pruned
# and if the SEARCHPATHS starts in such a filesystem locate will build
# an empty database.
#
# be careful if you add 'nfs'
FILESYSTEMS="msdos"
$ cat locate.Documents.rc
#
# Configuration for user home directory search
#
# temp directory
TMPDIR="/tmp"

# the actual database
FCODES="locate.Documents.database"

# directories to be put in the database
SEARCHPATHS="$HOME/Documents"

# directories unwanted in output
# PRUNEPATHS="/tmp /var/tmp /Users /Volumes"

# filesystems allowed. Beware: a non-listed filesystem will be pruned
# and if the SEARCHPATHS starts in such a filesystem locate will build
# an empty database.
#
# be careful if you add 'nfs'
FILESYSTEMS="hfs ufs apfs"

然后可以通过运行上述配置文件和增强的 locate.updatedb.md5 来创建 locate 的名称数据库。

$ export LOCATE_CONFIG="./locate.Documents.rc";./locate.updatedb.md5
$ export LOCATE_CONFIG="./locate.Kingston.rc";./locate.updatedb.md5

如果没有打印错误消息,则表示名称 databases 已成功创建,您可以如下浏览其内容和结构:

$ locate -d locate.Documents.database "*"|less

d4c5320cf104b5629d490976c1d8059e /Users/(...)/Documents/program.jpg
cc2f48fbb82a8840a9fcb9ab0d94d0b4 /Users/(...)/Documents/program.vthought
38279e78475f437c5ebdb508b9524c72 /Users/(...)/Documents/setArray.BAK.vthought
58a2e7ffd785b72868bf28bbcacb49c3 /Users/(...)/Documents/setArray.vthought
4d42423c69db19795f8169324ecf579e /Users/(...)/Documents/structure.jpg
c63b631236fb415838cb4b57ddb1ed92 /Users/(...)/Documents/structure.vthought

$ locate -d locate.Kingston.database "*"|less
...
a3d301ed3cc4442ff2df681a9f6dd0e1 /Volumes/Kingston/Addresses/Mac/Cards2ascii.vcf
ea4177dbe0aacdd4f0b962cc3a6ffe57 /Volumes/Kingston/Addresses/Mac/vCards.vcf
bf10d18437911231b07c172729e5516c /Volumes/Kingston/Addresses/Mac/vCards2.vcf
c00ca5c3b168936ebdb4d81172e11071 /Volumes/Kingston/Addresses/Mac/vCards2utf.vcf
a3d301ed3cc4442ff2df681a9f6dd0e1 /Volumes/Kingston/Addresses/Mac/vCards2utf16.vcf
a3d301ed3cc4442ff2df681a9f6dd0e1 /Volumes/Kingston/Addresses/Mac/vCards2utf8.vcf
162b1606424bca82fa234c726e94eeab /Volumes/Kingston/Addresses/Mac/vCards2w.vcf
2aace6876876d77691fb23d69319bab0 /Volumes/Kingston/Addresses/Mac/vCards3w.vcf
... 

如您所见,名称数据库以空格字符分隔的 md5 指纹和完整文件名。

中间结果

所以,让我们看看我们到目前为止取得了什么成就。我们现在有两个名称数据库,一个包含本地硬盘 Document 文件夹中文件的文件名及其 md5 指纹(locate.Documents.database),另一个包含 Compact Flash 卡上文件的文件名及其 md5 指纹(locate.Kingston.database)。

如以下所示,可以通过条目(md5 或文件名)的任何部分进行查询:

$ locate -d locate.Kingston.database bf10d18437911231b07c172729e5516c
bf10d18437911231b07c172729e5516c /Volumes/Kingston/Addresses/Mac/vCards2.vcf
$ locate -d locate.Kingston.database vCards2.vcf
effbec4ddbd7fd21dac46e11cda782e8 /Volumes/Kingston/Addresses/Mac/._vCards2.vcf

使用 md5 值而不是文件名而不是文件名的优点是,无论文件存储在目录结构中的什么位置或其当前名称是什么,它都可以被唯一标识。

但是,md5 值不能保证是唯一的。虽然不太可能,但两个文件仍然可能产生相同的 md5 指纹,即使它们不相同。为了确保两个文件相同,您需要对它们运行 cmp。但这不在本文的讨论范围内。

您现在可以使用这两个数据库来检查仅存在于 Compact Flash 卡上的文件,如下一节所示。

查找目标上已有的文件

您现在可以使用标准的 Unix 工具 comm 来检查和提取仅存在于 Compact Flash 名称数据库(locate.Kingston.database)中的 md5 值。

$ comm -23 <(locate -d locate.Kingston.database "*" |cut -f 1 -d " "|sort) 
<(locate -d locate.Documents.database "*" |cut -f 1 -d " "|sort) > KingstonOnly.md5.srt 

使用标准的 Unix 工具 join,您可以将 md5 值扩展到完整文件名。

join <(cat KingstonOnly.md5.srt) 
     <(locate -d locate.Kingston.database "*"|sort)|grep vCards2.vcf
bf10d18437911231b07c172729e5516c /Volumes/Kingston/Addresses/Mac/vCards2.vcf 

在上面的示例中,对特定文件进行了 grep。如果您想查看完整输出,可以将输出管道传输到 less 而不是 grep,或者将输出重定向到某个文件。

但是,当文件名中包含两个或多个连续空格时,您需要小心,因为 Unix join 默认使用空格作为分隔符,并且会折叠连续的空格,因此最好使用斜杠(/)作为分隔符,它不能是文件名的一部分(至少在 Unix 下)。这可以通过在命令中包含 awk 来将斜杠添加到 KingstonOnly.md5.srt 文件的输出中来实现。然后输出如下所示:

$ join -t / <(cat KingstonOnly.md5.srt|awk '{print $1 " / "}') 
            <(locate -d locate.Kingston.database "*"|sort)|grep vCards2.vcf
bf10d18437911231b07c172729e5516c / /Volumes/Kingston/Addresses/Mac/vCards2.vcf 

从这里开始,您将决定如何处理已确定仅存在于 Compact Flash 卡上的文件。最简单的方法是直接将它们 tar 到本地硬盘驱动器上的存档中,并留待以后处理。

join -t / <(cat KingstonOnly.md5.srt|awk '{print $1 " /"}') 
<(locate -d locate.Kingston.database "*"|sort)|cut -c 35-|tar -cf KingstonOnly.tar -T - 

上面示例中的额外 cut 是为了在将文件名管道传输到 tar 之前删除 MD5 指纹。

Compact Flash 卡上的文件现在是冗余的,该卡可以格式化并用于其他目的。

查找驱动器内的重复文件

对于那些熟悉关系数据库管理系统的人来说,当使用 join 命令将 md5 值扩展到完整文件名时,您可能已经开始担心了,因为在 Compact Flash 卡上存在重复文件的情况下,join 将不再产生一对一的匹配。

从这里开始,使用带有 md5 值的名称数据库还有另一个优点,那就是您可以使用 uniq 命令轻松地在数据库中识别重复文件。

$ join <(locate -d locate.Kingston.database "*"|cut -f 1 -d " "|sort|uniq -d) 
       <(locate -d locate.Kingston.database "*"|sort)|less
$ join <(locate -d locate.Documents.database "*"|cut -f 1 -d " "|sort|uniq -d) 
       <(locate -d locate.Documents.database "*"|sort)|less 

(在上面的示例中,忽略了文件名可能包含两个或多个连续空格的情况。在这种情况下,您需要在 join 语句中使用斜杠作为分隔符,如前一节示例所示。)

关注点

这里使用了来自:https://unix.stackexchange.com/questions/31653/two-pipes-to-one-command/31654<(..) <(..) 表示法的两个管道。

历史

  • 2020 年 10 月 - 2022 年 11 月:初始版本
© . All rights reserved.