我正在 HFS+ 文件系统上传输大量文件。
这些文件当前位于 ext2 分区上。
由于目标分区 (HFS+) 不区分大小写,我遇到了冲突。
我想识别具有重复文件名的小写文件,如果它们实际上是重复的,则将其删除。
我还发现,如果我将所有内容都转换为小写,则会出现重复的文件夹名称。基本上这些硬盘驱动器包含多年未排序的数据,我也碰巧遇到了文件夹名称的问题。
这看起来合理吗:
find . -type f | while read f; do echo $f:l; done | sort | uniq -d
$f:l
是 ZSH,用于转换为小写。
现在我只想保留每个具有重复项的文件的一个实例。如何有效地做到这一点?
我不想找到重复的文件内容,除非它们具有相同的小写文件名。我稍后会处理重复项。
答案1
管道中的第二步稍有损坏(它破坏了反斜杠以及前导和尾随空格),并且是一种复杂的方法。用于tr
转换为小写。您不应将搜索限制为文件:目录也可能发生冲突。
find . | tr '[:upper:]' '[:lower:]' | LC_ALL=C sort | LC_ALL=C uniq -d
请注意,这仅在文件名不包含换行符时才有效。在 Linux 下,切换到空字节作为分隔符以应对换行符。
find . -print0 | tr '[:upper:]' '[:lower:]' | LC_ALL=C sort -z | LC_ALL=C uniq -dz
这会打印文件名的小写版本,这实际上不利于对文件执行某些操作。
如果您使用 zsh,请忘记find
:zsh 内置了您需要的一切。
setopt extended_glob
for x in **/*; do
conflicts=($x:h/(#i)$x:t)
if (($#conflicts > 1)); then
## Are all the files identical regular files?
h=()
for c in $conflicts; do
if [[ -f $c ]]; then
h+=(${$(md5sum <$c)%% *})
else
h=(not regular)
break
fi
done
if (( ${#${(@u)h}} == 1 )); then
# Identical regular files, keep only one
rm -- ${conflicts[1,-2]}
else
echo >&2 "Conflicting files:"
printf >&2 ' %s\n' $conflicts
fi
fi
done
答案2
我正在使用 awk 开发解决方案,仅针对重复的文件名,它不会比较内容。
这是 awk 文件dups.awk
#!/usr/bin/awk -f
{
lc=tolower($0);
count[lc] = count[lc]+1;
tab[lc] = tab[lc] "*" $0;}
END {for (t in tab)
if (count[t]>1) {
split(tab[t],sp,"*");
r=1;sep="# ";
for (fn in sp)
if (length(sp[fn]))
{
print sep "rm '" sp[fn] "'";
if (r==1) {r=0; sep=" ";}
}
print ""; }
}
我这样称呼它:
#!/bin/zsh
find $1 -type f | dups.awk
有一个缺陷:它不适用于带有星号的文件名。
在这里行动:
ks% md5sum test/*
e342e6ab6ae71954a772409f23390fa4 test/file1
e342e6ab6ae71954a772409f23390fa4 test/File1
e342e6ab6ae71954a772409f23390fa4 test/file2
ks% ./dupsAwk.sh test
# rm "test/File1"
rm "test/file1"
答案3
这是一个使用 Perl 的解决方案File::Find
,而不是尝试解决 shell 的复杂性:
#!/usr/bin/env perl
use strict;
use warnings;
use File::Find;
use Digest::MD5 qw(md5); # To find duplicates
my %lower_case_files_found;
find(
sub{
-f or return; # Skip non-files
push @{$lower_case_files_found{+lc}},$File::Find::name;
},
'.'
);
for my $lower_case_name (sort keys %lower_case_files_found){
my $number_of_files = scalar @{$lower_case_files_found{$lower_case_name}};
if($number_of_files > 1){
my %digests_seen;
for my $file (@{$lower_case_files_found{$lower_case_name}}){
open my $fh,'<',$file or die "Failed to open $file: $!\n";
my $file_content = do {local $/;<$fh>};
my $digest = md5($file_content);
push @{$digests_seen{$digest}},$file;
}
for my $digest (sort keys %digests_seen){
my $num_of_files = scalar @{$digests_seen{$digest}};
if ($num_of_files > 1){
print "Duplicates: \n";
print "[$_]\n" for @{$digests_seen{$digest}}
}
}
}
}
这使用 MD5 和来确定重复文件并打印它找到的重复文件的列表。每个文件名都包含在其中,[]
以帮助您直观地确定包含换行符的文件名。我故意没有添加代码来删除任何文件,因为这段代码完全未经测试。我让您自行决定如何处理结果列表。
如果您的文件很大,则内存和 CPU 使用率会很高:上面的脚本将每个文件加载到内存中,并对其整个内容执行 MD5 求和。
答案4
find . -type f |sort |tee f1 |uniq -i |comm -3 - f1
将为您提供要删除或忽略的文件列表,您可以将其通过管道传输到忽略列表对于 rsync
24小时后:
在回答您的评论“这是不切实际的,我需要另一个发现”时,只需将结果通过管道传输到可以进行重命名损坏的东西中。例如,整个解决方案在一个命令行上,但可读性较差。
find . -type f |sort |tee f1 |uniq -i |comm -3 - f1|(n=0;while read a ;do n=$((${n}+1));echo mv ${a} `echo ${a}|tr \[:upper:\] \[:lower:\]`_renamed_${n};done)