使用 md5 和 Locate 查找重复文件
本技巧展示了如何使用 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 月:初始版本