mv arquivos para um caminho diferente que é construído por meio de um substituto de expressão regular

2

Eu tenho um diretório cheio de arquivos armazenados em subdiretórios de acordo com o nome do arquivo, ou seja:

20d1/d325/52d1/20d1d32552d1a95249e62662fbdf924dd72c4027.jpg
ccaf/13cf/3199/ccaf13cf319930e80f5f2ad02525b93e1326c160.jpg
ec07/53bd/2355/ec0753bd2355fa8ec5cf5163e219c162cce3b03a.jpg
...

Como você pode ver, os primeiros 12 caracteres do nome do arquivo são usados para criar três níveis de subdiretórios. Infelizmente, quatro caracteres por nome de diretório foram escolhidos e, como acontece, a quantidade de arquivos cresceu além do limite de 32.000 entradas por diretório no sistema de arquivos. Então, eles precisam ser reescritos para:

20d/1d3/255/2d1/20d1d32552d1a95249e62662fbdf924dd72c4027.jpg
cca/f13/cf3/199/ccaf13cf319930e80f5f2ad02525b93e1326c160.jpg
ec0/753/bd2/355/ec0753bd2355fa8ec5cf5163e219c162cce3b03a.jpg
...

Então, três letras por diretório em vez de quatro. Há uma tonelada de arquivos, então o processo deve ser o mais rápido possível.

Eu me envolvi com find :

find /path/to/files -mindepth 4 -type f -regextype posix-extended -regex \
".*/([0-9a-f]{4}/){3}(([0-9a-f]{3})([0-9a-f]{3})([0-9a-f]{3})([0-9a-f]{3})[0-9a-f]+\.\w+)" 

Isso imprime todos os arquivos bem, mas não sei como proceder com a reescrita. Eu gostaria de usar os grupos de captura regex no processo de reescrita para reescrever o caminho para $3/$4/$5/$6/$2 (referências anteriores para find regex). Mas find parece não suportar algo como:

find ... -exec cp {} /elsewhere/$3/$4/$5/$6/$2 ;

Qual é a melhor abordagem para lidar com isso? Alguma combinação de sed e xargs (não tenho muita experiência com isso)? Devo ir para um loop em vez de uma ação find ? Estou um pouco perdido.

    
por deceze 28.07.2013 / 20:43

3 respostas

2

Para copiar os arquivos, você pode usar uma combinação de find e GNU tar para o trabalho:

$ find -type f ... -print0 \
    | tar -c -f - --null --files-from - \
    | tar -C DEST_BASE -v -x -f - \
        --show-transformed \
        --transform 's,PATTERN,REPLACE,OPTIONS

(o find gera todo o nome do arquivo de origem, o primeiro tar lê-os em um pipe e o segundo tar faz o nome do arquivo / transformação do caminho)

Por padrão, a opção --transform espera uma expressão regular básica, mas há também a opção x regexp disponível. Outra opção útil de regexp é i para correspondência insensível a maiúsculas e minúsculas.

    
por 28.07.2013 / 21:02
2

Para se movimentar, você pode usar mmv :

$ mmv -n ';????????????*.jpg' '#2#3#4/#5#6#7/#8#9#10/#11#12#13/#14.jpg'
20d1/d325/52d1/20d1d32552d1a95249e62662fbdf924dd72c4027.jpg
    -> 20d/1d3/255/2d1/a95249e62662fbdf924dd72c4027.jpg
ccaf/13cf/3199/ccaf13cf319930e80f5f2ad02525b93e1326c160.jpg
    -> cca/f13/cf3/199/30e80f5f2ad02525b93e1326c160.jpg
ec07/53bd/2355/ec0753bd2355fa8ec5cf5163e219c162cce3b03a.jpg
    -> ec0/753/bd2/355/fa8ec5cf5163e219c162cce3b03a.jpg

(-n é apenas para relatórios e testes - os arquivos ainda não foram movidos)

Infelizmente, mmv não tem uma opção 'criar-diretórios ausentes' - portanto, temos que fazer isso antes da mudança real:

$ mmv -n ';????????????*.jpg' '#2#3#4/#5#6#7/#8#9#10/#11#12#13/#14.jpg' \
    | sed 's,^.* -> \(.*/\)[^/]\+$,,' \
    | xargs mkdir -p

mmv usa curingas do shell - não regexp. O caractere ; é especial e corresponde ao caminho base do arquivo de origem. Backreferences são indicadas por #n . Como o caractere curinga do shell não é tão poderoso quanto o regexp estendido, usei curingas de 12 ? para corresponder aos primeiros 12 caracteres do nome do arquivo.

    
por 28.07.2013 / 21:19
0

Como você tem muitos arquivos, é necessário ficar atento ao limite de comprimento da linha de comando. Além disso, por motivos de desempenho, é melhor você não iniciar um novo processo para cada arquivo.

Não copie os arquivos - isso levaria uma quantidade enorme de tempo, dobraria o espaço em disco e você teria o problema de excluir os originais sem excluir as cópias. Mova os arquivos, é muito mais confiável.

Embora isso possa ser feito com utilitários de shell, é muito mais fácil escrever um script robusto e eficiente em Perl, Python ou Ruby. Você não encontrará problemas de citação ou a necessidade de dividir as linhas de comando.

Perl (com a verificação de erros omitida na remoção do diretório):

#!/usr/bin/env perl
use warnings;
for my $dir1 (<*>) {
    for my $dir2 (<$dir1/*>) {
        for my $dir3 (<$dir2/*>) {
            for my $file (<$dir3/*>) {
                $file =~ m:.*/((...)(...)(...)(...).*):;
                mkdir "$1";
                mkdir "$1/$2";
                mkdir "$1/$2/$3";
                mkdir "$1/$2/$3/$4";
                rename $file, "$1/$2/$3/$4/$file" or die "$file: $!";
            }
            rmdir $dir3;
        }
        rmdir $dir2;
    }
    rmdir $dir1;
}
    
por 30.07.2013 / 04:15