3.4 查找并删除重复文件
重复文件是同一个文件的多个副本。有时候,我们需要删除重复的文件,只保留单个副本。通过查看文件内容来识别重复文件是件挺有意思的任务。可以结合多种shell工具来完成这项任务。在这则攻略中,我们讨论的是查找重复文件并基于查找结果执行相关的操作。
3.4.1 预备知识
重复文件指的是那些虽然名字不同但内容却一模一样的文件。我们可以通过比较文件内容来识别它们。校验和是依据文件内容来计算的,内容相同的文件自然会生成相同的校验和,因此,我们可以通过比较校验和来删除重复文件。
3.4.2 实战演练
按照下面的方法创建一些测试文件:
- $ echo "hello" > test ; cp test test_copy1 ; cp test test_copy2;
- $ echo "next" > other;
- # test_copy1和test_copy2都是test文件的副本
删除重复文件的脚本代码如下:
#!/bin/bash
#文件名: remove_duplicates.sh
#用途: 查找并删除重复文件,每一个文件只保留一个样本
ls -lS | awk 'BEGIN {
getline;getline;
name1=$8; size=$5
}
{ name2=$8;
if (size==$5)
{
"md5sum "name1 | getline; csum1=$1;
"md5sum "name2 | getline; csum2=$1;
if ( csum1==csum2 )
{print name1; print name2 }
};
size=$5; name1=name2;
}' | sort -u > duplicate_files
cat duplicate_files | xargs -I {} md5sum {} | sort | uniq -w 32 | awk
'{ print "^"$2"$" }' | sort -u > duplicate_sample
echo Removing..
comm duplicate_files duplicate_sample -2 -3 | tee /dev/stderr | xargs
rm
echo Removed duplicates files successfully.
执行方式:
- $ ./remove_duplicates.sh
3.4.3 工作原理
前文中的shell脚本会找出某个目录中同一文件的所有副本,然后保留单个副本的同时删除其他副本。让我们研究一下这个脚本的工作原理。ls -lS
对当前目录下的所有文件按照文件大小进行排序,并列出文件的详细信息。awk
读取ls -lS
的输出,对行列进行比较,找出重复文件。
下面是代码的执行逻辑。
我们将文件依据大小排序并列出,这样大小接近的文件就会排列在一起。识别大小相同的文件是我们查找重复文件的第一步。接下来,计算这些文件的校验和。如果校验和相同,那么这些文件就是重复文件,将被删除。
在从文件中读取文本行之前,首先要执行
awk
的BEGIN{}
语句块。读取文本行的工作在{}
语句块中进行,读取并处理完所有的行之后,执行END{}
语句块。ls -lS
的输出如下:
- total 16
- 4 -rw-r--r-- 1 slynux slynux 5 2010-06-29 11:50 other
- 4 -rw-r--r-- 1 slynux slynux 6 2010-06-29 11:50 test
- 4 -rw-r--r-- 1 slynux slynux 6 2010-06-29 11:50 test_copy1
- 4 -rw-r--r-- 1 slynux slynux 6 2010-06-29 11:50 test_copy2
- 第1行输出告诉我们文件数量,这个信息在本例中没什么用处。我们用
getline
读取第1行,然后丢弃。由于需要对每一行及其下一行来进行文件大小比对,因此用getline
读取长文件列表的第一行,并存储文件名和大小(它们分别是第8列和第5列)。这样我们就提前读取到了一行。接下来,awk
进入{}
语句块(在这个语句块中读取余下的文本行),对于读取到的每一行文本,都会执行该语句块。它将从当前行中读取到的文件大小与之前存储在变量size
中的值进行比较。如果相等,那就意味着两个文件至少在大小上是相同的,随后再用md5sum执行进一步的检查。
至此,我们已试着采用了一些技巧来逐步解决问题。
在awk
中,外部命令的输出可以用下面的方法读取:
"cmd" | getline
随后,我们就可以在$0
中获取命令的输出,在$1,$2,…$n
中获取命令输出中的每一列。我们将文件的md5sum保存在变量csum1
和csum2
中。变量name1
和name2
保存文件列表中位置连续的文件名。如果两个文件的校验和相同,那它们肯定是重复文件,而它们的文件名会被打印出来。
我们需要从每组重复文件中找出一个文件,使得在保留一个副本的同时,能够删除其他重复的文件。我们计算重复文件的 md5sum,从每一组重复文件中打印出一个文件。这是通过-w 32
比较每一行的md5sum(md5sum输出中的前32个字符,md5sum的输出通常由32个字符的散列值和文件名组成),然后找出那些不相同的行。因此,每组重复文件中的一个采样就被写入duplicate_sample
。
现在需要将duplicate_files
中列出的且未被列于duplicate_sample
之内的全部文件删除。comm
命令打印出未在duplicate_sample
中列出且被duplicate_files
包含的文件。
因此,我们可以使用差集操作(请参考3.3节)。
comm
通常只接受排序过的文件。所以,在重定向到duplicate_files
和duplicate_sample
之前,首先用sort -u
作为一个过滤器。
tee
命令在这里有一个妙用:它在将文件名传递给rm
命令的同时,也起到了print
的功能。tee
将来自stdin
的行写入文件,并将其发送到stdout
。我们也可以将文本重定向到stderr
来实现终端打印功能。/dev/stderr
是对应于stderr
(标准错误)的设备。通过重定向到stderr
设备文件,来自stdin
的文本将会以标准错误的形式出现在终端中。
3.4.4 参考
4.7节讲解了
awk
命令。2.7节讲解了
md5sum
命令。