Como preparar arquivos para rsync em um sistema de arquivos insensitivo a maiúsculas e minúsculas?

3

Estou transferindo um grande número de arquivos em um sistema de arquivos HFS +.

Os arquivos estão atualmente em partições ext2.

Eu tenho conflitos devido à insensibilidade a maiúsculas e minúsculas da partição de destino (HFS +).

Eu quero identificar os arquivos que têm nomes de arquivos duplicados, uma vez que estão em letras minúsculas, e excluí-los se eles forem realmente duplicados.

Também descobri que terei nomes de pastas duplicados se eu converter o everyhing em minúsculas. Basicamente, esses discos rígidos contêm anos de dados não classificados e, por acaso, tenho esse problema também com nomes de pastas.

Isso parece razoável:

find . -type f | while read f; do echo $f:l; done | sort | uniq -d 

$f:l é o ZSH para converter para minúsculas.

Agora quero manter apenas uma instância de cada arquivo com duplicatas. Como fazer isso de forma eficiente?

Eu não quero encontrar arquivos com conteúdo duplicado, a menos que eles tenham o mesmo nome de arquivo em minúsculas. Eu vou lidar com duplicatas depois.

    
por alecail 06.11.2013 / 11:12

4 respostas

2

O segundo passo no seu pipeline é um pouco quebrado (ele manuseia barras invertidas e espaços em branco iniciais e finais) e é uma maneira complicada de se fazer isso. Use tr para converter em minúsculas. Você não deve limitar a busca aos arquivos: os diretórios podem colidir também.

find . | tr '[:upper:]' '[:lower:]' | LC_ALL=C sort | LC_ALL=C uniq -d

Observe que isso só funciona se os nomes de arquivos não contiverem novas linhas. No Linux, mude para bytes nulos como o separador para lidar com novas linhas.

find . -print0 | tr '[:upper:]' '[:lower:]' | LC_ALL=C sort -z | LC_ALL=C uniq -dz

Isto imprime as versões minúsculas de nomes de arquivos, o que não é propício para fazer algo sobre os arquivos.

Se você estiver usando o zsh, esqueça o find : zsh tem tudo o que você precisa incluir.

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
    
por 06.11.2013 / 23:54
0

Estou trabalhando na solução usando o awk, apenas para nomes de arquivos duplicados, o que não compara o conteúdo.

Aqui o arquivo 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 ""; }
}

Estou ligando assim:

#!/bin/zsh
find $1 -type f | dups.awk

Existe uma falha: não funciona com nomes de arquivo com uma estrela.

Aqui em ação:

ks% md5sum test/*                               
e342e6ab6ae71954a772409f23390fa4  test/file1
e342e6ab6ae71954a772409f23390fa4  test/File1
e342e6ab6ae71954a772409f23390fa4  test/file2

ks% ./dupsAwk.sh test               
# rm "test/File1"
  rm "test/file1"
    
por 06.11.2013 / 16:51
0

Aqui está uma solução usando o File::Find do Perl em vez de tentar contornar as complexidades do 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}}
               }
           }
    }
}

Isso usa uma soma MD5 para determinar arquivos duplicados e imprime listas das duplicações encontradas. Cada nome de arquivo é colocado em [] para ajudá-lo a determinar visualmente os nomes de arquivos que contêm uma nova linha. Eu deliberadamente não adicionei código para excluir nenhum arquivo, pois esse código não foi testado completamente . Deixo para você fazer o que quiser com a lista resultante.

Espere alta memória e uso da CPU se os arquivos forem grandes: o script acima carrega cada arquivo na memória e executa uma soma MD5 em todo o seu conteúdo.

    
por 06.11.2013 / 13:06
0
find . -type f |sort |tee f1 |uniq -i |comm -3 - f1

Irá lhe dar uma lista de arquivos para deletar ou ignorar, os quais você pode enviar para ignore-list para o rsync

24 horas depois:

Em resposta ao seu comentário "É impraticável, eu preciso de outra descoberta", apenas canalize os resultados em algo que fará o seu renomear mangle. por exemplo, solução inteira em uma linha de comando, mas menos legível.

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)
    
por 06.11.2013 / 13:28