如何在不区分大小写的文件系统上为 rsync 准备文件?

如何在不区分大小写的文件系统上为 rsync 准备文件?

我正在 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)

相关内容