Table des matières
Résumé
A la fin de ce chapitre, vous serez capable de
Ecrire des boucles for, while and until , et décider quelle boucle convient à quelle besoin.
Utiliser les intégrées Bash break et continue
Ecrire des scripts avec l'instruction select.
Ecrire des scripts qui admettent un nombre variable de paramètres.
La boucle for est la première des 3 structures de boucles du Shell. Cette boucle autorise la spécification d'une liste de valeurs. Une liste de commandes est exécutée pour chaque valeur de la liste.
La syntaxe de cette boucle est:
for NOM [in LIST ]; do COMMANDES; done
Si [in LIST] est absent, il est remplacé par in $@ et for exécute les COMMANDES une fois pour chaque paramètre positionnel déclaré (voir Section 2.5, « Paramètres Spéciaux » et Section 2.1.2, « Contrôle des paramètres de la ligne de commande »).
Le statut retourné est le statut d'exécution de la dernière commande exécutée. Si aucune commande n'est exécutée parce que LIST ne résulte en aucun élément, le code retour est zéro.
NOM peut être tout nom de variable, même si i est employé très souvent. LIST peut être toute liste de mots, chaînes ou nombres qui peuvent être des littéraux ou générés par toute commande. Les COMMANDES à exécuter peuvent être aussi toute commande système, script, programme ou instruction Shell. Au premier passage dans la boucle, NOM est valorisé à la valeur du premier élément dans LIST. Au deuxième passage, sa valeur est donnée par le second élément dans la liste, et ainsi de suite. La boucle termine quand NOM a pris une fois la valeur de chaque élément de LIST et qu'il ne reste plus d'éléments dans LIST.
Le premier exemple est une ligne de commande qui montre l'emploi d'une boucle for pour effectuer une copie de sauvegarde de chaque fichier .xml. Une fois cette commande exécutée, il est plus sûr de travailler sur les sources:
[carol@octarine ~/articles]ls*.xmlfile1.xml file2.xml file3.xml[carol@octarine ~/articles]ls*.xml>list[carol@octarine ~/articles]foriin`cat list`; do cp"$i" "$i".bak; done[carol@octarine ~/articles]ls*.xml*file1.xml file1.xml.bak file2.xml file2.xml.bak file3.xml file3.xml.bak
Celui-ci liste les fichiers de /sbin qui sont des purs fichiers texte, et donc peut-être des scripts:
for i in `ls /sbin`; do file /sbin/$i | grep ASCII; done
Ce qui suit est un script spécifique pour convertir des fichiers HTML respectant un certain schéma en fichiers PHP. La conversion est faite en extrayant les 25 premières lignes et les 21 dernières, les remplaçant par 2 étiquettes PHP qui correspondent aux lignes d'entête et d'empied:
[carol@octarine ~/html]cathtml2php.sh#!/bin/bash # specific conversion script for my html files to php LIST="$(ls *.html)" for i in "$LIST"; do NEWNAME=$(ls "$i" | sed -e 's/html/php/') cat beginfile > "$NEWNAME" cat "$i" | sed -e '1,25d' | tac | sed -e '1,21d'| tac >> "$NEWNAME" cat endfile >> "$NEWNAME" done
Comme nous ne faisons pas un comptage des lignes, il n'y a pas moyen de savoir le numéro de ligne à laquelle commencer la suppression avant d'avoir atteint la fin. Cette difficulté est surmontée en utilisant tac, lequel inverse l'ordre des lignes dans un fichier.
La structure while permet une exécution répétitive d'une liste de commandes tant que la commande qui contrôle le while s'exécute avec succès (code retour égal à zéro). La syntaxe est:
while CONTROL-COMMAND; do CONSEQUENT-COMMANDS; done
CONTROL-COMMAND peut être toute commande(s) qui peut s'achever avec un statut de succès ou d'échec. Le CONSEQUENT-COMMANDS peut être tout programme, script ou bloc Shell.
Dès que CONTROL-COMMAND échoue, la boucle est arrêtée. Dans un script, la commande suivant l'instruction done est exécutée.
Le statut retourné est le statut d'exécution de la dernière commande de CONSEQUENT-COMMANDS ou zéro si aucune n'est exécutée
Voici un exemple pour les impatients:
#!/bin/bash # Le script ouvre 4 fenêtres de terminal. i="0" while [ $i -lt 4 ] do xterm & i=$[$i+1] done
L'exemple ci-dessous a été écrit pour copier des images qui sont prises par une WEBcam vers un répertoire WEB. Toute les 5 minutes une image est prise. Toute les heures, un nouveau répertoire est créé, pour contenir les images de cette heure. Chaque jour, un nouveau répertoire est créé contenant 24 sous-répertoires. Le script s'exécute en tâche de fond.
#!/bin/bash # Ce script copie les fichiers de mon répertoire racine dans le répertoire du serveur WEB. # (Utilisez des clés scp et SSH pour un répertoire distant) # Un nouveau répertoire est créé à chaque heure. PICSDIR=/home/carol/pics WEBDIR=/var/www/carol/webcam while true; do DATE=`date +%Y%m%d` HOUR=`date +%H` mkdir $WEBDIR/"$DATE" while [ $HOUR -ne "00" ]; do DESTDIR=$WEBDIR/"$DATE"/"$HOUR" mkdir "$DESTDIR" mv $PICDIR/*.jpg "$DESTDIR"/ sleep 3600 HOUR=`date +%H` done done
Notez l'emploi de l'instruction true. Il signifie: continuer l'exécution jusqu'à une interruption forcée (avec kill ou Ctrl+C).
Ce petit script peut être utilisé pour des tests de simulation; il génère des fichiers:
#!/bin/bash # Ce script génère un fichier toute les 5 minutes while true; do touch pic-`date +%s`.jpg sleep 300 done
Notez l'emploi de la commande date pour générer toute sorte de nom de fichier et de répertoire. Voir les pages man pour plus de détails.
L'exemple précédent existe pour le besoin de la démonstration. Des contrôles réguliers peuvent être facilement faits avec l'outil système cron. Ne pas oublier de rediriger les sorties et les erreurs quand un script est utilisé par crontab!
Ce script peut être interrompu par l'usager quand une séquence Ctrl+C est frappée:
#!/bin/bash # Ce script vous apporte sagesse FORTUNE=/usr/games/fortune while true; do echo "Sur quel sujet voulez-vous un conseil?" cat << topics politique startrek noyau sports excusesbidon magie amour littérature drogues éducation topics echo echo -n "Faites votre choix: " read topic echo echo "Conseil gratuit sur le sujet $topic: " echo $FORTUNE $topic echo done
Un document intégré est utilisé pour présenter à l'usager les choix possibles. Et de nouveau le test true répète les commandes de la liste CONSEQUENT-COMMANDS encore et encore.
Ce script calcule la moyenne à partir de la saisie utilisateur, qui est testée avant d'être traitée: Si la saisie n'est pas dans un intervalle, un message est affiché. Si q est frappée la boucle est abandonnée:
#!/bin/bash
# Calcul de la moyenne d'une série de nombres.
SCORE="0"
AVERAGE="0"
SUM="0"
NUM="0"
while true; do
echo -n "Entrez votre score [0-100%] ('q' pour quitter): "; read SCORE;
if (("$SCORE" < "0")) || (("$SCORE" > "100")); then
echo "Soyez sérieux. Banal, essayer encore: "
elif [ "$SCORE" == "q" ]; then
echo "Evaluation moyenne: $AVERAGE%."
break
else
SUM=$[$SUM + $SCORE]
NUM=$[$NUM + 1]
AVERAGE=$[$SUM / $NUM]
fi
done
echo "Je quitte."
Remarquez que les variables sur les dernières lignes ne sont pas protégées pour pouvoir en faire des calculs.
La boucle until est très similaire à while, à part que elle s'exécute jusqu'à ce que TEST-COMMAND s'exécute avec succès. Tant que cette commande échoue, la boucle se poursuit. La syntaxe est la même que pour la boucle while:
until TEST-COMMAND; do CONSEQUENT-COMMANDS; done
Le statut retourné est le statut d'exécution de la dernière commande exécutée dans la liste CONSEQUENT-COMMANDS ou zéro si aucune n'est exécutée. TEST-COMMAND peut être toute commande qui peut s'achever avec un statut de succès ou d'échec, et CONSEQUENT-COMMANDS peut être toute commande UNIX, script ou bloc Shell.
Comme nous l'avons déjà expliqué auparavant; le « ; » peut être remplacé par un ou plus sauts de lignes quelque soit l'endroit où il apparaît.
Script amélioré de picturesort.sh (voir Section 2.2.2, « Des boucles while imbriquées »), qui teste l'espace disque disponible. Si pas assez d'espace disque disponible, supprimer les images des mois précédents:
#!/bin/bash
# Ce script copie les fichiers de mon répertoire racine dans le répertoire du serveur WEB.
# Un nouveau répertoire est créé à chaque heure.
# Si les images prennent trop de place, les plus anciennes sont supprimées.
while true; do
DISKFUL=$(df -h $WEBDIR | grep -v File | awk '{print $5 }' | cut -d "%" -f1 -)
until [ $DISKFUL -ge "90" ]; do
DATE=`date +%Y%m%d`
HOUR=`date +%H`
mkdir $WEBDIR/"$DATE"
while [ $HOUR -ne "00" ]; do
DESTDIR=$WEBDIR/"$DATE"/"$HOUR"
mkdir "$DESTDIR"
mv $PICDIR/*.jpg "$DESTDIR"/
sleep 3600
HOUR=`date +%H`
done
DISKFULL=$(df -h $WEBDIR | grep -v File | awk '{ print $5 }' | cut -d "%" -f1 -)
done
TOREMOVE=$(find $WEBDIR -type d -a -mtime +30)
for i in $TOREMOVE; do
rm -rf "$i";
done
done
Notez l'initialisation des variables HOUR et DISKFULL et l'emploi d'options avec ls et date afin d'obtenir une liste correcte pour TOREMOVE.
Plutôt que de contrôler une boucle en testant le résultat d'une commande ou la saisie utilisateur, vous pouvez spécifier un fichier depuis lequel l'entrée est lue pour contrôler la boucle. Dans un tel cas, read est souvent la commande de contrôle. Tant que des lignes sont entrées dans la boucle, les commandes de la boucle sont exécutées. Dès que toutes les lignes ont été lues la boucle s'arrête.
Parce que la structure de boucle est considérée comme étant une structure de commande (tel que while TEST-COMMAND; do CONSEQUENT-COMMANDS; done), la redirection doit apparaître après l'instruction done afin de respecter la syntaxe.
command < file
Ce genre de redirection convient aux autres types de boucles.
Dans l'exemple suivant, le résultat de la commande find est utilisé comme entrée de la commande read afin de contrôler une boucle while:
[carol@octarine ~/testdir]catarchiveoldstuff.sh#!/bin/bash # Ce script crée un sous-répertoire dans le répertoire courant où sont gardés les # fichiers supprimés. # Cela pourrait être adapté à cron (avec modifications) pour être exécuté # chaque semaine ou mois. ARCHIVENR=`date +%Y%m%d` DESTDIR="$PWD/archive-$ARCHIVENR" mkdir $DESTDIR find $PWD -type f -a -mtime +5 | while read file do gzip "$file"; mv "$file".gz "$DESTDIR" echo "$file archived" done
Les fichiers sont compressés avant d'être déplacés dans le répertoire d'archive.
L'instruction break est employée pour quitter la boucle en cours avant sa fin normale. Ceci peut être nécessaire quand vous ne savez pas à l'avance combien de fois la boucle devra s'exécuter, par exemple parce que elle est dépendante de la saisie utilisateur.
Cet exemple montre une boucle while qui peut être interrompue. Ceci est une version légèrement améliorée du script wisdom.sh de la Section 2.2.3, « Contrôle d'une boucle while avec des saisies au clavier ».
#!/bin/bash
# Ce script vous apporte sagesse
# Vous pouvez maintenant quitter d'une façon décente.
FORTUNE=/usr/games/fortune
while true; do
echo "Sur quel sujet voulez-vous un conseil?"
echo "1. politique"
echo "2. startrek"
echo "3. noyau"
echo "4. sports"
echo "5. excusesbidon"
echo "6. magie"
echo "7. amour"
echo "8. littérature"
echo "9. drogues"
echo "10. éducation"
echo
echo -n "Entrez votre choix, ou 0 pour quitter: "
read choice
echo
case $choice in
1)
$FORTUNE politique
;;
2)
$FORTUNE startrek
;;
3)
$FORTUNE noyau
;;
4)
echo "Le sport est une perte d'argent, d'énergie et de temps."
echo "Retournez à votre clavier."
echo -e "\t\t\t\t -- \"Unhealthy is my middle name\" Soggie."
;;
5)
$FORTUNE excusesbidon
;;
6)
$FORTUNE magie
;;
7)
$FORTUNE amour
;;
8)
$FORTUNE littérature
;;
9)
$FORTUNE drogues
;;
10)
$FORTUNE éducation
;;
0)
echo "OK, au revoir!"
break
;;
*)
echo "Ceci n'est pas un choix valide, taper un chiffre entre 0 et 10."
;;
esac
done
Mémorisez que break quitte la boucle, pas le script. Ceci se voit en ajoutant une commande echo à la fin du script. Cet echo sera aussi exécuté à la saisie qui provoque l'exécution du break (quand l'usager frappe « 0 »).
Dans les boucles imbriquées, break autorise la spécification de la boucle dont il faut sortir. Voir les pages Bash info pour plus de détails.
L'instruction continue repart à l'itération d'une boucle for, while, until ou select.
Quand elle est utilisée dans une boucle for la variable de contrôle prend la valeur de l'élément suivant de la liste. Quand elle est utilisée dans une structure while ou until à contrario, l'exécution repart avec la première commande de TEST-COMMAND en haut de la boucle.
Dans les exemples suivants, les noms de fichiers sont convertis en minuscule. Si la conversion n'est pas nécessaire, une instruction continue recommence l'exécution de la boucle. Ces commandes ne consomment pas trop de ressources systèmes, et la plupart du temps, un effet similaire peut être obtenu avec sed et awk. Cependant, il est utile de connaître ces structures pour l'exécution de travaux coûteux, cela ne serait sans doute pas nécessaire si les tests était positionnés aux bons endroits dans le script, en partageant les ressources systèmes.
[carol@octarine ~/test]cattolower.sh#!/bin/bash # Ce script convertit tous les noms de fichiers contenant des majuscules en nom de fichier contenant que des minuscules LIST="$(ls)" for name in "$LIST"; do if [[ "$name" != *[[:upper:]]* ]]; then continue fi ORIG="$name" NEW=`echo $name | tr 'A-Z' 'a-z'` mv "$ORIG" "$NEW" echo "nouveau nom pour $ORIG est $NEW" done
Ce script a au moins un inconvénient: il écrase les fichiers existants. L'option noclobber de Bash est seulement utile quand intervient des redirections. L'option -b de la commande mv offre plus de sécurité, mais seulement dans le cas de réécriture accidentelle, comme le montre ce test:
[carol@octarine ~/test]rm*[carol@octarine ~/test]touchtest Test TEST[carol@octarine ~/test]bash-xtolower.sh++ ls + LIST=test Test TEST + [[ test != *[[:upper:]]* ]] + continue + [[ Test != *[[:upper:]]* ]] + ORIG=Test ++ echo Test ++ tr A-Z a-z + NEW=test + mv -b Test test + echo 'nouveau nom pour Test est test' new name for Test is test + [[ TEST != *[[:upper:]]* ]] + ORIG=TEST ++ echo TEST ++ tr A-Z a-z + NEW=test + mv -b TEST test + echo 'nouveau nom pour TEST est test' nouveau nom pour TEST est test[carol@octarine ~/test]ls-a./ ../ test test~
tr fait parti du paquet textutils, il peut effectuer toute sorte de transformation de caractère.
La structure select permet la génération facile de menus. La syntaxe est assez similaire à celle de la boucle for:
select WORD [in LIST]; do RESPECTIVE-COMMANDS; done
LIST est interprété, ce qui génère une liste d'éléments. L'expansion est affichée sur le standard d'erreurs; chaque élément est précédé d'un numéro. Si in LIST est absent, les paramètres positionnels sont affichés, comme si in $@ avait été spécifié. LIST est affiché une fois seulement.
A l'affichage des tous les éléments, l'invite PS3 est affichée et une ligne du standard d'entrée est lue. Si la ligne consiste en un nombre qui correspond à un des éléments, la valeur de WORD est définie avec le nom de cet élément. Si la ligne est vide, les éléments et l'invite PS3 sont réaffichés. Si un caractère EOF (End Of File) est lu, la boucle se termine. Parce que la plupart des usagers n'ont pas idée de la combinaison de touches pour EOF, il est plus convivial d'ajouter une commande break comme l'un des éléments. Tout autre valeur issue de la ligne lue définira WORD comme une chaîne nulle.
La ligne lue est mémorisée dans la variable REPLY.
Les RESPECTIVE-COMMANDS sont exécutées après chaque choix valide jusqu'à ce que le nombre représentant le break soit lu. Ce qui fait quitter la boucle.
Voici un exemple très simple, mais comme vous le constatez, il n'est pas très convivial:
[carol@octarine testdir]catprivate.sh#!/bin/bash echo "Ce script peut mettre un accès privé à tout fichier de ce répertoire." echo "Entrez le numéro du fichier que vous voulez protéger:" select FILENAME in *; do echo "Vous avez sélectionné $FILENAME ($REPLY), il est maintenant accessible que par vous." chmod go-rwx "$FILENAME" done[carol@octarine testdir]./private.sh Ce script peut mettre un accès privé à tout fichier de ce répertoire. Entrez le numéro du fichier que vous voulez protéger: 1) archive-20030129 2) bash 3) private.sh #? 1 Vous avez sélectionné archive-20030129 (1) #?
Déclarer l'invite PS3 et ajouter la possibilité de quitter l'améliore:
#!/bin/bash
echo "Ce script peut mettre un accès privé à tout fichier de ce répertoire."
echo "Entrez le numéro du fichier que vous voulez protéger"
PS3="Votre choix: "
QUIT="QUITTER CE PROGRAMME - Je me sens plus en confiance là."
touch "$QUIT"
select FILENAME in *;
do
case $FILENAME in
"$QUIT")
echo "Fin."
break
;;
*)
echo "Vous avez sélectionné $FILENAME ($REPLY)"
chmod go-rwx "$FILENAME"
;;
esac
done
rm "$QUIT"
Toute instruction dans une structure select peut être un autre select ce qui autorise un(des) sous-menu(s) dans un menu.
Par défaut la variable PS3 n'est pas changée dans une boucle select imbriquée. Si vous voulez une invite différente dans le sous-menu, assurez-vous de la définir aux bons moments.
La commande shift est l'une des intégrées Bourne Shell qui est fournie par Bash. Cette commande prend un paramètre, un nombre. Les paramètres positionnels sont décalés sur la gauche le nombre de fois N. Les paramètres positionnels de N+1 à $# sont renommés avec les noms de variable $1 à $# - N+1.
Disons que nous avons une commande qui a 10 paramètres, et N vaut 4, alors $4 devient $1, $5 devient $2 et ainsi de suite. $10 devient $7 et les anciens$1, $2 et $3 sont éliminés.
Si N est zéro ou supérieur à $# les paramètres positionnels ne sont pas décalés (le nombre total de paramètres, voir Section 2.1.2, « Contrôle des paramètres de la ligne de commande »). Si N est absent, il est considéré valant 1. Le code renvoyé est zéro à moins que N soit supérieur à $# ou inférieur à zéro, sinon il est différent de zéro.
Une instruction shift est typiquement employée quand le nombre de paramètres d'une commande n'est pas connu par avance, par exemple quand l'usager peut donner autant de paramètres qu'il le souhaite. Dans de tels cas, les paramètres sont généralement traités dans une boucle while avec un test sur (( $# )). Ce test est vrai tant que le nombre de paramètres est supérieur à zéro. La variable $1 et l'instruction shift traite chaque paramètre. Le nombre de paramètres est diminué chaque fois que shift est exécutée et finalement devient zéro, sur quoi la boucle while s'arrête.
L'exemple suivant, cleanup.sh, emploie l'instruction shift pour traiter chaque fichier d'une liste générée par find:
#!/bin/bash
# Ce script peut éliminer des fichiers qui n'ont pas été accédés depuis plus de 365 jours.
USAGE="Utilisation: $0 dir1 dir2 dir3 ... dirN"
if [ "$#" == "0" ]; then
echo "$USAGE"
exit 1
fi
while (( "$#" )); do
if [[ "$(ls $1)" == "" ]]; then
echo "Répertoire vide, rien à faire."
else
find $1 -type f -a -atime +365 -exec rm -i {} \;
fi
shift
done
La commande find ci-dessus peut être remplacée par ce qui suit:
find options | xargs [commands_to_execute_on_found_files
La commande xargs construit et exécute des lignes de commandes depuis l'entrée standard. Ceci présente l'avantage que la ligne de commande est renseignée jusqu'à ce que la limite du système soit atteinte. Seulement à ce moment la commande à exécuter sera lancée, dans l'exemple ci-dessus ce serait rm. Si il y a plus de paramètres, une nouvelle ligne de commande sera utilisée, jusqu'à ce qu'elle soit elle aussi pleine ou jusqu'à ce qu'il n'y ait plus de paramètres. La même chose avec find -exec appelle la commande à exécuter sur chaque fichier trouvé. Donc, l'usage de xargs accélère grandement l'exécution des scripts et améliore les performances de votre machine.
Dans l'exemple suivant, nous avons modifié le script de la Section 2.4.4, « Les documents intégrés (NdT: here documents, que l'on appele aussi 'document lié') » afin qu'il accepte de multiples paquets à installer d'un coup:
#!/bin/bash
if [ $# -lt 1 ]; then
echo "Utilisation: $0 package(s)"
exit 1
fi
while (($#)); do
yum install $1 << CONFIRM
y
CONFIRM
shift
done
Dans ce chapitre, nous avons vu comment les commandes répétitives peuvent être incorporées dans des structures de boucles. La plupart des boucles sont construites avec for, while ou until ou une combinaison de ces commandes. La boucle for exécute une tâche un nombre défini de fois. Si vous ne savez pas combien de fois une commande devrait s'exécuter, employez plutôt until ou while pour spécifier quand la boucle devrait s'arrêter.
Les boucles peuvent être interrompues ou réitérée avec break et continue.
Un fichier peut être utilisé comme entrée pour une boucle qui a un opérateur de redirection, les boucles peuvent aussi lire les résultats de commandes qui sont fournis à la boucle par un tube.
La structure select est employée pour afficher des menus dans les scripts interactifs. Le décalage des paramètres d'une ligne de commande peut être fait avec shift.
Rappelez-vous: quand vous écrivez des scripts, travaillez par étapes et les tester avant de les incorporer à votre script.
Créer un script qui fera une copie (récursive) des fichiers dans /etc de sorte qu'un administrateur système débutant puisse les éditer sans craintes.
Ecrire un script qui prendra exactement un paramètre, le nom d'un répertoire. Si le nombre de paramètres est supérieur ou inférieur à 1, afficher le message d'utilisation. Si l'argument n'est pas un répertoire, afficher un autre message. Pour le répertoire donné, afficher les 5 fichiers les plus gros et les 5 fichiers le plus récemment modifiés.
Pouvez-vous expliquer pourquoi il est si important de mettre les variables entre guillemets dans l'exemple de la Section 4.2, « Redirection des sorties »?
Ecrivez un script similaire à celui de la Section 5.1, « L'intégrée break », mais pensez à un moyen de quitter après que l'usager ait effectué 3 boucles.
Penser à une meilleur solution que move -b pour le script de la Section 5.3, « Exemples » pour éviter d'écraser les fichiers existants. Par exemple, tester si un fichier existe ou pas. Ne faites pas de travail inutile!
Réécrire le script whichdaemon.sh de la Section 2.4, « Opérations booléennes », de sorte qu'il:
Affiche une liste des serveurs à contrôler, tel que Apache, le serveur SSH, le démon NTP, un démon nom, un démon d'administration, etc.
Pour chaque choix l'utilisateur peut afficher des informations importantes, telles que le nom du serveur WEB, les informations de trace NTP, etc.
Optionnellement, prévoir une possibilité pour les utilisateurs de contrôler d'autres serveurs que ceux listés. Dans de tels cas, vérifiez que au moins le processus en question tourne.
Revoir les scripts de la Section 2.2.4, « Calcul d'une moyenne ». Remarquez comment les caractères entrés autre que q sont traités. Réécrire ce script afin qu'il affiche un message si des caractères sont saisis.