Renommer cinq fichiers à la main, c’est supportable. En renommer trois cents — les photos d’un catalogue produit envoyées pêle-mêle par un client, avec des espaces, des majuscules et des accents dans les noms — c’est une corvée qui prend l’après-midi et où l’on se trompe. C’est précisément ce que les boucles font à votre place : appliquer la même action à chaque élément d’un lot, sans fatigue et sans erreur. À la fin de ce tutoriel, un seul script nettoiera un dossier entier de photos en quelques secondes.
Dans le fil rouge de la série, Boubacar reçoit régulièrement des dossiers de photos pour les boutiques en ligne qu’il gère. Les noms sont catastrophiques : IMG 2024 (1).JPG, Photo Final!!.png… Il va écrire renommer-photos.sh pour tout convertir en une convention propre : produit-001.jpg, produit-002.jpg, etc.
🎯 Ce que vous allez apprendre
- Écrire des boucles
for(sur une liste et à la C),whileetuntil; - Parcourir des fichiers proprement avec le globbing (
*.jpg) et éviter le piègefor f in $(ls); - Lire un fichier ligne par ligne avec
while IFS= read -rsans rien casser ; - Manipuler des noms de fichiers avec l’expansion de paramètres (
${f##*/},${f%.jpg},${f,,}).
🛠️ Ce que vous allez construire
Un script renommer-photos.sh qui prend en argument un dossier de photos, les renomme selon une convention numérotée propre, ignore les fichiers déjà conformes, et affiche un compte rendu de chaque opération. La corvée de l’après-midi devient une commande d’une ligne.
Prérequis
- Avoir suivi Variables, conditions et tests (
if,[[ ]], codes de sortie). - Test express : si vous savez écrire un
if [[ -f "$x" ]], vous êtes prêt. - Bash 4+ (l’expansion
${var,,}exige Bash 4 ; testé sous Bash 5.3). ⏱️ ~35 minutes.
Étape 1 — La boucle for sur une liste
La forme la plus courante de boucle parcourt une liste de valeurs, une par une. À chaque tour, la variable de boucle prend la valeur suivante, et le bloc entre do et done s’exécute. Commençons par le cas le plus simple : une liste écrite à la main.
for client in boutique-aminata association-thies cabinet-diop; do
echo "Traitement du site : $client"
done
À chaque itération, $client vaut tour à tour chacun des trois noms, et la ligne echo s’exécute trois fois. La structure for ... in ...; do ... done est le squelette : on y reviendra sans cesse. Notez le point-virgule avant do (ou un retour à la ligne), exactement comme pour le if.
Étape 2 — Parcourir des fichiers avec le globbing
Le vrai usage, c’est de boucler sur des fichiers. Et ici, une tentation guette tous les débutants : écrire for f in $(ls *.jpg). Ne le faites jamais. Cette forme casse dès qu’un nom contient une espace (le fichier Photo Final.jpg est vu comme deux fichiers Photo et Final.jpg). La bonne méthode est le globbing : on donne directement le motif à for, et Bash le développe en liste de fichiers réels.
for photo in *.jpg; do
echo "Photo trouvée : $photo"
done
Bash remplace *.jpg par tous les fichiers .jpg du dossier courant, en gérant correctement les espaces et caractères spéciaux. Chaque $photo est un nom intact. Un détail à connaître : si aucun fichier ne correspond, Bash laisse le motif *.jpg littéral, et la boucle tourne une fois avec cette valeur bizarre. On corrige ce comportement à l’étape suivante.
Étape 3 — Sécuriser le globbing avec shopt
Deux options de Bash rendent le parcours de fichiers fiable. nullglob fait qu’un motif sans correspondance se développe en rien (la boucle ne tourne pas) plutôt qu’en texte littéral. nocaseglob rend le motif insensible à la casse, pour attraper aussi bien .jpg que .JPG. On les active avec shopt -s.
shopt -s nullglob nocaseglob
for photo in *.jpg *.jpeg *.png; do
echo "À traiter : $photo"
done
Désormais, si le dossier ne contient aucune image, la boucle est simplement sautée — pas de message parasite. Et un fichier nommé VACANCES.JPG est bien attrapé par le motif *.jpg grâce à nocaseglob. Ces deux réglages devraient être un réflexe dans tout script qui parcourt des fichiers.
✅ Point d’étape — Créez un dossier de test avec quelques fichiers (
touch "Photo Une.jpg" deux.JPG trois.png) et vérifiez que votre boucle les liste tous, espaces compris. C’est le terrain de jeu pour la suite.
Étape 4 — Manipuler les noms de fichiers
Renommer, c’est transformer un nom en un autre. Bash dispose pour cela d’opérations d’expansion de paramètres très efficaces, qui évitent de lancer des commandes externes pour chaque fichier (important quand on en traite des centaines). Voici les plus utiles sur des chemins.
chemin="/var/www/boutique/Photo Une.JPG"
echo "${chemin##*/}" # Photo Une.JPG → ne garde que le nom (comme basename)
echo "${chemin%/*}" # /var/www/boutique → ne garde que le dossier (comme dirname)
echo "${chemin##*.}" # JPG → l'extension seule
nom="${chemin##*/}"
echo "${nom%.*}" # Photo Une → le nom sans extension
echo "${nom,,}" # photo une.jpg → tout en minuscules (Bash 4+)
echo "${nom// /-}" # Photo-Une.JPG → remplace toutes les espaces par des tirets
Décortiquons : ##*/ supprime le plus long préfixe finissant par / (donc tout le chemin, on garde le nom). %.* supprime le plus court suffixe commençant par . (donc l’extension). ,, met en minuscules. // /- est un remplacer-tout : « remplace toutes les espaces par des tirets ». Ces transformations sont instantanées et purement internes à Bash — pas de processus lancé, contrairement à basename ou sed.
Étape 5 — La boucle while read pour lire un fichier
L’autre grande boucle, while, répète tant qu’une condition reste vraie. Son usage emblématique est la lecture d’un fichier ligne par ligne. La forme correcte, à mémoriser telle quelle, est while IFS= read -r ligne; do ... done < fichier. Chaque mot compte.
while IFS= read -r ligne; do
echo "Site à sauvegarder : $ligne"
done < sites.txt
Pourquoi cette formule exacte ? read -r lit une ligne sans interpréter les antislashs (sinon un \ dans un nom serait mangé). IFS= en début de ligne empêche read de rogner les espaces de début et de fin. Et < sites.txt redirige le fichier vers la boucle. Avec cette forme, vous lisez n’importe quel fichier — une liste de sites, de fichiers, d’URLs — sans surprise. C’est un idiome qu’on garde tel quel.
Pour lire la sortie d’une commande plutôt qu’un fichier, on utilise la substitution de processus < <(commande), qui évite un piège subtil du pipe (les variables modifiées dans une boucle après un | sont parfois perdues) :
compteur=0
while IFS= read -r fichier; do
(( compteur++ ))
done < <(find . -name '*.log')
echo "$compteur journaux trouvés."
Ici la variable compteur garde bien sa valeur après la boucle, ce qui ne serait pas garanti avec find ... | while .... Retenez la forme done < <(commande) dès que vous bouclez sur une sortie ET modifiez une variable.
Parfois on veut d’abord rassembler tous les éléments dans une liste pour les compter ou les trier avant d’agir. Bash dispose pour cela de tableaux, et de mapfile (alias readarray) qui charge des lignes directement dans un tableau, une par case. C’est plus sûr que de bricoler avec des chaînes séparées par des espaces.
mapfile -t photos < <(find . -maxdepth 1 -name '*.jpg')
echo "Nombre de photos : ${#photos[@]}" # taille du tableau
echo "Première : ${photos[0]}" # premier élément
for p in "${photos[@]}"; do # parcourir tout le tableau
echo " - $p"
done
L’option -t retire le saut de ligne final de chaque entrée. On accède à la taille avec ${#photos[@]}, à un élément par son index ${photos[0]}, et on parcourt tout avec "${photos[@]}" — les guillemets et le [@] sont essentiels pour préserver les noms à espaces. Les tableaux deviennent vite indispensables dès qu’un script manipule des collections (fichiers, serveurs, options).
✅ Point d’étape — Vous distinguez
for(sur une liste connue, typiquement des fichiers via globbing) etwhile read(sur les lignes d’un fichier ou d’une commande), et vous savez stocker une liste dans un tableau avecmapfile. Vous transformez un nom de fichier sans outil externe.
Étape 6 — La boucle for à la C et les compteurs
Quand on a besoin d’un compteur numérique — pour numéroter nos photos, justement — la forme arithmétique for (( )) est la plus claire. Elle reprend la syntaxe des langages comme C : initialisation, condition, incrément.
for (( i = 1; i <= 5; i++ )); do
printf 'produit-%03d\n' "$i"
done
Cette boucle affiche produit-001 à produit-005. i++ augmente i de 1 à chaque tour, i <= 5 est la condition d’arrêt. Le %03d de printf formate le nombre sur trois chiffres avec des zéros devant — indispensable pour que les fichiers se trient correctement (sinon produit-10 se classe avant produit-2). On combine maintenant tout ça.
Étape 7 — Assembler renommer-photos.sh
Voici l’outil complet. Il se place dans le dossier passé en argument, active le globbing sûr, et numérote chaque image avec un compteur, en minuscules et sans espaces. Il refuse d’écraser un fichier existant grâce à mv -n, et le -- protège contre les noms commençant par un tiret.
#!/usr/bin/env bash
#
# renommer-photos.sh — normalise les noms des photos d'un dossier.
# Usage : ./renommer-photos.sh ~/clients/boutique-aminata/photos
# Boîte à outils Atelier — tutoriel 3.
dossier="${1:?Usage : $0 <dossier-photos>}"
cd "$dossier" || { echo "❌ Dossier inaccessible : $dossier" >&2; exit 1; }
shopt -s nullglob nocaseglob
compteur=1
renommees=0
for photo in *.jpg *.jpeg *.png; do
ext="${photo##*.}" # extension d'origine
ext="${ext,,}" # en minuscules
[[ "$ext" == "jpeg" ]] && ext="jpg" # on uniformise jpeg → jpg
nouveau=$(printf 'produit-%03d.%s' "$compteur" "$ext")
if [[ "$photo" == "$nouveau" ]]; then
echo "⏭ $photo est déjà conforme."
elif [[ -e "$nouveau" ]]; then
echo "⚠️ $nouveau existe déjà, $photo non renommée." >&2
else
mv -n -- "$photo" "$nouveau"
echo "✅ $photo → $nouveau"
(( renommees++ ))
fi
(( compteur++ ))
done
echo "Terminé : $renommees photo(s) renommée(s) sur $(( compteur - 1 )) examinée(s)."
Plusieurs réflexes acquis se retrouvent ici : l’argument obligatoire ${1:?...}, le cd ... || { ...; exit 1; } qui regroupe message et sortie en cas d’échec, le globbing sécurisé, l’expansion de paramètres pour l’extension, et printf '%03d' pour la numérotation. La condition [[ "$photo" == "$nouveau" ]] rend le script idempotent : on peut le relancer sans tout re-renommer en boucle. Testez-le sur un dossier de copies (jamais sur vos originaux du premier coup !).
✅ Point d’étape final — Sur un dossier de test, le script renomme les images en
produit-001.jpg,produit-002.jpg… et affiche un bilan. Relancez-le : il doit dire « déjà conforme » pour les fichiers déjà traités, preuve qu’il est sûr à relancer.
🐞 Pièges fréquents
| Symptôme / erreur | Cause probable | Correctif |
|---|---|---|
Un fichier *.jpg littéral est « traité » |
Aucun fichier ne correspond et nullglob n’est pas actif |
shopt -s nullglob en tête de script |
| Noms à espaces découpés en plusieurs | for f in $(ls) ou variable sans guillemets |
Boucler sur un glob ; toujours "$f" |
| Espaces de fin de ligne perdus à la lecture | read sans IFS= |
while IFS= read -r ligne |
| Compteur remis à zéro après un pipe | cmd | while ouvre un sous-shell |
Utiliser done < <(cmd) |
${nom,,}: bad substitution |
Bash trop ancien (3.2, ex. macOS) | Installer un Bash 4+ ou utiliser tr '[:upper:]' '[:lower:]' |
🌍 Adaptation au contexte ouest-africain
Le traitement par lot est une mine d’or pour les freelances et les petites agences de la région : préparer des centaines de photos produit pour une boutique en ligne, organiser les justificatifs scannés d’une association, ranger des PDF de factures. Tout cela se fait localement, sans abonnement à un logiciel cloud payant en devises. Un point d’attention : sur des supports lents (clé USB, disque externe sur un vieux port), le traitement de gros volumes prend du temps — testez d’abord sur un petit échantillon, et travaillez sur une copie pour ne jamais risquer les originaux d’un client.
✅ Récapitulatif
Vous savez désormais répéter une action sur des lots entiers. Vous maîtrisez la boucle for sur une liste et sur un glob (*.jpg), la forme arithmétique for (( )) avec compteur, et la boucle while IFS= read -r pour parcourir un fichier ou une commande ligne par ligne. Vous évitez les deux grands pièges — for f in $(ls) et le pipe qui perd les variables — et vous transformez les noms de fichiers à la volée avec l’expansion de paramètres. renommer-photos.sh rejoint la boîte à outils, et il est idempotent.
🧾 Aide-mémoire
| Élément | Rôle |
|---|---|
for x in *.jpg; do ... done |
Boucle sur des fichiers (globbing sûr) |
for (( i=1; i<=N; i++ )) |
Boucle avec compteur numérique |
while IFS= read -r l; do ... done < f |
Lire un fichier ligne par ligne |
done < <(commande) |
Boucler sur une sortie sans perdre les variables |
shopt -s nullglob nocaseglob |
Glob sûr et insensible à la casse |
${f##*/} / ${f%.*} |
Nom seul / nom sans extension |
${f,,} / ${f// /-} |
Minuscules / remplacer espaces par tirets |
💪 À vous de jouer
Modifiez renommer-photos.sh pour qu’il range les images renommées dans un sous-dossier pretes/ au lieu de les renommer sur place — plus sûr, car les originaux restent intacts.
Voir une solution
destination="$dossier/pretes"
mkdir -p "$destination"
# ... dans la boucle, remplacer le mv par :
cp -n -- "$photo" "$destination/$nouveau" && echo "✅ $photo → pretes/$nouveau"
On utilise cp au lieu de mv pour conserver les originaux, et mkdir -p crée le dossier de destination une seule fois avant la boucle.
Tutoriels frères
- Variables, conditions et tests en Bash — la logique de décision utilisée dans la boucle.
- Fonctions, arguments et scripts robustes — transformer ces scripts en outils réutilisables.
Pour aller plus loin
- 🔝 Retour au guide principal : Bash scripting : le guide complet.
- Prochain tutoriel conseillé : Fonctions, arguments et robustesse.
- Section « Shell Parameter Expansion » du manuel GNU Bash.
FAQ
Q : Pourquoi ne pas faire for f in $(ls) ?
R : Parce que ls renvoie du texte que Bash redécoupe sur les espaces : un nom de fichier contenant une espace est cassé en plusieurs, et les caractères spéciaux s’emmêlent. Le globbing (for f in *) donne des noms intacts.
Q : Quelle différence entre while et until ?
R : while répète tant que la condition est vraie ; until répète jusqu’à ce que la condition devienne vraie (donc tant qu’elle est fausse). until est rare ; on s’en sert pour attendre qu’un service démarre, par exemple.
Q : Mon expansion ${nom,,} renvoie « bad substitution ». Pourquoi ?
R : Cette syntaxe exige Bash 4. Vous êtes probablement sur un Bash 3.2 (macOS par défaut). Installez un Bash récent, ou utilisez tr '[:upper:]' '[:lower:]' à la place.
Q : Comment parcourir aussi les sous-dossiers ?
R : Le glob simple *.jpg ne descend pas dans l’arborescence. Activez shopt -s globstar et utilisez **/*.jpg, ou passez par find avec une boucle while read.
📚 Ressources et références
- GNU Bash Manual — Looping Constructs : la référence sur
for,while,until. - BashFAQ 001 — How can I read a file line by line : l’idiome
while readexpliqué en détail. - Linux fondamentaux — pour réviser
find,mv,cpet la navigation.
Mots-clés : boucle bash, for bash, while read bash, globbing, traitement de fichiers, renommer fichiers en lot, expansion de paramètres, nullglob.