Linux: mova 1 milhão de arquivos para pastas criadas com base no prefixo
Pergunta
Eu tenho um diretório chamado "imagens" cheio de cerca de um milhão de imagens. Sim.
Quero escrever um comando Shell para renomear todas essas imagens no seguinte formato:
original: filename.jpg
novo: /f/i/l/filename.jpg
Alguma sugestão?
Obrigado,
Dan
Solução
for i in *.*; do mkdir -p ${i:0:1}/${i:1:1}/${i:2:1}/; mv $i ${i:0:1}/${i:1:1}/${i:2:1}/; done;
o ${i:0:1}/${i:1:1}/${i:2:1}
A parte provavelmente poderia ser uma variável, ou mais curta ou diferente, mas o comando acima faz o trabalho. Você provavelmente enfrentará problemas de desempenho, mas se realmente quiser usá -lo, restrinja o *.*
para menos opções (a*.*
, b*.*
ou o que se encaixa em você)
editar: adicionado um $
antes da i
por mv
, como observado por Dan
Outras dicas
Você pode gerar o novo nome do arquivo usando, por exemplo, sed:
$ echo "test.jpg" | sed -e 's/^\(\(.\)\(.\)\(.\).*\)$/\2\/\3\/\4\/\1/'
t/e/s/test.jpg
Então, você pode fazer algo assim (assumindo que todos os diretórios já estejam criados):
for f in *; do
mv -i "$f" "$(echo "$f" | sed -e 's/^\(\(.\)\(.\)\(.\).*\)$/\2\/\3\/\4\/\1/')"
done
Ou, se você não pode usar a festa $(
sintaxe:
for f in *; do
mv -i "$f" "`echo "$f" | sed -e 's/^\(\(.\)\(.\)\(.\).*\)$/\2\/\3\/\4\/\1/'`"
done
No entanto, considerando o número de arquivos, você pode apenas usar o Perl, pois são muitos processos Sed e MV para gerar:
#!/usr/bin/perl -w
use strict;
# warning: untested
opendir DIR, "." or die "opendir: $!";
my @files = readdir(DIR); # can't change dir while reading: read in advance
closedir DIR;
foreach my $f (@files) {
(my $new_name = $f) =~ s!^((.)(.)(.).*)$!$2/$3/$4/$1/;
-e $new_name and die "$new_name already exists";
rename($f, $new_name);
}
Que o Perl é certamente limitado apenas ao mesmo sistema de arquivos, embora você possa usar File::Copy::move
para contornar isso.
Você pode fazer isso como um script bash:
#!/bin/bash
base=base
mkdir -p $base/shorts
for n in *
do
if [ ${#n} -lt 3 ]
then
mv $n $base/shorts
else
dir=$base/${n:0:1}/${n:1:1}/${n:2:1}
mkdir -p $dir
mv $n $dir
fi
done
Escusado será dizer que você pode precisar se preocupar com espaços e os arquivos com nomes curtos.
Eu sugiro um script python curto. A maioria das ferramentas de concha se recusará a tantas entradas (embora os Xargs possam fazer o truque). Atualizará com o exemplo em um segundo.
#!/usr/bin/python
import os, shutil
src_dir = '/src/dir'
dest_dir = '/dest/dir'
for fn in os.listdir(src_dir):
os.makedirs(dest_dir+'/'+fn[0]+'/'+fn[1]+'/'+fn[2]+'/')
shutil.copyfile(src_dir+'/'+fn, dest_dir+'/'+fn[0]+'/'+fn[1]+'/'+fn[2]+'/'+fn)
Qualquer uma das soluções propostas que usam uma sintaxe curinga no shell provavelmente falharão devido ao grande número de arquivos que você possui. Das soluções propostas atuais, o Perl é provavelmente o melhor.
No entanto, você pode adaptar facilmente qualquer um dos métodos de script da Shell para lidar com qualquer número de arquivos assim:
ls -1 | \
while read filename
do
# insert the loop body of your preference here, operating on "filename"
done
Eu ainda usaria o Perl, mas se você estiver limitado a ter apenas ferramentas simples do Unix, combinando uma das soluções de shell acima com um loop como eu mostrei que deveria levá -lo até lá. Vai ficar lento, no entanto.