我想根据 MD5 值删除重复文件。我已经获得了下面的脚本,但我该如何修改它以使其以递归方式工作?
例如我有一个包含 3 个子文件夹 A B C 的文件夹
我希望检查 ./ ./A/ ./B/ ./C/ 中的所有文件的 md5 并相互比较,如果发现匹配,则随机删除任一匹配。最后不再存在重复项。我不关心哪个匹配项先被删除。
我希望我已经足够清楚地表达了我需要实现的目标,如果没有,请告诉我:)
#!/bin/bash
while true
do
echo "Enter the directory:"
read directory
if [ -d $directory ]; then
break
else
echo "Invalid directory"
fi
done
for FILE in `ls $directory`
do
if [ ! -f $FILE ]; then
break;
fi
h=`md5sum $directory/$FILE | awk '{ print $1 }'`
for f in `ls $directory`
do
if [ -f $f ] && [ $FILE != $f ]; then
s=`md5sum $directory/$f | awk '{ print $1 }'`
if [ "$s" = "$h" ]; then
echo Removing $f
rm -rf $directory/$f
fi
fi
done
done
答案1
我建议采取如下措施:
find . -type f \
| xargs md5sum \
| sort -k1,1 \
| uniq -Dw32
这将列出具有相同 MD5 哈希值的文件组中的所有重复文件。
注意,因为-w32
参数uniq
只会比较前 32 个字符……如果您更改哈希的长度,则需要更新它。
考虑下面的树,其内容如下:
./a/1: foo
./a/2: bar
./b/3: hello world
./b/d/5: bar
./c/4: foo
$ find . -type f \
> | xargs md5sum \
> | sort -k1,1 \
> | uniq -Dw32
c157a79031e1c40f85931829bc5fc552 ./a/2
c157a79031e1c40f85931829bc5fc552 ./b/d/5
d3b07384d113edec49eaa6238ad5ff00 ./a/1
d3b07384d113edec49eaa6238ad5ff00 ./c/4
您现在可以逐行处理这些行...每行前面都有匹配的哈希值,指向一个可以进行重复数据删除的文件。
如果你不太在意哪个文件被删除,那么类似这样的操作就会起作用:
find . -type f \
| xargs md5sum \
| sort -k1,1 \
| uniq -Dw32 \
| while read hash file; do
[ "${prev_hash}" == "${hash}" ] && rm -v "${file}"
prev_hash="${hash}";
done
注意MD5 不再被认为是安全的... 因此,如果您在用户控制文件的系统中使用此方法,那么他们就有可能设计冲突 - 因此您可能会意外删除合法/目标文件,而不是像您希望的那样进行重复数据删除。更喜欢更强大的哈希,例如SHA-256。
答案2
首先要提醒的是:基于校验和假设身份非常危险。不推荐。
使用校验和作为过滤器来删除确定的非重复项是可以的。
如果我要这么做,我会这样做:
根据长度创建文件列表(长度,完整路径名)
扫描该列表寻找潜在的重复长度。
任何比赛潜在的重复,如果可能的话,我会正确比较可疑文件。
使用长度的原因是,该信息可以非常快速地获得,而无需逐字节扫描文件,因为它通常在文件系统统计信息中以便快速访问。
如果您认为这比直接比较文件更快,可以使用类似的方法(计算一次校验和)添加另一个阶段来比较校验和(针对相似长度的文件)。使用类似的方法(从匹配的长度列表开始并计算这些文件的校验和)。
仅当存在多个长度相同的文件时,进行校验和计算才会对您有益,即使如此,直接逐字节比较也可能会很快发现不匹配的内容。
答案3
md5sum prime-* | awk 'n[$1]++' | cut -d " " -f 3- | xargs -I {} echo rm {}
答案4
进入您想要检查的文件夹,列出文件并检查所有文件,如果 md5 匹配并且文件名不同,则建议删除该文件。
下面的脚本正是这样做的。请记住,这是一个模板,它会吐出所有文件名和校验和以用于调试目的,它实际上并没有删除,而是回显了您可以删除的文件名。
根据您的需要进行编辑。
#!/bin/bash
function getone(){
h=$(md5sum "${a}" | awk '{print $1}')
}
function gettwo(){
s=$(md5sum "${x}" | awk '{print $1}')
}
echo "Type the directory NAME"
read directory
if [ -d ${directory} ]
then cd ${directory}
for a in *.*
do echo checking "${a}"
getone
echo $h # irrelevant echo, just for debug, you can remove it
for x in *.*
do echo scanning "${x}" # irrelevant echo, just for debug, you can remove it
gettwo
echo $s # irrelevant echo, just for debug, you can remove it
if [ "${a}" = "${x}" ]
then echo "Original file, skipping" # irrelevant echo, just for debug, you can remove it by leaving empty quotes.
elif [ "${h}" = "${s}" ]
then echo "Delete ${x}" # This should be replaced by rm once you are happy with the script
fi
done
done
else echo "The directory name does not exist"
fi
但是,这种方法并不是最好的方法,因为如果您检查文件 A,发现它与文件 B 相同,那么它会告诉您删除文件 B,而当它检查文件 B 时,它会告诉您删除文件 A……所以,它会先找到文件 A,再删除文件 B。在这个例子中,B 将被首先删除。一旦它尝试检查文件 B,发现 B 不再存在,它会中断循环吗?我不知道。我没有检查……