======================= Bash Scripting 03 HOWTO (c) Nicolas Kovacs ======================= Dernière révision : 3 novembre 2015 Voici la troisième partie du HOWTO qui présente les fonctionnalités de base de la programmation shell. * La structure de contrôle 'if' * La structure de contrôle 'case' * La boucle 'for' * La boucle 'while' La structure de contrôle 'if' ----------------------------- La structure de contrôle 'if' permet de réaliser des tests. La commande derrière le mot 'if' est exécutée. En fonction du code renvoyé par celle-ci, le shell oriente le flux d'exécution dans la partie 'then' si la commande a renvoyé 0 (vrai), dans la partie 'else' si la commande a renvoyé 1 (faux). Si la commande renvoie 1 (faux) et qu'il n'y a pas de partie 'else', le flux d'exécution se poursuit à la première commande située sous le 'fi'. Première syntaxe : if commande1 then commande2 commande3 ... else commande4 ... fi Le mot clé 'then' peut être placé sur la première ligne à condition d'utiliser un point-virgule ";" pour le séparer de la commande : if commande1; then commande2 commande3 ... else commande4 ... fi La partie 'else' est factultative : if commande1; then commande2 commande3 ... fi Il est possible d'utiliser le mot clé 'elif' qui signifie "sinon si" : if commande1; then commande2 elif commande3; then commande4 elif commande5; then commande6 fi Le script 'exist_user.sh' reçoit en argument un nom d'utilisateur. Le script affiche à l'écran si l'utilisateur est défini ou non sur le système. $ nl exist_user.sh 1 #!/bin/bash 2 if [ $# -ne 1 ]; then 3 echo "Mauvais nombre d'arguments." 4 echo "Usage : $0 nom_user" 5 exit 1 6 fi 7 if grep -q "^$1:" /etc/passwd; then 8 echo "L'utilisateur $1 est défini sur le système." 9 else 10 echo "L'utilisateur $1 n'est pas défini sur le système." 11 fi Exécution du script : $ ./exist_user.sh ftp L'utilisateur ftp est défini sur le système. $ ./exist_user.sh kikinovak L'utilisateur kikinovak est défini sur le système. $ ./exist_user.sh jktartempion L'utilisateur jktartempion n'est pas défini sur le système. Le script 'codepos.sh' demande à l'utilisateur de saisir un code postal et teste si ce dernier est valide. $ nl codepos.sh 1 #!/bin/bash 2 # 3 # Script pour tester la validité d'un code postal 4 echo -e "Entrez un code postal : \c" 5 read cp 6 if [[ $cp = 75[0-9][0-9][0-9] ]]; then 7 echo "$cp est un code postal parisien." 8 elif [[ $cp = @(7[78]|9[1-5])[0-9][0-9][0-9] ]]; then 9 echo "$cp est un code postal de la région parisienne." 10 elif [[ $cp = @([0-9][0-9][0-9][0-9][0-9]) ]]; then 11 echo "$cp est un code postal de province (Métropole)." 12 else 13 echo "$cp n'est pas un code postal de France Métropolitaine." 14 fi 15 exit 0 Exécution : $ ./codepos.sh Entrez un code postal : 75020 75020 est un code postal parisien. $ ./codepos.sh Entrez un code postal : 95851 95851 est un code postal de la région parisienne. $ ./codepos.sh Entrez un code postal : 89000 89000 est un code postal de province (Métropole). $ ./codepos.sh Entrez un code postal : 789659 789659 n'est pas un code postal de France Métropolitaine. $ ./codepos.sh Entrez un code postal : asdf45 asdf45 n'est pas un code postal de France Métropolitaine. La structure de contrôle 'case' ------------------------------- La structure de contrôle 'case' permet de comparer une variable à différentes valeurs ou modèles. Lorsque le test s'y prête, elle remplace avantageusement le 'if-elif', car elle est beaucoup plus lisible. Syntaxe : case $variable in modele1) commande ... ;; modele2) commande ... ;; modele3 | modele4 | modele5) commande ... ;; esac Le shell compare la valeur de la variable à chacun des modèles cités, l'évaluation étant faite de haut en bas. Lorsque la valeur correspond à l'un des modèles, les commandes relatives à ce dernier sont exécutées. Les caractères ";;" représentent la fin du traitement et permettent de sortir du 'case'. Dans ce cas, la prochaine commande exécutée est celle située en-dessous de 'esac'. Les modèles sont des chaînes de caractères pouvant inclure des caractères spéciaux. Le caractère "|" permet d'exprimer l'alternative entre plusieurs modèles. Le script 'menu.sh' affiche un menu, saisit le choix de l'utilisateur et teste la validité de ce choix à l'aide d'une structure 'case'. En ligne 18, le caractère "*" signifie "toute autre chaîne de caractères". $ nl menu.sh 1 #!/bin/bash 2 echo "- 1 - Sauvegarde " 3 echo "- 2 - Restauration " 4 echo "- 3 - Fin " 5 echo -e "Votre choix : \c" 6 read reponse 7 case "$reponse" in 8 1) echo "Vous avez choisi la sauvegarde." 9 # Lancement de la sauvegarde. 10 ;; 11 2) echo "Vous avez choisi la restauration." 12 # Lancement de la restauration. 13 ;; 14 3) echo "Fin du traitement." 15 echo "À bientôt..." 16 exit 0 17 ;; 18 *) echo "Saisie incorrecte." 19 echo "Bye..." 20 exit 1 21 ;; 22 esac Exécution du script : $ ./menu.sh - 1 - Sauvegarde - 2 - Restauration - 3 - Fin Votre choix : 1 Vous avez choisi la sauvegarde. $ ./menu.sh - 1 - Sauvegarde - 2 - Restauration - 3 - Fin Votre choix : 2 Vous avez choisi la restauration. $ ./menu.sh - 1 - Sauvegarde - 2 - Restauration - 3 - Fin Votre choix : 3 Fin du traitement. À bientôt... $ ./menu.sh - 1 - Sauvegarde - 2 - Restauration - 3 - Fin Votre choix : 4 Saisie incorrecte. Bye... Le script 'codepos.sh' demande la saisie d'un code postal et teste la validité de ce dernier : $ nl codepos.sh 1 #!/bin/bash 2 # 3 # Script pour tester la validité d'un code postal 4 echo -e "Entrez un code postal : \c" 5 read cp 6 case "$cp" in 7 75[0-9][0-9][0-9] ) 8 echo "$cp est un code postal parisien." 9 ;; 10 7[78][0-9][0-9][0-9] | 9[1-5][0-9][0-9][0-9] ) 11 echo "$cp est un code postal de la région parisienne." 12 ;; 13 [0-9][0-9][0-9][0-9][0-9] ) 14 echo "$cp est un code postal de province (Métropole)." 15 ;; 16 * ) 17 echo "$cp n'est pas un code postal de France Métropolitaine." 18 ;; 19 esac 20 exit 0 Exécution : $ ./codepos.sh Entrez un code postal : 75020 75020 est un code postal parisien. $ ./codepos.sh Entrez un code postal : 78900 78900 est un code postal de la région parisienne. $ ./codepos.sh Entrez un code postal : 30730 30730 est un code postal de province (Métropole). $ ./codepos.sh Entrez un code postal : 123456 123456 n'est pas un code postal de France Métropolitaine. $ ./codepos.sh Entrez un code postal : asdf asdf n'est pas un code postal de France Métropolitaine. Le script 'testnombre.sh' demande la saisie d'un nombre et teste la validité de ce dernier. Notez que le script utilise des expressions complexes. $ nl testnombre.sh 1 #!/bin/bash 2 shopt -s extglob 3 echo -e "Saisir un nombre : \c" 4 read nombre 5 case "$nombre" in 6 ?(+)+([0-9]) ) 7 echo "$nombre est un nombre entier positif." 8 ;; 9 -+([0-9]) ) 10 echo "$nombre est un nombre entier négatif." 11 ;; 12 ?(+)+([0-9]).+([0-9]) ) 13 echo "$nombre est un nombre flottant positif." 14 ;; 15 -+([0-9]).+([0-9]) ) 16 echo "$nombre est un nombre flottant négatif." 17 ;; 18 * ) 19 echo "$nombre n'est pas un nombre." 20 exit 1 21 ;; 22 esac Exécution : $ ./testnombre.sh Saisir un nombre : 34 34 est un nombre entier positif. $ ./testnombre.sh Saisir un nombre : +89 +89 est un nombre entier positif. $ ./testnombre.sh Saisir un nombre : -15 -15 est un nombre entier négatif. $ ./testnombre.sh Saisir un nombre : 3.2 3.2 est un nombre flottant positif. $ ./testnombre.sh Saisir un nombre : 58.17 58.17 est un nombre flottant positif. $ ./testnombre.sh Saisir un nombre : -4.231 -4.231 est un nombre flottant négatif. La boucle 'for' --------------- La structure de contrôle 'for' permet de traiter une liste de valeurs représentée par "val1", "val2", "val3"... "valn". À chaque tour de boucle, la variable "var" est initialisée avec l'une des valeurs de la liste. Les valeurs sont traitées dans l'ordre de leur énumération. La liste des valeurs peut être citée directement ou générée par substitution de caractères spéciaux du shell. Liste de valeurs citée directement : for var in val1 val2 val3 ... valn ; do commande ... done Liste de valeurs générée par substitution de variable : for var in $variable ; do commande ... done Liste de valeurs générée par substitution de commande : for var in $(commande) ; do commande ... done Liste de valeurs générée par substitution de caractères : for var in *.c ; do commande ... done La liste par défaut correspond aux arguments de la ligne de commande : for var ; do commande ... done Cette dernière boucle équivaut à : for var in $* ; do commande ... done La liste de valeurs du script 'rebours.sh' est citée directement : $ nl rebours.sh 1 #!/bin/bash 2 # 3 # Compte à rebours 4 for rebours in 4 3 2 1 "Feu !" ; do 5 sleep 1 6 echo "$rebours" 7 done 8 exit 0 Exécution : $ ./rebours.sh 4 3 2 1 Feu ! Le script 'testfichier.sh' prend une liste de noms de fichiers en argument et donne une indication à l'utilisateur sur le type du fichier. La liste de valeurs est générée par substitution de la variable "$*". $ nl testfichier.sh 1 #!/bin/bash 2 # 3 echo -e "Liste des arguments reçus : \n$*\n" 4 for fichier in $* ; do # équivalent à "for fichier" 5 if [ -f $fichier ] ; then 6 echo "$fichier est un fichier ordinaire." 7 elif [ -d $fichier ] ; then 8 echo "$fichier est un répertoire." 9 elif [ -e $fichier ] ; then 10 echo "$fichier n'est ni un fichier ni un répertoire." 11 else 12 echo "$fichier n'existe pas." 13 fi 14 done 15 exit 0 Exécution : $ ./testfichier.sh /etc /etc/passwd /dev/console /backup Liste des arguments reçus : /etc /etc/passwd /dev/console /backup /etc est un répertoire. /etc/passwd est un fichier ordinaire. /dev/console n'est ni un fichier ni un répertoire. /backup n'existe pas. Le script 'traituser.sh' traite tous les utilisateurs définis dans le fichier '/etc/passwd' dont le nom commence par "compta". La liste des valeurs est générée à partir d'une substitution de commande. $ nl traituser.sh 1 #!/bin/bash 2 # 3 for user in $(cut -d":" -f1 /etc/passwd | grep "^compta") ; do 4 echo "Traitement de l'utilisateur : $user" 5 done 6 exit 0 Exécution : $ ./traituser.sh Traitement de l'utilisateur : compta01 Traitement de l'utilisateur : compta02 Traitement de l'utilisateur : compta03 Traitement de l'utilisateur : compta04 La boucle 'while' ----------------- La structure de contrôle 'while' permet de boucler tant qu'une commande retourne le code 0 (vrai). À chaque tour de boucle, la commande spécifiée derrière le mot 'while' est exécutée. Lorsque celle-ci renvoie un code "vrai", le shell exécute les commandes situées entre le 'do' et le 'done'. puis remonte au 'while' pour exécuter de nouveau la commande. Si cette dernière renvoie "faux", le shell sort du 'while' et exécute la commande située immédiatement derrière le 'done'. while commande1 ; do commande2 done Le script 'somme.sh' affiche à l'écran la somme des nombres saisis. $ nl somme.sh 1 #!/bin/bash 2 shopt -s extglob 3 somme=0 4 echo "Saisir un nombre par ligne, [Ctrl]+[D] pour afficher la somme :" 5 while read nombre ; do 6 if [[ "$nombre" != ?([+-])+([0-9]) ]] ; then 7 echo "La valeur saisie n'est pas un nombre." 8 continue 9 fi 10 ((somme+=nombre)) 11 done 12 echo "Somme : $somme" 13 exit 0 /!\ La commande interne 'continue' permet de remonter immédiatement au 'while'. Elle est détaillée un peu plus loin. Exécution : $ ./somme.sh Saisir un nombre par ligne, [Ctrl]+[D] pour afficher la somme : 1 3 a La valeur saisie n'est pas un nombre. 4 Somme : 8 Le shell propose la commande interne ':' dont l'intérêt réside dans le fait qu'elle renvoie toujours 0 (vrai). Placée derrière un 'while', elle permet de fabriquer une boucle infinie. $ type : : est une primitive du shell $ echo $? 0 Le shell Bash propose également la commande interne 'true' équivalente à la commande ':', dont le mérite est d'avoir un nom plus explicite. Les plates-formes Unix proposent également la commande '/usr/bin/true' ou '/bin/true'. $ type -a true true est une primitive du shell true est /usr/bin/true true est /bin/true $ ls -l /usr/bin/true lrwxrwxrwx 1 root root 14 oct. 18 13:15 /usr/bin/true -> ../../bin/true Syntaxe d'une boucle infinie : while true ; do ... done Le script 'menuboucle.sh' reprend le programme 'menu.sh' un peu plus haut. L'affichage et le traitement du menu sont inclus dans une boucle infinie, ce qui permet de réafficher le menu après chaque traitement (sauf le cas de sortie). Pour que l'utilisateur ait le temps de visualiser les affichages écran, le script utilise la commande 'read x', qui provoque une pause écran en attendant que l'utilisateur appuie sur la touche [Entrée]. Le contenu de la variable "x" ne sera pas exploité. $ nl menuboucle.sh 1 #!/bin/bash 2 while true ; do 3 echo 4 echo "----------------" 5 echo "- 1 - Sauvegarde" 6 echo "- 2 - Restauration" 7 echo "- 3 - Fin" 8 echo -e "Votre choix : \c" 9 read reponse 10 case "$reponse" in 11 1) echo -e "Vous avez choisi la sauvegarde. \c" 12 # Lancement de la sauvegarde. 13 read x 14 ;; 15 2) echo -e "Vous avez choisi la restauration. \c" 16 # Lancement de la restauration. 17 read x 18 ;; 19 3) echo "Fin du traitement." 20 echo "À bientôt..." 21 exit 0 22 ;; 23 *) echo -e "Saisie incorrecte. \c" 24 read x 25 ;; 26 esac 27 done Exécution : $ ./menuboucle.sh ---------------- - 1 - Sauvegarde - 2 - Restauration - 3 - Fin Votre choix : 1 Vous avez choisi la sauvegarde. ---------------- - 1 - Sauvegarde - 2 - Restauration - 3 - Fin Votre choix : 2 Vous avez choisi la restauration. ---------------- - 1 - Sauvegarde - 2 - Restauration - 3 - Fin Votre choix : 4 Saisie incorrecte. ---------------- - 1 - Sauvegarde - 2 - Restauration - 3 - Fin Votre choix : 3 Fin du traitement. À bientôt... ------------------------------------------------------------------------------ # vim: syntax=txt