Linux: Mover 1 millón de archivos en carpetas creadas basado en el prefijo
Pregunta
Tengo un directorio llamado "imágenes" y relleno de aproximadamente un millón de imágenes. Sí.
Quiero escribir un comando shell para cambiar el nombre de todas esas imágenes en el formato siguiente:
ORIGINAL: filename.jpg
nuevo: /f/i/l/filename.jpg
¿Alguna sugerencia?
Gracias,
Dan
Solución
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;
La parte ${i:0:1}/${i:1:1}/${i:2:1}
probablemente podría ser una variable, o más corto o diferentes, pero el comando anterior hace el trabajo. Es probable que se enfrentan a problemas de rendimiento, pero si realmente desea utilizarlo, angosto el *.*
a un menor número de opciones (a*.*
, b*.*
o lo que encaja)
Edit: añadido un $
antes i
para mv
, como se ha señalado por Dan
Otros consejos
Puede generar el nuevo nombre de archivo usando, por ejemplo, sed:
$ echo "test.jpg" | sed -e 's/^\(\(.\)\(.\)\(.\).*\)$/\2\/\3\/\4\/\1/'
t/e/s/test.jpg
Por lo tanto, se puede hacer algo como esto (suponiendo que todos los directorios ya se crean):
for f in *; do
mv -i "$f" "$(echo "$f" | sed -e 's/^\(\(.\)\(.\)\(.\).*\)$/\2\/\3\/\4\/\1/')"
done
o, si no se puede utilizar la sintaxis fiesta $(
:
for f in *; do
mv -i "$f" "`echo "$f" | sed -e 's/^\(\(.\)\(.\)\(.\).*\)$/\2\/\3\/\4\/\1/'`"
done
Sin embargo, teniendo en cuenta el número de archivos, es posible que sólo desee utilizar Perl ya que es una gran cantidad de procesos de sed y mv para desovar:
#!/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);
}
Eso Perl es sin duda limita sólo a personas del mismo sistema de archivos, aunque se puede utilizar File::Copy::move
a conseguir alrededor de eso.
Puede hacerlo como un 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
Ni que decir tiene, es posible que tenga que preocuparse de los espacios y los archivos con nombres cortos.
Sugiero un script en Python corto. La mayoría de las herramientas de la shell se resisten a que gran parte de entrada (aunque xargs pueden hacer el truco). Se actualizará con el ejemplo en un 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)
Cualquiera de las soluciones propuestas que utilizan una sintaxis de comodín en la cáscara fallará probablemente debido al gran número de archivos que tienes. De las soluciones propuestas actuales, el Perl es probablemente el mejor.
Sin embargo, se puede adaptar fácilmente cualquiera de los métodos de secuencias de comandos shell para hacer frente a cualquier número de archivos de este modo:
ls -1 | \
while read filename
do
# insert the loop body of your preference here, operating on "filename"
done
Me gustaría volver a utilizar Perl, pero si usted está limitado a sólo tener herramientas simples UNIX alrededor, entonces la combinación de una de las soluciones anteriores de concha con un bucle, como he demostrado debe llegar hasta allí. Va a ser lento, sin embargo.