Linux: Déplacer 1 million de fichiers dans des dossiers créés sur la base préfixe-
Question
J'ai un répertoire appelé « images » rempli d'environ un million d'images. Yep.
Je veux écrire une commande shell pour renommer toutes ces images dans le format suivant:
d'origine: filename.jpg
nouveau: /f/i/l/filename.jpg
Toutes les suggestions?
Merci,
Dan
La solution
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 partie ${i:0:1}/${i:1:1}/${i:2:1}
pourrait probablement être une variable, ou plus ou moins différents, mais la commande passe au-dessus du travail. Vous aurez probablement face à des problèmes de performance, mais si vous voulez vraiment l'utiliser, ciblez *.*
à moins d'options (a*.*
, b*.*
ou ce qui vous convient)
edit: ajouté un $
avant i
pour mv
, comme l'a noté Dan
Autres conseils
Vous pouvez générer le nouveau nom de fichier en utilisant, par exemple, sed:
$ echo "test.jpg" | sed -e 's/^\(\(.\)\(.\)\(.\).*\)$/\2\/\3\/\4\/\1/'
t/e/s/test.jpg
Alors, vous pouvez faire quelque chose comme ceci (en supposant que tous les répertoires sont déjà créés):
for f in *; do
mv -i "$f" "$(echo "$f" | sed -e 's/^\(\(.\)\(.\)\(.\).*\)$/\2\/\3\/\4\/\1/')"
done
ou, si vous ne pouvez pas utiliser la syntaxe bash $(
:
for f in *; do
mv -i "$f" "`echo "$f" | sed -e 's/^\(\(.\)\(.\)\(.\).*\)$/\2\/\3\/\4\/\1/'`"
done
Toutefois, compte tenu du nombre de fichiers, vous pouvez juste utiliser Perl comme c'est beaucoup de processus sed et mv pour se reproduire:
#!/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);
}
Ce perl est sûrement limitée à même système de fichiers, mais vous pouvez utiliser File::Copy::move
pour contourner cette difficulté.
Vous pouvez le faire comme 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
Inutile de dire que vous pourriez avoir à vous soucier des espaces et des fichiers avec des noms courts.
Je suggère un court script python. La plupart des outils shell rechignent à cette entrée beaucoup (bien que xargs peut faire l'affaire). Mettra à jour l'exemple dans une seconde.
#!/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)
Toutes des solutions proposées qui utilisent une syntaxe générique dans le shell va probablement échouer en raison du grand nombre de fichiers que vous avez. Parmi les solutions actuelles proposées, le Perl un est probablement le meilleur.
Cependant, vous pouvez facilement adapter l'une des méthodes de script shell pour traiter un certain nombre de fichiers ainsi:
ls -1 | \
while read filename
do
# insert the loop body of your preference here, operating on "filename"
done
Je encore utiliser Perl, mais si vous êtes limité à seulement avoir des outils simples unix autour, puis en combinant l'une des solutions de coque au-dessus avec une boucle comme je vous ai montré devrait y arriver. Ce sera lent, cependant.