objectif
Renommer rapidement un grand nombre de fichiers (espaces -> tirets, mise en minuscules, normalisation d’extensions) avec un aperçu sécurisé (dry-run), en gérant les noms tordus et en évitant les collisions.
code minimal
# dry-run: espaces -> tirets, minuscules, .JPG/.PNG -> .jpg/.png (perl rename)
rename -n 's/\s+/-/g; $_=lc $_; s/\.jpg$/.jpg/; s/\.jpeg$/.jpg/; s/\.png$/.png/' -- *.JPG *.JPEG *.PNG *.png *.jpg
# exécution (retirer -n)
# rename 's/\s+/-/g; $_=lc $_; s/\.jpeg$/.jpg/; s/\.png$/.png/' -- *.JPG *.JPEG *.PNG *.png *.jpg
utilisation
# 1) détecter la variante de rename et adapter (perl vs util-linux)
if rename -V 2>&1 | grep -qi perl; then
variant="perl"
else
variant="util"
fi
echo "rename variant: $variant"
# 2) perl rename: slug simple pour assets de data.pm
# -n apercu, -v verbose; utilisez -0 pour NUL-terminated
find ./public -type f -print0 \
| xargs -0 -I{} bash -lc 'f="{}"; b="${f##*/}"; d="${f%/*}";
nb="$(perl -CSDA -MUnicode::Normalize -e "$code" <<<"$b")";
[ "$b" = "$nb" ] || { echo "$f -> $d/$nb"; [ "${DRY:-1}" = 1 ] || mv -n -- "$f" "$d/$nb"; }' \
code='binmode(STDIN,":utf8"); $_=<>; chomp;
$_=NFD($_); s/[^A-Za-z0-9._-]+/-/g; s/-+/-/g; s/^-|-$//g; $_=lc $_; print $_'
# exécuter réellement: DRY=0 ... (environnement)
# 3) util-linux rename (syntaxe "from to files"): .jpeg -> .jpg sur un dossier
# dry-run:
rename -n .jpeg .jpg ./images/*.jpeg
# exécution:
rename .jpeg .jpg ./images/*.jpeg
# 4) ajouter un préfixe "data.pm-" aux dumps du jour (perl rename)
find ./backups -maxdepth 1 -type f -name "dump-*.sql" -print0 \
| xargs -0 rename -n 's{^.*/}{${&}}e; s{^}{data.pm-} unless /^data\.pm-/' 2>/dev/null \
|| echo "util-linux rename ne supporte pas les regex; utilisez mv en fallback"
# 5) sécuriser contre les collisions: renommer via temp unique puis final
find ./uploads -type f -name "*.csv" -print0 \
| xargs -0 -I{} bash -lc 'f="{}"; d="${f%/*}"; b="${f##*/}";
nb="$(echo "$b" | tr "[:upper:]" "[:lower:]" | sed -E "s/[[:space:]]+/-/g")";
tmp="$d/.ren.$(date +%s%N).$$.$RANDOM";
[ "$b" = "$nb" ] || { mv -n -- "$f" "$tmp" && mv -n -- "$tmp" "$d/$nb"; }'
variante(s) utile(s)
# A) fallback portable sans rename: loop + mv (dry-run avec DRY=1)
DRY=1
find . -type f -name "*.md" -print0 \
| while IFS= read -r -d '' f; do
d="${f%/*}"; b="${f##*/}"
nb="$(printf '%s' "$b" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9._-]+/-/g; s/-+/-/g; s/^-|-$//g')"
[ "$b" = "$nb" ] && continue
echo "$f -> $d/$nb"
[ "${DRY}" = 1 ] || { tmp="$d/.ren.$RANDOM.$$"; mv -n -- "$f" "$tmp" && mv -n -- "$tmp" "$d/$nb"; }
done
# B) renommer récursivement uniquement sous data.pm/static et ignorer node_modules
find ./data.pm/static -type f -not -path "*/node_modules/*" -name "* *" -print0 \
| xargs -0 rename -n 's/\s+/-/g'
# C) changer une extension partout (util-linux): .tar.gz -> .tgz
find . -type f -name "*.tar.gz" -print0 | xargs -0 -I{} bash -lc '
f="{}"; nf="${f%.tar.gz}.tgz"; echo "$f -> $nf"; [ "${DRY:-1}" = 1 ] || mv -n -- "$f" "$nf"
'
# D) prévisualiser les collisions potentielles
find . -type f -print0 \
| xargs -0 -I{} bash -lc 'b="${1##*/}"; nb="$(echo "$b" | tr A-Z a-z | sed -E "s/\s+/-/g")"; echo "$nb"' bash {} \
| sort | uniq -d
# E) git: renommer les fichiers suivis avec préservation de l'historique
git ls-files -z '*.PNG' '*.JPG' \
| xargs -0 -I{} bash -lc 'f="{}"; g="$(echo "$f" | sed -E "s/\.PNG$/.png/; s/\.JPG$/.jpg/")"; [ "$f" = "$g" ] || git mv -- "$f" "$g"'
notes
- Il existe deux
rename: la variante perl (puissante, regex et-nde dry-run) et la variante util-linux (simple “from to files”). Détectez la vôtre. - Commencez toujours par un dry-run et utilisez des listes NUL-terminées (
-print0/-0) pour des noms robustes. - Évitez les collisions: calculez d’abord les nouveaux noms, vérifiez les doublons (variante D) et, si besoin, renommez via un fichier temporaire.
- Sur des dépôts git, préférez
git mvpour conserver l’historique et faciliter les revues. - Pour la normalisation avancée (accents, unicode), utilisez
Unicode::Normalize(perl) ou un script Python de “slugify” si vous avez des besoins multi-langues.