1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
|
#!/bin/bash
# Script du mini projet du cours IN405
# Auteur : Marc DORIDANT
# Ce script doit permettre de renommer une liste de fichiers
# en appliquant une commande Unix passée en paramètre
# pour transformer le nom dorigine de chaque fichier en un nouveau nom
# Ce script comporte trois parties
# Partie 1 : Positionnement des traps
# Partie 2 : Déclarations des variables et des fonctions utiles aux traitements
# Partie 3 : Corps du script consacré à l'analyse des paramètres (options et arguments) avant appel aux fonctions de traitements
#####################################
# Partie 1 : Positionnement des traps
#####################################
trap confirmer 2 3 # L'utilisateur devra confirmer avant de quitter (la fonction confirmer appelera aussi la fonction bilan)
trap bilan 0 # Un bilan du nombre de fichier(s) traité(s) sera fourni avant de sortir
######################################
# Partie 1 : Déclaration des variables
######################################
# Variable relative au nombre de fichiers traités et au fait que le traitement a pu débuter
# Tant qu'il vaut N, le véritable traitement des fichiers n'a pas débuté ou n'a pas pu débuter
# S'il passe à 0, le traitement a débuté mais n'a pas encore traité de fichier
nb=N # Par défaut, aucun fichier n'a été traité, et on n'a même pas débuté de traitement
# Variable relative à l'activation du mode verbeux
verbeux=N # Par défaut, le mode n'est pas activé (N pour No, Y pour Yes)
# Variable relative à l'activation du mode interactif, où l'utilisateur doit confirmer pour chaque fichier
confirmation=N # Par défaut, le mode n'est pas activé (N pour No, Y pour Yes)
# Variable relative à l'activation du mode avec extension
extension=N # Par défaut, le mode n'est pas activé (N pour No, Y pour Yes)
# Variable relative au nom avec chemin absolu du script
# Elle sera nécessaire pour s'interdire de se renommer soit-même
myself=`readlink -f $(which "$0")`
######################################
# Partie 3 : Déclaration des fonctions
######################################
# Fonctions d'erreurs : appelée lorsqu'une erreur est détectée par le script
# Elles envoient leurs messages sur la sortie d'erreur
# Puis elles termine le script avec un code de retour selon le numéro d'erreur
erreur1(){
echo "Erreur : rename nécessite des paramètres !" >&2
echo "Utilisez l'option -h pour afficher l'aide sur cette commande" >&2
exit 1
}
erreur2(){
echo "Erreur : option inconnue !" >&2
echo "Utilisez l'option -h pour afficher l'aide sur cette commande" >&2
exit 2
}
erreur3(){
echo "Erreur : fonction non autorisée !" >&2
echo "Utilisez l'option --allowed pour afficher la liste des fonctions autorisées" >&2
exit 3
}
erreur4(){
echo "Erreur : Aucun fichier à traiter !" >&2
echo "Rename nécessite des noms de fichier en paramètre" >&2
echo "Utilisez l'option -h pour afficher l'aide sur cette commande" >&2
exit 4
}
erreur5(){
echo "Erreur : Fonction autorisée mais commande invalide !" >&2
echo "Veuillez vous reporter au man de la commande utilisée" >&2
exit 5
}
# D'autres messages d'erreurs peuvent être générés, au sein du script
# N'étant pas factorisable et ne provoquant pas de sortie du script, ils sont générés "sur place"
# Fonction aide : appelée par l'option -h sur le script
# Elle affiche l'aide pour utiliser la commande
aide(){
echo "Usage rename.sh [-q] [-v] [-h] <commande> fichier ..."
echo "Renomme les fichiers en appliquant la commande sur les noms"
# Cette option a été ajoutée suite à des tests faisant apparaitre des comportements insatisfaisants
# En effet, faire un rev sur toto.txt qui génère un txt.otot n'était pas satisfaisant
# En revanche, l'utilisateur doit pouvoir utiliser sed ou tr pour modifier des extensions de fichier
echo "-e : extension, autorise la commande à agir sur l'extension du fichier"
echo "-q : query, demande confirmation pour chaque fichier"
echo "-v : verbeux, affiche ce qui est fait"
echo "--alowed : affiche la liste des commandes autorisées"
# L'option --help a été ajouté juste pour le plaisir de voir comment traiter des options longues
echo "-h -- help : affichage de ce message d'aide"
echo "Exemple :"
echo "rename.sh 'tr a-z A-Z' toto titi"
echo "renommera toto en TOTO et titi en TITI"
exit 0
}
# Fonction allowed : appelée par l'option -allowed sur le script
# Cette fonction affiche la liste des commandes autorisées en paramètre de ce script
# En effet, dans l'optique de sécuriser l'usage du script à sa capacité de renommer des fichiers
# J'ai choisi de limiter les fonctions utilisables pour éviter tout détournement
# Dans l'idéal, j'aurais souhaité catégoriser les commandes agissant sur des lignes de texte lues sur l'entrée standard
# Je n'y suis pas parvenu j'ai donc recherché les commandes utilisables dans ce contexte
# Cette liste peut facilement être étendue (voir les 15 dernières lignes du script)
allowed(){
echo "Liste des commandes autorisées"
echo "tr"
echo "head -c (option obligatoire)"
echo "tail -c (option obligatoire)"
echo "rev"
echo "cut"
echo "sed"
exit 0
}
# Fonction confirmer : appelée si l'utilsateur tente de terminer le script pendant son exécution
confirmer(){
echo "Voulez-vous vraiment intérompre l'exécution de rename ? (Y/N)"
# Lecture d'un seul caractère en tant que réponse
read -n 1 -s conf
# On ne confirme l'intéruption qu'avec Y ou y, tout autre caractère fait reprendre le traitement
if [ "$conf" = "Y" ] || [ "$conf" = "y" ] ; then
exit 0
fi
# On informe l'utilsateur de cette reprise
echo "Reprise"
}
# Fonction bilan : appelée lors d'une sortie sans erreur de rename
bilan(){
# Si nb est encore à N, c'est qu'on n'a rien fait (sortie via help ou allowed, ou avant même d'avoir validé la commande)
if test "$nb" != "N"; then
if [ $nb -eq 0 ]; then # On a commencé mais aucun fichier n'a été renommé
echo "Aucun fichier n'a été traité"
elif [ $nb -eq 1 ]; then # Un fichier renommé
echo "Un fichier a été renommé"
else # Plusieurs fichiers renommés (orthographe oblige !)
echo "$nb fichiers ont été renommés"
fi
fi
}
#####################################################################################################
# Fonction renommer : réalise l'odre de renommer un fichier et gère le décompte des fichiers renommés
#####################################################################################################
# Cette fonction est l'ultime étape, à l'échelle d'un fichier, du traitement
renommer(){
# Certaines vérifications préliminaires faites ici aurait pu être déléguées à la commande mv
# Mais afin de fiabliser le décompte des fichiers vraiment traités
# Et pour masquer l'implémentation à l'utilisateur (seul le script lui parle sauf mode verbeux)
# C'est le script que se charge en amont de ces vérifications et qui propose les solutions
# On vérifie si on ne va pas écraser un fichier existant
if test -f "$2"; then
# Si on a le droit d'écraser ce fichier
if test -w "$2"; then
# On propose d'écraser le fichier
echo "Le nouveau nom "${2##./}" du fichier "${1##./}" existe, voulez-vous écrasez l'actuel "${2##./}" ? (Y/N)"
# Lecture d'un seul caractère en tant que réponse
read -n 1 -s conf
# On ne confirme l'écrasement qu'avec Y ou y, tout autre caractère fait reprendre le traitement sans écraser le fichier
if [ "$conf" != "Y" ] && [ "$conf" != "y" ] ; then
return # On sort de la fonction
fi
else
# Note : mv proposerait d'outrepasser les droits quand c'est possible, pas ce script
echo "Impossible de renommer "${1##./}" en "${2##./}", "${2##./}" existe et vous n'avez pas les droits pour l'écraser" >&2
return
fi
else
if test -e "$2"; then
echo "Impossible de renommer "${1##./}" en "${2##./}", "${2##./}" existe et n'est pas un fichier ordinaire" >&2
return
fi
fi
# C'est cette commande "ultime" qui réalise vraiment le taitement
$ordre "$1" "$2"
# Si cette dernière commande s'est bien terminée
if [ $? -eq 0 ]; then
# On a traité un fichier de plus
nb=`expr $nb + 1`
fi
}
#############################################################
# Fonction traitement : réalise le traitement nominal attendu
#############################################################
# Cette fonction est appelée lorsque tous les contrôles des paramètres ont été effectués
# Elle est le corps principal du traitement des fichiers, préparant le nécessaire pour l'appel de la fonction renommer
traitement(){
# La commande ayant été validée syntaxiquement
# on considère que le vrai traitement a débuté
# On peut donc commencer à compter les fichiers traités
nb=0
# Tant qu'il y a des noms de fichier à traiter
while [ $# -gt 0 ]; do
if test -f "$1"; then
if test -w "$1"; then
# On s'interdit de se renommer soit même
# On détermine le nom avec chemin absolu du fichier à renommer
absNom=`readlink -f "$1"`
# On le compare avec celui du script rename.sh
if [ "$absNom" = "$myself" ]; then
echo "Il est interdit d'utiliser ce script sur lui même" >&2
else
# À ce stade, le fichier existe, est ordinaire, et l'on possède les droits pour le renommer
# Il faut cependant limiter cette action au nom de fichier, en écartant son chemin et son suffixe
# On sépare d'abord le nom complet du chemin
chem=`dirname "$1"`
nomComp=`basename "$1"`
if [ "$extension" = "Y" ]; then
name="$nomComp"
ext=""
else
# Puis on sépare le nom de l'extension
ext="${nomComp##*.}"
name="${nomComp%.*}"
fi
# Cas particulier : s'il s'agit d'un fichier sans extension, ext et name seront égaux
if test "$ext" = "$name"; then
# Si la recomposition ne redonne pas le nom complet du fichier
if test "$name"."$ext" != "$nomComp"; then
ext="" # C'est qu'il n'y a pas d'extension
fi
fi
# Si l'extension n'est pas vide, alors on prépare la recomposition du nom de fichier complet
if test "$ext" != ""; then
ext=."$ext"
fi
# On tente d'appliquer la fonction fournie sur ce nom de fichier pour fabriquer le nouveau
newName=`echo "$name" | eval "$commande"`
# Si la dernière commande ne s'est pas bien terminée
if [ $? -ne 0 ]; then
erreur5 # C'est que l'utilisateur a mal formulé sa commande, on sort avec une erreur 5
fi
# Avant de renommer le fichier, il faut s'assurer que ce nouveau nom est valable et utile
# Il faut que $newName ne soit ni vide, ni ".", ni "..", ni identique à l'ancien nom
# Il faut aussi qu'il ne contienne aucun caractère interdit ( "/" et "\0" )
# On pourrait aussi proscrire d'autres caractères fortement déconseillés comme le retour à la ligne
# Il semblerait après plusieurs tests que "\0" ne pose aucun soucis mais il reste filté par prudence
if [ "$name" = "$newName" ]; then
# Ce message n'est pas considéré comme un message d'erreur, il s'agit d'une information
# Il est donc laissé sur la sortie standard
echo "Aucun changement pour "${1##./}", le nouveau nom est identique à l'ancien"
else
if [ -z "$newName" ] || [ "$newName" = "." ] || [ "$newName" = ".." ] || [[ "$newName" == */* ]] || [[ "$newName" == *\0* ]]; then
echo "Aucun changement pour "${1##./}", le nouveau nom est vide ou interdit" >&2
else
# On recompose le nom complet du fichier
newName="$chem"/"$newName""$ext"
# On vérfie maintenant si le script ne se cible pas lui-même
absNom=`readlink -f "$newName"`
# On le compare avec celui du script rename.sh
if [ "$absNom" = "$myself" ]; then
echo "Aucun changement pour "${1##./}", le nouveau nom est interdit" >&2
else
# Si on est en mode confirmation
if [ "$confirmation" = "Y" ] ; then
# On montre la sitation à l'utilisateur pour avis
echo "Voulez-vous renommmer $1 en $new ? (Y/N)"
# Réponse en 1 caractère, masqué, sans apuyer sur entrée
read -n 1 -s rep
# Y validera le changement de nom, tout autre réponse non
if [ "$rep" = "Y" ] || [ "$rep" = "y" ] ; then
renommer "$1" "$newName"
fi
else
# Commande exécutée sans confirmation
renommer "$1" "$newName"
fi
fi
fi
fi
fi
else
echo "Erreur : Vous n'avez pas les droits requis pour renommer "${1##./}"" >&2
fi
else
echo "Erreur : "${1##./}" n'existe pas ou n'est pas un fichier ordinaire" >&2
fi
shift
done
}
# Partie 2 : Corps du script consacré à l'analyse des options et arguments passés en paramètres
# S'il n'y a aucun paramètres, on appelle la fonction d'erreur
[[ $# -lt 1 ]] && erreur1
# Analyse des options à l'aide de la fonction getopt
# Les options invalides seront ignorées
# Les options peuvent avoir été mélangées dans les paramètres, elles seront prises en compte
options=$(getopt -o q,v,h,e -l help,allowed -- "$@" 2>/dev/null)
# éclatement de $options en $1, $2...
eval set -- $options
while true; do
case "$1" in
-q) confirmation=Y # On mémorise que le mode confirmation est actif
shift;; # On décale la liste des options de 1
-v) verbeux=Y # On mémorise que le mode verbeux est actif
shift;; # On décale la liste des options de 1
-e) extension=Y # On inclu l'extension dans la partie à renommer
shift;; # On décale la liste des options de 1
-h|--help) aide # On lance la fonction d'affichage de l'aide, la présence de cette option inhibe tout traitement
shift;; # On décale la liste des options de 1
--allowed) allowed # On lance l'affichage des fonctions autorisées, la présence de cette option inhibe tout taitement
shift;;
--) # fin des options
shift # On décale la liste des options de 1
break;; # On est prêt pour passer au traitement
*) erreur2;; # Au cas où quelque chose passerait au travers de getopt, on sort avec une erreur 2
esac
done
# Analyse du premier argument : la commande
# On commance par récupérer la commande passé en argument dans la variable commande
commande="$1"
shift # On décale la liste des options de 1, il ne doit rester que les noms de fichier à traiter
# Il faut s'assurer que la commande et ses paramètres sont valides et non dangereux
# Cette validation repose une liste de commandes autorisées (avec éventuellement une option obligatoire)
# Liste des commandes actuellement autorisées : voir fonction allowed
# Pour savoir si la commande correspond à l'une de ses commandes autorisées, on test si la chaine correspond à l'un des motifs
# On chaine ces tests de tels sorte que si rien ne match, le code de retour final ne sera pas 0
expr match "$commande" '^tr ' >&/dev/null || expr match "$commande" '^head -c ' >&/dev/null || expr match "$commande" '^tail -c ' >&/dev/null || expr match "$commande" '^rev$' >&/dev/null || expr match "$commande" '^cut ' >&/dev/null || expr match "$commande" '^sed ' >&/dev/null
# Si le code de retour de la chaine de commande précendent vaut 0, c'est que la commande passée n'est pas autorisée
if [ $? -ne 0 ]; then
erreur3 # Donc on sort avec une erreur 3
fi
# Note : Pour étendre l'usage du script, il suffit donc d'ajouter un test dans la chaine
# Il ne faut pas oublier de mettre à jour la fonction allowed en conséquence
# Vérification sommaire sur les noms de fichier
# S'il n'y a pas d'autre paramètre, c'est qu'aucun nom de fichier n'a été saisi
if [ $# -eq 0 ]; then
erreur4 # Donc on sort avec une erreur 4
fi
# Les vérifications plus poussées se feront en durant le traitement
# On prépare la commande, à base de commande mv
if [ "$verbeux" = "Y" ]; then # Si le mode verbeur a été demandé
ordre="mv -v" # On s'appuie sur le mode verbeux de la commande mv (dans une version précédente, on utilisait mv -v -i)
else
ordre="mv" # On garde la commande mv de base (dans une version précedente, on utilisait mv -i)
fi
# On peut maitenant lancer la fonction de traitement
traitement "$@" |
Partager