J'ai réussi avec cette commande.
rename JPG jpg *.JPG
Où rename
est une commande qui indique au shell de renommer chaque occurrence de JPG
à jpg
dans le dossier courant avec tous les noms de fichiers ayant l'extension JPG
.
Si vous voyez que Bareword "JPG" n'est pas autorisé alors que "strict subs" est utilisé à (eval 1) ligne 1 avec cette approche, essayez :
rename 's/\.JPG$/.jpg/' *.JPG
Solution
Vous pouvez résoudre la tâche en une seule ligne :
find . -name '*.*' -exec sh -c '
a=$(echo "$0" | sed -r "s/([^.]*)\$/\L\1/");
[ "$a" != "$0" ] && mv "$0" "$a" ' {} \;
Remarque :cela cassera pour les noms de fichiers qui contiennent des retours à la ligne. Mais supportez-moi pour l'instant.
Exemple d'utilisation
$ mkdir C; touch 1.TXT a.TXT B.TXT C/D.TXT
$ find .
.
./C
./C/D.TXT
./1.TXT
./a.TXT
./B.TXT
$ find . -name '*.*' -exec sh -c 'a=$(echo "$0" | sed -r "s/([^.]*)\$/\L\1/"); [ "$a" != "$0" ] && mv "$0" "$a" ' {} \;
$ find .
.
./C
./C/D.txt
./a.txt
./B.txt
./1.txt
Explication
Vous trouvez tous les fichiers dans le répertoire courant (.
) qui ont la période .
en son nom (-name '*.*'
) et exécutez la commande pour chaque fichier :
a=$(echo "$0" | sed -r "s/([^.]*)\$/\L\1/");
[ "$a" != "$0" ] && mv "{}" "$a"
Cette commande signifie :essayez de convertir l'extension de fichier en minuscules (ce qui fait sed
):
$ echo 1.txt | sed -r "s/([^.]*)\$/\L\1/"
1.txt
$ echo 2.TXT | sed -r "s/([^.]*)\$/\L\1/"
2.txt
et enregistrez le résultat dans le a
variables.
Si quelque chose a été changé [ "$a" != "$0" ]
, renommer le fichier mv "$0" "$a"
.
Le nom du fichier en cours de traitement ({}
) passé à sh -c
comme argument supplémentaire et il est vu à l'intérieur de la ligne de commande comme $0
.Cela rend le script sûr, car dans ce cas, le shell prend {} comme une donnée, et non comme une partie de code, comme lorsqu'il est spécifié directement dans la ligne de commande.(Je remercie @gniourf_gniourf pour m'avoir signalé ce problème très important ).
Comme vous pouvez le voir, si vous utilisez {}
directement dans le script, il est possible d'avoir des injections de shell dans les noms de fichiers, quelque chose comme :
; rm -rf * ;
Dans ce cas, l'injection sera considérée par le shell comme faisant partie du code et elle sera exécutée.
While-version
Version plus claire, mais un peu plus longue, du script :
find . -name '*.*' | while IFS= read -r f
do
a=$(echo "$f" | sed -r "s/([^.]*)\$/\L\1/");
[ "$a" != "$f" ] && mv "$f" "$a"
done
Cela casse toujours pour les noms de fichiers contenant des retours à la ligne. Pour résoudre ce problème, vous devez disposer d'un find
qui prend en charge -print0
(comme GNU find
) et Bash (pour que read
prend en charge le -d
commutateur de délimiteur) :
find . -name '*.*' -print0 | while IFS= read -r -d '' f
do
a=$(echo "$f" | sed -r "s/([^.]*)\$/\L\1/");
[ "$a" != "$f" ] && mv "$f" "$a"
done
Cela casse toujours pour les fichiers qui contiennent des retours à la ligne (car ils seront absorbés par le a=$(...)
sous-coque. Si vous vraiment voulez une méthode infaillible (et vous devriez !), avec une version récente de Bash (Bash≥4.0) qui supporte le ,,
expansion des paramètres voici la solution ultime :
find . -name '*.*' -print0 | while IFS= read -r -d '' f
do
base=${f%.*}
ext=${f##*.}
a=$base.${ext,,}
[ "$a" != "$f" ] && mv -- "$f" "$a"
done
Retour à la solution d'origine
Ou en un find
go (retour à la solution d'origine avec quelques correctifs qui la rendent vraiment infaillible):
find . -name '*.*' -type f -exec bash -c 'base=${0%.*} ext=${0##*.} a=$base.${ext,,}; [ "$a" != "$0" ] && mv -- "$0" "$a"' {} \;
J'ai ajouté -type f
afin que seuls les fichiers normaux soient renommés. Sans cela, vous pourriez toujours avoir des problèmes si les noms de répertoires sont renommés avant les noms de fichiers. Si vous souhaitez également renommer des répertoires (et des liens, des canaux, etc.), vous devez utiliser -depth
:
find . -depth -name '*.*' -type f -exec bash -c 'base=${0%.*} ext=${0##*.} a=$base.${ext,,}; [ "$a" != "$0" ] && mv -- "$0" "$a"' {} \;
de sorte que find
effectue une recherche en profondeur d'abord.
Vous pouvez dire qu'il n'est pas efficace de générer un bash
processus pour chaque fichier trouvé. C'est exact, et la version précédente de la boucle serait alors meilleure.
Ceci est plus court mais plus général, combiné à partir de la réponse des autres :
rename 's/\.([^.]+)$/.\L$1/' *
Simulation
Pour la simulation, utilisez -n
, soit rename -n 's/\.([^.]+)$/.\L$1/' *
. De cette façon, vous pouvez voir ce qui sera modifié avant que les modifications réelles ne soient effectuées. Exemple de sortie :
Happy.Family.GATHERING.JPG renamed as Happy.Family.GATHERING.jpg
Hero_from_The_Land_Across_the_River.JPG renamed as Hero_from_The_Land_Across_the_River.jpg
rAnD0m.jPg1 renamed as rAnD0m.jpg1
Brève explication sur la syntaxe
- La syntaxe est
rename OPTIONS 's/WHAT_TO_FIND_IN_THE_NAME/THE_REPLACEMENT/' FILENAMES
\.([^.]+)$
signifie une séquence de tout sauf un point ([^.]
) à la fin de la chaîne ($
), après le point (\.
).\L$1
signifie point (\.
) suivi de minuscules (\L
) de 1 groupe ($1
)- Le premier groupe dans ce cas est l'extension (
[^.]+
) - Vous feriez mieux d'utiliser l'apostrophe
'
au lieu des guillemets doubles"
pour envelopper la regex pour éviter l'expansion du shell