Shell 查找并删除重复文件,无论是恢复备份,还是在离线模式(disconnected mode)下使用笔记本电脑,或是从手机中下载图片,到最后都会碰到具有相同内容的重复文件。你接下来要做的大概会是删除这些重复文件,只保留单个副本。我们可以使用一些shell实用工具检查文件内容,识别重复文件。本章我们将讨论如何查找重复文件并根据查找结果执行相关的操作。
预备知识
我们可以通过比较文件内容来识别重复文件。校验和是一种理想的解决方法。内容相同的文件自然会生成相同的校验和。
实战演练
下面是查找并删除重复文件的步骤。
(1) 创建一些测试文件:
$ echo "hello" > test ; cp test test_copy1 ; cp test test_copy2;
$ echo "next" > other;
# test_copy1和test_copy2都是test文件的副本
(2) 我们在脚本中使用awk
(Linux/Unix系统中都存在的一个解释器)来删除重复文件:
# !/bin/bash
# 文件名: remove_duplicates.sh
# 用途: 查找并删除重复文件,每一个文件只保留一份
ls -lS --time-style=long-iso | 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 '{ print2 }' | \
sort -u > unique_files
echo Removing..
comm duplicate_files unique_files -3 | tee /dev/stderr | \
xargs rm
echo Removed duplicates files successfully.
(3) 执行该脚本:
$ ./remove_duplicates.sh
工作原理
前文中的shell脚本会找出某个目录中同一文件的所有副本,然后保留单个副本的同时删除其他副本。让我们研究一下这个脚本的工作原理。
ls -lS
对当前目录下的所有文件按照文件大小进行排序并列出文件的详细信息。--time-style=long-iso
告诉ls
依照ISO
格式打印日期。awk
读取ls -lS
的输出,对行列进行比较,找出重复文件。这段代码的执行逻辑如下。
- 我们将文件依据大小排序并列出,这样大小相同的文件就会排列在一起。识别大小相同的文件是我们查找重复文件的第一步。接下来,计算这些文件的校验和。如果校验和相同,那么这些文件就是重复文件,将被删除。
-
在进行主要处理之前,首先要执行
awk
的BEGIN{}
语句块。该语句块读取文件所有的行并初始化变量。处理ls
剩余的输出都是在{}
语句块中完成的。读取并处理完所有的行之后,执行END{}
语句块。ls -lS
的输出如下:
total 16
-rw-r--r-- 1 slynux slynux 5 2010-06-29 11:50 other
-rw-r--r-- 1 slynux slynux 6 2010-06-29 11:50 test
-rw-r--r-- 1 slynux slynux 6 2010-06-29 11:50 test_copy1
-rw-r--r-- 1 slynux slynux 6 2010-06-29 11:50 test_copy2
- 第1行输出告诉了我们文件的总数量,这个信息在本例中没什么用处。我们用
getline
读取该行,然后丢弃掉。我们需要比对每一行及其下一行的文件大小。在BEGIN
语句块中,使用getline
读取文件列表的第一行并存储文件名和大小分别对应第8列和第5列)。当awk
进入{}
语句块后,依次读取余下的行(一次一行)。在该语句块中,将从当前行中得到的文件大小与之前存储在变量size
中的值进行比较。如果相等,那就意味着两个文件至少在大小上是相同的,必须再用md5sum
做进一步的检查。
我们在给出的解决方法中使用了一些技巧。在awk
内部可以读取外部命令的输出:
"cmd"| getline
读入一行后,该行就被保存在$0
中,行中的每一列分别被保存在$1
、$2
…$n
中。我们将文件的md5校验和分别保存在变量csum1
和csum2
中。变量name1
和name2
保存文件列表中相邻两个文件的文件名。如果两个文件的校验和相同,那它们肯定是重复文件,其文件名会被打印出来。
我们需要从每组重复文件中找出一个文件,这样就可以删除其他副本了。计算重复文件的 md5sum
值并从每一组重复文件中打印出其中一个。这是通过用-w 32
比较每一行的md5sum
输出中的前32个字符(md5sum
的输出通常由32个字符的散列值和文件名组成)来找出那些不相同的行(注:也就是不重复的文件)。这样,每组重复文件中的一个采样就被写入unique_files
文件。
现在需要将duplicate_files
中列出的、未包含在unique_files
之内的文件全部删除。comm
命令可以将其打印出来。对此,我们可以使用差集操作来实现(参考Shell 文本文件的交集与差集)。
comm
只能处理排序过的文件。因此,使用sort -u
来过滤duplicate_files
和unique_files
文件。
tee
可以将文件名传给rm
命令并输出。tee
可以将输出发送至stdout
和另一个文件中。我们也可以将文本重定向到stderr
来实现终端打印功能。/dev/stderr是对应于stderr
(标准错误)的设备。通过重定向到stderr
设备文件,发送到stdin
的文本将会以标准错误的形式出现在终端中。