Les variables $* et $@ contiennent la liste des arguments d'un script shell.
Lorsqu'elles ne sont pas entourées par des guillemets, elles sont équivalentes.
Exemple :
$ nl scr01.sh
1 #!/bin/bash
2 cpt=1
3 echo "Utilisation de la variable \$*"
4 for arg in $*
5 do
6 echo "Argument $cpt : $arg"
7 ((cpt+=1))
8 done
9 cpt=1
10 echo "Utilisation de la variable \$@"
11 for arg in $@
12 do
13 echo "Argument $cpt : $arg"
14 ((cpt+=1))
15 done
16 exit 0
$ ./scr01.sh a b "c d e" f
Utilisation de la variable $*
Argument 1 : a
Argument 2 : b
Argument 3 : c
Argument 4 : d
Argument 5 : e
Argument 6 : f
Utilisation de la variable $@
Argument 1 : a
Argument 2 : b
Argument 3 : c
Argument 4 : d
Argument 5 : e
Argument 6 : f
$
Interprétation :
Les guillemets autour de $* supprime la signification des espaces contenus dans $*.
Exemple :
$ nl scr02.sh
1 #!/bin/bash
2 cpt=1
3 echo "Utilisation de la variable \"\$*\""
4 for arg in "$*"
5 do
6 echo "Argument $cpt : $arg"
7 ((cpt+=1))
8 done
9 exit 0
$ ./scr02.sh a b c "d e f" g
Utilisation de la variable "$*"
Argument 1 : a b c d e f g
$
Interprétation :
La variable $@ placée entre guillemets permet de conserver la protection des arguments par les guillemets.
Exemple :
$ nl scr03.sh
1 #!/bin/bash
2 cpt=1
3 echo "Utilisation de la variable \"\$@\""
4 for arg in "$@"
5 do
6 echo "Argument $cpt : $arg"
7 ((cpt+=1))
8 done
9 exit 0
$ ./scr03.sh a b c "d e f" g
Utilisation de la variable "$@"
Argument 1 : a
Argument 2 : b
Argument 3 : c
Argument 4 : d e f
Argument 5 : g
$
Interprétation :
Les shells BASH et KSH offrent des fonctionnalités supplémentaires au niveau des substitution de variables.
Syntaxe :
${#variable}
Exemple :
$ variable="Une chaine de texte"
$ echo $variable
Une chaine de texte
$ echo "La chaine contient ${#variable} caractères"
La chaine contient 19 caractères
$
Exemple avec la variable suivante :
$ variable="col1|col2|col3|col4|col5"
$ echo $variable
col1|col2|col3|col4|col5
$
Cette variable est constituée de 5 segments délimités par le caractère |
Le contenu de la variable n'est jamais modifié
Syntaxe :
${variable#modele}
modele est une chaine de caractère incluant les caractères spéciaux *, ?, [ ], ?(expression), +(expression), *(expression), @(expression), !(expression)
Rappel des expressions pour la substitution des noms de fichiers
Le caractère # signifie "Chaine la plus courte possible en partant de la gauche"
Exemple :
$ echo ${variable#*|}
col2|col3|col4|col5
$
Syntaxe :
${variable##modele}
Les caractères ## signifient "Chaine la plus longue possible en partant de la gauche"
Exemple :
$ echo ${variable##*|}
col5
$
Syntaxe :
${variable%modele}
Le caractère % signifie "Chaine la plus courte possible en partant de la droite"
Exemple :
$ echo ${variable%|*}
col1|col2|col3|col4
$
Syntaxe :
${variable%%modele}
Les caractères %% signifient "Chaine la plus longue possible en partant de la droite"
Exemple :
$ echo ${variable%%|*}
col1
$
En bash, ne pas oublier d'activer l'option extglob avec la commande shopt -s extglob afin de permettre au shell d'interpréter les modèles utilisant les expressions complexes.
Avec les shells récents, il est possible d'utiliser des tableaux à 1 dimension.
Les éléments du tableau commencent toujours à l'indice 0.
Syntaxe :
tableau[indice]=valeur
Exemple :
$ tableau[0]=15
$ tableau[3]=20
$
Les indices non initialisés sont vides.
Syntaxe :
${tableau[indice]}
Exemple :
$ echo ${tableau[3]}
20
$ echo ${tableau[0]}
15
$ echo ${tableau[2]}
$
L'indice 2 du tableau est vide.
Syntaxe :
tableau=(valeur1 valeur2 valeur3 ..... valeurn)
Exemple :
$ tableau=(02 40 35 68 98 45 52 03)
$ echo ${tableau[0]}
02
$ echo ${tableau[2]}
35
$
Toutes les précédentes valeurs contenues dans le tableaux sont effacées.
Syntaxe :
${tableau[*]}
Exemple :
$ echo ${tableau[*]}
02 40 35 68 98 45 52 03
$
Syntaxe :
${#tableau[*]}
Exemple :
$ echo ${#tableau[*]}
8
$
Syntaxe :
${#tableau[indice]}
Exemple :
$ echo ${#tableau[0]}
2
$
$ tab=("un" "deux" "trois")
$ for m in ${tab[@]}; do echo $m; done
un
deux
trois
$
La commande set utilisée sans option mais suivie d'arguments affecte ces derniers aux paramètres positionnels $1, $2, $3 ....., $*, $@ et $#.
Cela permet de manipuler plus facilement le résultat de diverses substitutions.
Exemple :
$ ls
fichier_0 fichier_2 fichier_4 fichier_6 fichier_8
fichier_1 fichier_3 fichier_5 fichier_7 fichier_9
$ set `ls`
$
La liste des fichiers obtenue avec la commande ls est maintenant affecté aux paramètres positionnels.
$ echo $#
10
$ echo $*
fichier_0 fichier_1 fichier_2 fichier_3 fichier_4 fichier_5 fichier_6 fichier_7 fichier_8 fichier_9
$ echo $1
fichier_0
$ echo $3
fichier_2
$ echo $9
fichier_8
$ echo ${10}
fichier_9
$
Les fonctions permettent de regrouper et d'exécuter des commandes à différents endroits d'un script.
Cela permet de créer des fonctions personnalisées réutilisables.
Une fonction doit être définie au début d'un script, avant sa première utilisation.
Il existe 2 syntaxes permettant de définir une fonction.
Première syntaxe :
Ce sont les doubles parenthèses ( ) qui indique au shell la définition d'une fonction
Définition d'une fonction
maFonction () {
commande1
commande2
.....
}
Appel d'une fonction
maFonction
Seconde syntaxe :
Le mot clé function remplace les doubles parenthèses ( )
Définition d'une fonction
function maFonction {
commande1
commande2
.....
}
Appel d'une fonction
maFonction
Une fonction peut être appelée aussi bien à partir du programme principal qu'à partir d'une autre fonction.
Exemple :
$ nl fonction01.sh
1 #!/bin/bash
2 fctn01 () {
3 echo "Fonction fctn01"
4 }
5 function fctn02 {
6 echo "Fonction fctn02"
7 }
8 echo "Début du programme principal"
9 echo "Appel de la fonction fctn01"
10 fctn01
11 echo "Appel de la fonction fctn02"
12 fctn02
13 echo "Fin du programme principal"
14 exit 0
$ ./fonction01.sh
Début du programme principal
Appel de la fonction fctn01
Fonction fctn01
Appel de la fonction fctn02
Fonction fctn02
Fin du programme principal
$
Dès qu'une fonction est définie, celle-ci est considérée par le shell comme étant une commande interne.
Comme toutes commandes Linux, une fonction retourne également un code d'erreur.
Si le code erreur n'est pas spécifié, celui retourné par défaut correspond au code erreur de la dernière commande exécutée dans la fonction.
La commande return permet de retourner le code erreur de la fonction concernée. Ce code doit obligatoirement correspondre à un nombre compris entre 0 et 255.
Le code erreur retourné par la fonction est récupérable grâce à la variable $?.
Exemple :
Le script suivant test si l'utilisateur saisi existe sur le système.
$ nl fonction02.sh
1 #!/bin/bash
2 function pause {
3 echo "Appuyer sur Entrée pour continuer"
4 read x
5 }
6 function existUser {
7 echo -e "Saisir le nom d'un utilisateur : \c"
8 read user
9 if grep -q "^$user:" /etc/passwd ; then
10 return 0
11 fi
12 return 1
13 }
14 while true
15 do
16 clear
17 echo "- 1 - Savoir si un utilisateur existe"
18 echo "- 2 - Connaitre l'UID d'un utilisateur"
19 echo "- 3 - Fin"
20 echo -e "Votre choix : \c"
21 read choix
22 case $choix in
23 1) if existUser
24 then
25 echo "L'utilisateur $user existe"
26 else
27 echo "l'utilisateur $user n'existe pas"
28 fi
29 ;;
30 2) echo "Option non disponible"
31 ;;
32 3) exit 0
33 ;;
34 esac
35 pause
36 done
$
Dans un script shell, sans définition particulière, toutes les variables utilisées sont globales à tout le script.
Qu'une variable soit définie au niveau du programme principal ou d'une fonction, elle est accessible n'importe où dans le script.
Par exemple, dans le script fonction02.sh, la variable $user est initialisée dans la fonction existUser (ligne 8) et utilisée également au niveau du programme principal (ligne 25 et 27).
$ nl fonction02.sh
1 #!/bin/bash
2 function pause {
3 echo "Appuyer sur Entrée pour continuer"
4 read x
5 }
6 function existUser {
7 echo -e "Saisir le nom d'un utilisateur : \c"
8 read user
9 if grep -q "^$user:" /etc/passwd ; then
10 return 0
11 fi
12 return 1
13 }
14 while true
15 do
16 clear
17 echo "- 1 - Savoir si un utilisateur existe"
18 echo "- 2 - Connaitre l'UID d'un utilisateur"
19 echo "- 3 - Fin"
20 echo -e "Votre choix : \c"
21 read choix
22 case $choix in
23 1) if existUser
24 then
25 echo "L'utilisateur $user existe"
26 else
27 echo "l'utilisateur $user n'existe pas"
28 fi
29 ;;
30 2) echo "Option non disponible"
31 ;;
32 3) exit 0
33 ;;
34 esac
35 pause
36 done
$
La commande typeset permet de définir des variables locales à une fonction.
Syntaxe :
typeset variable
typeset variable=valeur
Exemple :
$ nl fonction03.sh
1 #!/bin/bash
2 function f1 {
3 # var1 est une variable locale
4 typeset var1
5 echo "Dans la fonction f1 => var1 avant : $var1"
6 var1=100
7 echo "Dans la fonction f1 => var1 après : $var1"
8 echo "Dans la fonction f1 => var2 avant : $var2"
9 var2=200
10 echo "Dans la fonction f1 => var2 après : $var2"
11 }
12 # var1 et var2 sont des variables globales
13 var1=1
14 var2=2
15 echo "Dans le programme principal => var1 avant appel f1 : $var1"
16 echo "Dans le programme principal => var2 avant appel f1 : $var2"
17 f1
18 echo "Dans le programme principal => var1 après appel f1 : $var1"
19 echo "Dans le programme principal => var2 après appel f1 : $var2"
20 exit 0
$ ./fonction03.sh
Dans le programme principal => var1 avant appel f1 : 1
Dans le programme principal => var2 avant appel f1 : 2
Dans la fonction f1 => var1 avant :
Dans la fonction f1 => var1 après : 100
Dans la fonction f1 => var2 avant : 2
Dans la fonction f1 => var2 après : 200
Dans le programme principal => var1 après appel f1 : 1
Dans le programme principal => var2 après appel f1 : 200
$
2 variables globales var1 et var2 sont définies et initialisées ligne 13 et 14.
1 variable locale var1 est définie dans la fonction ligne 4 et initialisée à 100 ligne 6.
Après exécution de la fonction f1, la variable globale var1 a conservée sa valeur (1) alors que la variable globale var2 a été modifiée.
Dans un script shell, il est tout à fait possible de passer des arguments à une fonction étant donné qu'une fonction est reconnue par le shell comme étant une commande à part entière.
Ces arguments sont récupérables dans les fonctions grâce aux variables spéciales $1, $2, $3, ....., ${10} ......$*, $@ et $#. Ces variables sont aussi locales aux fonctions.
Par contre, la variable $0 contient toujours le nom du script.
Exemple :
$ nl fonction04.sh
1 #!/bin/bash
2 function f1 {
3 echo "Arguments de la fonction f1 :"
4 echo "\$0 => $0"
5 echo "\$1 => $1"
6 echo "\$2 => $2"
7 echo "\$3 => $3"
8 echo "\$* => $*"
9 echo "\$# => $#"
10 }
11 function f2 {
12 echo "Arguments de la fonction f2 :"
13 echo "\$0 => $0"
14 echo "\$1 => $1"
15 echo "\$2 => $2"
16 echo "\$3 => $3"
17 echo "\$* => $*"
18 echo "\$# => $#"
19 }
20 function f3 {
21 echo "Arguments de la fonction f3 :"
22 echo "\$0 => $0"
23 echo "\$1 => $1"
24 echo "\$2 => $2"
25 echo "\$3 => $3"
26 echo "\$* => $*"
27 echo "\$# => $#"
28 }
29 echo "Arguments du programme principal :"
30 echo "\$0 => $0"
31 echo "\$1 => $1"
32 echo "\$2 => $2"
33 echo "\$3 => $3"
34 echo "\$* => $*"
35 echo "\$# => $#"
36 # Appel de la fonction f1 avec 3 arguments
37 f1 a b c
38 # Appel de la fonction f2 avec 3 arguments
39 f2 file.c 2000 500
40 # Appel de la fonction f3 avec 2 arguments provenant du programme principal
41 f3 $2 $3
42 exit 0
$
Appel du script fonction04.sh avec 3 arguments :
$ ./fonction04.sh arg1 arg2 arg3
Arguments du programme principal :
$0 => ./fonction04.sh
$1 => arg1
$2 => arg2
$3 => arg3
$* => arg1 arg2 arg3
$# => 3
Arguments de la fonction f1 :
$0 => ./fonction04.sh
$1 => a
$2 => b
$3 => c
$* => a b c
$# => 3
Arguments de la fonction f2 :
$0 => ./fonction04.sh
$1 => file.c
$2 => 2000
$3 => 500
$* => file.c 2000 500
$# => 3
Arguments de la fonction f3 :
$0 => ./fonction04.sh
$1 => arg2
$2 => arg3
$3 =>
$* => arg2 arg3
$# => 2
$
Comme n'importe quelle commande renvoyant un résultat, une fonction peut également être placée à l'intérieur de caractères de substitution de commande `` ou $( ).
Exemple :
$ nl fonction05.sh
1 #!/bin/bash
2 function getUid {
3 grep "^$1:" /etc/passwd | cut -d':' -f3
4 }
5 # Initialisation de la variable globale uid
6 uid=""
7 # Appel de la fonction getUid avec l'argument du programme principal
8 # Juste pour l'affichage
9 getUid $1
10 # Affectation du résultat de la fonction getUid à la variable uid
11 uid=$(getUid $1)
12 if [[ $uid != "" ]]
13 then
14 echo "L'utilisateur $1 a pour UID : $uid"
15 else
16 echo "L'utilisateur $1 n'existe pas"
17 fi
18 exit 0
$ ./fonction05.sh root
0
L'utilisateur root a pour UID : 0
$
Exemple d'un script reprenant toutes les commandes et fonctions vues précédement
$ nl fonction06.sh
1 #!/bin/bash
2 # Pour faire une pause
3 function pause {
4 echo "Appuyer sur Entrée pour continuer"
5 read x
6 }
7 # Pour savoir si un utilisateur existe
8 function existUser {
9 grep -qi "^$1:" /etc/passwd && return 0
10 return 1
11 }
12 # Pour connaitre l'uid de l'utilisateur
13 function getUid {
14 grep -i "^$1:" /etc/passwd | cut -d':' -f3
15 }
16 # Initialisation des variables globales
17 uid=""
18 user=""
19 choix=""
20 while true
21 do
22 clear
23 echo "- 1 - Savoir si un utilisateur existe"
24 echo "- 2 - Connaitre l'UID d'un utilisateur"
25 echo "- 3 - Fin"
26 echo -e "Votre choix : \c"
27 read choix
28 if [[ $choix = @(1|2) ]] ; then
29 echo -e "Saisir le nom d'un utilisateur : \c"
30 read user
31 fi
32 case $choix in
33 1) if existUser $user ; then
34 echo "L'utilisateur $user existe"
35 else
36 echo "l'utilisateur $user n'existe pas"
37 fi
38 ;;
39 2) if existUser $user ; then
40 uid=$(getUid $user)
41 echo "l'UID de l'utilisateur $user est : $uid"
42 else
43 echo "L'utilisateur $user n'existe pas"
44 fi
45 ;;
46 3) exit 0
47 ;;
48 esac
49 pause
50 done
$
En KSH, la commande print apporte des fonctionnalités supplémentaires à la commande echo.
Exemple :
Utilisation simple
$ print coucou
coucou
$
Supprimer le saut de ligne
$ print -n coucou
coucou$
Afficher des arguments commançant par le caractère "-"
$ print - "-i : Option invalide"
-i : Option invalide
$
Ecrire sur un descripteur particulier
$ print -u2 "Message d'erreur"
Message d'erreur
$
Comparaison avec la commande echo
$ echo "Message d'erreur" 1>&2
Message d'erreur
$
En BASH, cette commande est identique à celle du langage C.
Elle permet de formater les affichages.
Syntaxe :
printf chaine expr1 expr2 ..... exprn
chaine représente la chaîne qui sera affichée à l'écran.
Elle peut contenir des formats qui seront substitués par la valeur des expressions citées à sa suite.
Il doit y avoir autant de formats que d'expressions.
Exemple de formats utilisés.
%20s | Affichage d'une chaine (string) sur 20 positions avec cadrage à droite |
%-20s | Affichage d'une chaine (string) sur 20 positions avec cadrage à gauche |
%3d | Affichage d'un entier (décimal) sur 3 positions avec cadrage à droite |
%03d | Affichage d'un entier (décimal) sur 3 positions avec cadrage à droite et complété avec des 0 à gauche |
%-3d | Affichage d'un entier (décimal) sur 3 positions avec cadrage à gauche |
%+3d | Affichage d'un entier (décimal) sur 3 positions avec cadrage à droite et affichage systématique du signe (un nombre négatif est toujours affiché avec son signe) |
%10.2f | Affichage d'un nombre flottant sur 10 positions dont 2 décimales |
%+010.2f | Affichage d'un nombre flottant sur 10 positions dont 2 décimales, complété par des 0 à gauche, avec cadrage à droite et affichage systématique du signe |
Exemple :
$ article="Livres"
$ quantite=3
$ prix=3,5
$ printf "%-20s***%03d***%+10.2f\n" $article $quantite $prix
Livres ***003*** +3,50
$
En utilisant un tableau
$ liste=(livre 10 3,5 cd 5 10,65 dvd 7 19,70 bd 80 5,25)
$ printf "%-20s***%03d***%+10.2f\n" ${liste[*]}
livre ***010*** +3,50
cd ***005*** +10,65
dvd ***007*** +19,70
bd ***080*** +5,25
$
La commande interne exec permet de manipuler les descripteurs de fichier du shell courant.
Utilisée à l'intérieur d'un script, elle permet de rediriger de manière globale les entrées/sorties de celui-ci.
Rediriger l'entrée standard d'un script :
exec 0< fichier1
Toutes les commandes du script placées après cette directive et qui lisent leur entrée standard vont extraire leurs données à partir de fichier1.
Il n'y aura donc pas d'interaction avec le clavier.
Rediriger la sortie standard et la sortie d'erreur standard d'un script :
exec 1> fichier1 2> fichier2
Toutes les commandes du script placées après cette directive et qui écrivent sur leur sortie standard enverront leurs résultat dans fichier1.
Celles qui écrivent sur leur sortie d'erreur standard enverront leurs erreurs dans fichier2.
Rediriger la sortie standard et la sortie d'erreur standard d'un script dans un même fichier :
exec 1> fichier1 2>&1
Toutes les commandes du script placées après cette directive enverront leurs résultats et leurs erreurs dans fichier1
Premier exemple :
Redirection de la sortie standard vers /tmp/fichier1.log et redirection de la sortie d'erreur standard vers /tmp/fichier2.log.
$ nl test.sh
1 #!/bin/bash
2 exec 1> /tmp/fichier1.log 2> /tmp/fichier2.log
3 echo "Début du traitement : $(date)"
4 ls
5 cp *.zzz /tmp
6 rm *.zzz
7 sleep 5
8 echo "Fin du traitement : $(date)"
9 exit 0
$
Exécution du script.
$ ./test.sh
$
Affichage du fichier /tmp/fichier1.log
$ nl /tmp/fichier1.log
1 Début du traitement : lundi 12 décembre 2011, 08:23:33 (UTC+0100)
2 1coucou
3 24902
4 25013
5 25031
6 25043
7 boucleFor01.sh
8 boucleFor02.sh
9 boucleUntil01.sh
.....
87 Fin du traitement : lundi 12 décembre 2011, 08:23:38 (UTC+0100)
$
Affichage du fichier /tmp/fichier2.log (les fichiers *.zzz n'exsistant pas, 2 erreurs sont générées)
$ nl /tmp/fichier2.log
1 cp: impossible d'évaluer «*.zzz»: Aucun fichier ou dossier de ce type
2 rm: impossible de supprimer «*.zzz»: Aucun fichier ou dossier de ce type
$
Deuxième exemple :
Redirection de la sortie standard et de la sortie d'erreur standard vers le fichier /tmp/fichier3.log
$ nl ./test2.sh
1 #!/bin/bash
2 exec 1> /tmp/fichier3.log 2>&1
3 echo "Début du traitement : $(date)"
4 ls
5 cp *.zzz /tmp
6 rm *.zzz
7 sleep 5
8 echo "Fin du traitement : $(date)"
9 exit 0
$
Exécution du script
$ ./test2.sh
$
Affichage du fichier /tmp/fichier3.log
$ nl /tmp/fichier3.log
1 Début du traitement : lundi 12 décembre 2011, 12:56:35 (UTC+0100)
2 1coucou
3 24902
4 25013
5 25031
6 25043
.....
88 cp: impossible d'évaluer «*.zzz»: Aucun fichier ou dossier de ce type
89 rm: impossible de supprimer «*.zzz»: Aucun fichier ou dossier de ce type
90 Fin du traitement : lundi 12 décembre 2011, 12:56:40 (UTC+0100)
$
Troisième exemple :
Redirection de l'entrée standard
$ nl test3.sh
1 #!/bin/bash
2 exec 0< /etc/passwd
3 cpt=1
4 while read ligne
5 do
6 echo "Lecture de la ligne $cpt"
7 echo $ligne
8 ((cpt+=1))
9 done
10 exit 0
$
Exécution du script
$ ./test3.sh
Lecture de la ligne 1
root:x:0:0:root:/root:/bin/bash
Lecture de la ligne 2
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
Lecture de la ligne 3
bin:x:2:2:bin:/bin:/bin/sh
Lecture de la ligne 4
sys:x:3:3:sys:/dev:/bin/sh
Lecture de la ligne 5
sync:x:4:65534:sync:/bin:/bin/sync
Lecture de la ligne 6
games:x:5:60:games:/usr/games:/bin/sh
Lecture de la ligne 7
man:x:6:12:man:/var/cache/man:/bin/sh
.....
$
Les shells récents apportent une fonctionnalité supplémentaire.
La possibilité d'ouvrir et de manipuler des fichiers en utilisant des descripteurs compris entre 3 et 9 (en supplément de 0, 1 et 2).
L'avantage est de pouvoir manipuler des fichiers tout en conservant les descripteurs 0, 1 et 2 connectés sur le terminal.
En lecture
exec desc <fichier
En écriture
exec desc>fichier
read variable1 variable2 ..... variablen <&desc
ou
read -udesc variable1 variable2 ..... variablen
echo variable1 variable2 ..... variablen >&desc
ou
print -udesc variable1 variable2 ..... variablen
Syntaxe :
Ouvert en lecture
exec desc<&-
Ouvert en écriture
exec desc>&-
$ nl test4.sh
1 #!/bin/bash
2 # Ouverture du fichier /etc/passwd en lecture sous le descripteur 3
3 # et du fichier /tmp/resultat.log en écriture sous le descripteur 4
4 exec 3</etc/passwd 4>/tmp/resultat.log
5 cpt=1
6 # Lecture ligne par ligne du fichier /etc/passwd
7 # correspondant au descripteur 3
8 while read -u3 ligne
9 do
10 # Ecriture des données dans le fichier /tmp/resultat.log
11 # correspondant au descripteur 4
12 echo "Ecriture de la ligne $cpt" >&4
13 echo $ligne >&4
14 ((cpt+=1))
15 done
16 # Fermeture du fichier /etc/passwd correspondant au descripteur 3
17 exec 3<&-
18 # Fermeture du fichier /tmp/resultat.log correspondant au descripteur 4
19 exec 4>&-
20 exit 0
$
Résultat :
$ ./test4.sh
$ nl /tmp/resultat.log
1 Ecriture de la ligne 1
2 root:x:0:0:root:/root:/bin/bash
3 Ecriture de la ligne 2
4 daemon:x:1:1:daemon:/usr/sbin:/bin/sh
5 Ecriture de la ligne 3
6 bin:x:2:2:bin:/bin:/bin/sh
7 Ecriture de la ligne 4
8 sys:x:3:3:sys:/dev:/bin/sh
9 Ecriture de la ligne 5
10 sync:x:4:65534:sync:/bin:/bin/sync
11 Ecriture de la ligne 6
12 games:x:5:60:games:/usr/games:/bin/sh
13 Ecriture de la ligne 7
14 man:x:6:12:man:/var/cache/man:/bin/sh
.....
$
Les redirections peuvent également être faites au moment de l'exécution du script.
Exemple :
Avec le script suivant
$ nl test5.sh
1 #!/bin/bash
2 cpt=1
3 # Lecture ligne par ligne du fichier passé en paramètre
4 # ou lecture de la saisie clavier si pas de fichier en paramètre
5 while read ligne
6 do
7 # Ecriture des données dans le fichier passé en paramètre
8 # ou affichage à l'écran si pas de fichier en paramètre
9 echo "Ecriture de la ligne $cpt"
10 echo $ligne
11 ((cpt+=1))
12 done
13 exit 0
$
Exécution du script sans paramètre
$ ./test5.sh
saisie 1 # saisie
Ecriture de la ligne 1
saisie 1
saisie 2 # saisie
Ecriture de la ligne 2
saisie 2
saisie 3 # saisie
Ecriture de la ligne 3
saisie 3
^d
$
Exécution du script avec paramètre en entrée
$ ./test5.sh < /etc/passwd
Ecriture de la ligne 1
root:x:0:0:root:/root:/bin/bash
Ecriture de la ligne 2
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
Ecriture de la ligne 3
bin:x:2:2:bin:/bin:/bin/sh
Ecriture de la ligne 4
sys:x:3:3:sys:/dev:/bin/sh
Ecriture de la ligne 5
sync:x:4:65534:sync:/bin:/bin/sync
Ecriture de la ligne 6
games:x:5:60:games:/usr/games:/bin/sh
Ecriture de la ligne 7
man:x:6:12:man:/var/cache/man:/bin/sh
.....
$
Exécution du script avec paramètres en entrée et en sortie
$ ./test5.sh < /etc/passwd > /tmp/resultat.log
$ nl /tmp/resultat.log
1 Ecriture de la ligne 1
2 root:x:0:0:root:/root:/bin/bash
3 Ecriture de la ligne 2
4 daemon:x:1:1:daemon:/usr/sbin:/bin/sh
5 Ecriture de la ligne 3
6 bin:x:2:2:bin:/bin:/bin/sh
7 Ecriture de la ligne 4
8 sys:x:3:3:sys:/dev:/bin/sh
9 Ecriture de la ligne 5
10 sync:x:4:65534:sync:/bin:/bin/sync
11 Ecriture de la ligne 6
12 games:x:5:60:games:/usr/games:/bin/sh
13 Ecriture de la ligne 7
14 man:x:6:12:man:/var/cache/man:/bin/sh
.....
$
Les redirections d'entrée (0) et de sortie (1) standards peuvent également être faite à l'intérieur du script.
Exemple :
Avec le script suivant
$ nl test6.sh
1 #!/bin/bash
2 exec </etc/passwd >/tmp/resultat.log
3 cpt=1
4 while read ligne
5 do
6 echo "Ecriture de la ligne $cpt"
7 echo $ligne
8 ((cpt+=1))
9 done
10 exit 0
$
Exécution du script et affichage du résultat
$ ./test6.sh
$ nl /tmp/resultat.log
1 Ecriture de la ligne 1
2 root:x:0:0:root:/root:/bin/bash
3 Ecriture de la ligne 2
4 daemon:x:1:1:daemon:/usr/sbin:/bin/sh
5 Ecriture de la ligne 3
6 bin:x:2:2:bin:/bin:/bin/sh
7 Ecriture de la ligne 4
8 sys:x:3:3:sys:/dev:/bin/sh
9 Ecriture de la ligne 5
10 sync:x:4:65534:sync:/bin:/bin/sync
11 Ecriture de la ligne 6
12 games:x:5:60:games:/usr/games:/bin/sh
13 Ecriture de la ligne 7
14 man:x:6:12:man:/var/cache/man:/bin/sh
.....
$
Il est également possible de rediriger uniquement les commandes situées à l'intérieur d'une structure de contrôle.
Les redirections doivent être écrites derrière le mot clé qui ferme la structure de contrôle.
A l'exécution, elles sont mises en place avant le traitement de la structure de contrôle.*
Exemple :
Dans le script suivant, seules les commandes situées à l'intérieur de la boucle while seront redirigées.
$ nl test7.sh
1 #!/bin/bash
2 echo "Lancement du script"
3 cpt=1
4 while read ligne
5 do
6 echo "Ecriture de la ligne $cpt"
7 echo $ligne
8 ((cpt+=1))
9 done </etc/passwd >/tmp/resultat.log
10 exit 0
$ ./test7.sh
Lancement du script
$ nl /tmp/resultat.log
1 Ecriture de la ligne 1
2 root:x:0:0:root:/root:/bin/bash
3 Ecriture de la ligne 2
4 daemon:x:1:1:daemon:/usr/sbin:/bin/sh
5 Ecriture de la ligne 3
6 bin:x:2:2:bin:/bin:/bin/sh
$
Rediriger un bloc avec des fichiers ouverts en amont
$ nl test8.sh
1 #!/bin/bash
2 exec 3</etc/passwd 4>/tmp/resultat.log
3 echo "Lancement du script"
4 cpt=1
5 while read ligne
6 do
7 # Ecriture des données dans le fichier /tmp/resultat.log
8 # correspondant au descripteur 4
9 echo "Ecriture de la ligne $cpt"
10 echo $ligne
11 ((cpt+=1))
12 done <&3 >&4
13 echo "Fin du script"
14 exec 3<&-
15 exec 4>&-
16 exit 0
$ ./test8.sh
Lancement du script
Fin du script
$ nl /tmp/resultat.log
1 Ecriture de la ligne 1
2 root:x:0:0:root:/root:/bin/bash
3 Ecriture de la ligne 2
4 daemon:x:1:1:daemon:/usr/sbin:/bin/sh
5 Ecriture de la ligne 3
6 bin:x:2:2:bin:/bin:/bin/sh
$
Si les lignes du fichier à traiter sont structurées en champs, il est très facile de récupérer chacun de ceux ci dans une variable.
Pour cela, il faut modifier la valeur de la variable IFS.
Exemple :
Le script suivant génère, à partir du fichier /etc/passwd, un affichage à l'écran du username suivi de son uid.
La variable IFS est sauvegardée (ligne 13) afin d'être restaurée (ligne 19) et son contenu est remplacé par ":" (ligne 14).
":" étant le caractère séparant les champs du fichier /etc/passwd.
La commande read reçoit 7 variables en argument (ligne 15). Autant de variables que de champs dans le fichier /etc/passwd.
Elle découpe donc la ligne lue (ligne 18) en champs en utilisant le caractère ":" comme séparateur.
La ligne est donc automatiquement découpée et les valeurs récupérables via les variables indiquées.
$ nl test9.sh
1 #!/bin/bash
2 if (( $# != 1 ))
3 then
4 echo "Mauvais nombre d'arguments"
5 echo "Usage : $0 fichier"
6 exit 1
7 fi
8 if [[ ( ! -f "$1" ) || ( ! -r "$1" ) ]]
9 then
10 echo "$1 n'est pas un fichier ordinaire ou n'est pas accessible en lecture"
11 exit 2
12 fi
13 oldIFS="$IFS"
14 IFS=":"
15 while read username password uid gid nomComplet home shell
16 do
17 echo "$username ==> $uid"
18 done < $1
19 IFS="$oldIFS"
20 exit 0
$ ./test9.sh /etc/passwd
root ==> 0
daemon ==> 1
bin ==> 2
sys ==> 3
$
Ce script peut très bien être dirigé dans un tube et exploité avec la commande grep
$ ./test9.sh /etc/passwd | grep "root"
root ==> 0
$
Syntaxe :
eval expr1 expr2 ..... exprn
La commande eval permet de faire subir à une ligne de commande une double évaluation.
Exemple :
Initialisation de la variable nom
$ nom=toto
Initialisation de la variable var avec le nom de la variable définie ci dessus
$ var=nom
Affichage des valeurs des variables
$ echo $nom
toto
$ echo $var
nom
$ echo \$$var
$nom
$
Pour obtenir $var=toto, il faut obligatoirement utiliser la commande eval de cette manière
$ eval echo \$$var
toto
$
La commande eval évalue en premier la variable $var, le premier $ étant protégé par un anti-slash
==> eval echo \$nom
puis évalue le résultat de la première évaluation, c'est à dire $nom (suppression de l'anti-slash)
==> eval echo $nom
Initialisation de la variable var2
$ var2=\$$var
$ echo $var2
$nom
$ eval var2=\$$var
$ echo $var2
toto
$
Il est possible de modifier le comportement des signaux envoyés au shell en utilisant la commande trap.
Liste des signaux :
$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
$
Détail des signaux :
Dans les commandes, les signaux peuvent être exprimés sous forme numérique ou symbolique.
Les signaux HUP, INT, TERM et KILL possèdent la même valeur numérique sur toutes les plates-formes Unix, ce qui n'est pas le cas de tous les signaux.
Il est donc préférable d'utiliser la forme symbolique.
Le signal INT est généré à partir du clavier.
Il est utilisé pour tuer le processus qui tourne en avant plan.
Pour connaitre la saisie clavier correspondant au signal INT, voir le paramètre intr de la commande stty -a.
$ stty -a
speed 38400 baud; rows 36; columns 134; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S;
susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
-parenb -parodd cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany -imaxbel -iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke
$
Syntaxe :
trap ' ' sig1 sig2
Exemple :
Le shell courant correspond au PID 30819
$ echo $$
30819
Modification des signaux HUP et TERM
$ trap '' HUP TERM
Envoi des signaux HUP et TERM
$ kill -HUP 30819
$ kill -TERM 30819
Les signaux sont ignorés et le processus est toujours actif
$ echo $$
30819
$
Syntaxe :
trap 'cmd1 ; cmd2 ; cmd3 ; ..... ; cmdn' sig1 sig2
Exemple :
Le shell courant correspond au PID 23217
$ ps
PID TTY TIME CMD
22109 pts/0 00:00:00 bash
23217 pts/0 00:00:00 bash
23465 pts/0 00:00:00 ps
$ echo $$
23217
Modification du traitement associé au signal ^C (ctrl+c)
On demande au shell d'afficher le message "Signal INT reçu" après avoir appuyer sur ^C (ctrl+c)
$ trap 'echo "Signal INT reçu" ; exit 1' INT
$ ^C # Saisie
$ Signal INT reçu
Le shell courant correspondant au PID 23217 n'existe plus
$ ps
PID TTY TIME CMD
22109 pts/0 00:00:00 bash
24229 pts/0 00:00:00 ps
$ echo $$
22109
$
Syntaxe :
trap - sig1 sig2 ..... sign
Exemple :
Création du fichier /tmp/fichier
$ > /tmp/fichier
$ ls /tmp/fichier
/tmp/fichier
Modification du traitement associé au signal INT et TERM
On demande au shell de supprimer le fichier "/tmp/fichier" après avoir appuyer sur ^C (ctrl+c)
$ trap 'rm -f /tmp/fichier' INT TERM
Le shell courant correspond au PID 22109
$ echo $$
22109
Envoi du signal INT "^C" (ctrl+c)
$ ^C
Le fichier a bien été suprimé
$ ls /tmp/fichier
ls: impossible d'accéder à /tmp/fichier: Aucun fichier ou dossier de ce type
Création d'un nouveau fichier /tmp/fichier
$ > /tmp/fichier
$ ls /tmp/fichier
/tmp/fichier
Traitement associé au signal INT et TERM remis par défaut
$ trap - INT TERM
Le shell courant correspond au PID 22109
$ echo $$
22109
Envoi du signal INT "^C" (ctrl+c)
$ ^C
Le fichier n'a pas été supprimé
$ ls /tmp/fichier
/tmp/fichier
$
L'utilisation de trap dans un script shell va permettre de gérer des actions en fonctions de différents signaux reçus.
Exemple :
Dans le script suivant, à la réception d'un signal HUP INT ou TERM, la fonction "fin" est appelée et le fichier $fileTmp est supprimé.
$ nl signaux.sh
1 #!/bin/bash
2
3 # Nom du fichier temporaire
4 fileTmp=/tmp/fileTemp
5
6 # Fonction appelée lors de la réception d'un signal HUP INT TERM
7 function fin {
8 echo -e "\nSuppression du fichier $fileTmp"
9 echo "Fin du script"
10 rm -f $fileTmp
11 ls $fileTmp
12 exit 1
13 }
14
15 # Paramétrage de la fonction "fin" à la réception d'un signal HUP INT TERM
16 trap fin HUP INT TERM
17
18 # Création du fichier temporaire
19 > $fileTmp
20
21 echo "Lancement du script"
22 # Vérification de la création du fichier temporaire
23 ls $fileTmp
24 sleep 100
25 echo "Arrêt du script"
26 exit 0
$
Exécution du script :
$ ./signaux.sh
Lancement du script
/tmp/fileTemp
^C # Envoi du signal
Suppression du fichier /tmp/fileTemp
Fin du script
ls: impossible d'accéder à /tmp/fileTemp: Aucun fichier ou dossier de ce type
$
Syntaxe :
select var in item1 item2 item3 ..... itemn
do
commandes
done
La commande interne select est une structure de contrôle de type boucle qui permet d'afficher de manière cyclique un menu.
La liste des items sera affichée à l'écran à chaque tour de boucle.
Les items sont indicés automatiquement.
La variable var sera initialisée avec l'item correspondant au choix de l'utilisateur.
Cette commande utilise également deux variables réservées :
La variable var contient le libellé du choix et REPLY l'indice de ce dernier.
Exemple :
$ nl select.sh
1 #!/bin/bash
2
3 function sauve {
4 echo "Lancement de la sauvegarde"
5 }
6
7 function restaure {
8 echo "Lancement de la restauration"
9 }
10
11 PS3="Votre choix : "
12
13 select item in "- Sauvegarde -" "- Restauration -" "- Fin -"
14 do
15 echo "Vous avez choisi l'item $REPLY : $item"
16 case $REPLY in
17 1)
18 # Appel de la fonction sauve
19 sauve
20 ;;
21 2)
22 # Appel de la fonction restaure
23 restaure
24 ;;
25 3)
26 echo "Fin du script"
27 exit 0
28 ;;
29 *)
30 echo "Choix incorrect"
31 ;;
32 esac
33 done
$
Exécution :
$ ./select.sh
1) - Sauvegarde -
2) - Restauration -
3) - Fin -
Votre choix : 2
Vous avez choisi l'item 2 : - Restauration -
Lancement de la restauration
La saisie de la touche [Entrée] permet de réafficher le menu :
$ .
Votre choix : # Appui sur la touche [Entrée]
1) - Sauvegarde -
2) - Restauration -
3) - Fin -
Votre choix : 3
Vous avez choisi l'item 3 : - Fin -
Fin du script
$
$ type getopts
getopts est une primitive du shell
$
Syntaxe :
getopts listeOptionsAttendues option
La commande interne getopts permet à un script d'anayser les options passées en argument.
Chaque appel à la commande getopts analyse l'option suivante de la ligne de commande.
Pour vérifier la validité de chacune des options, il faut appeler getopts à partir d'une boucle.
Pour getopts, une option est composée d'un caractère précédé du signe "+" ou "-".
Premier exemple :
$ ls -l *.sh
Par exemple, pour la commande ls, "-l" est une option et "*.sh" est un argument.
Une option peut fonctionner seule ou être associée à un argument.
Second exemple :
Le script suivant détail la manière d'utiliser la commande getopts.
$ nl test_getopts_1.sh
1 #!/bin/bash
2
3 while getopts "abcd:e:" option
4 do
5 echo "getopts a trouvé l'option $option"
6 case $option in
7 a)
8 echo "Exécution des commandes de l'option a"
9 echo "Indice de la prochaine option à traiter : $OPTIND"
10 ;;
11 b)
12 echo "Exécution des commandes de l'option b"
13 echo "Indice de la prochaine option à traiter : $OPTIND"
14 ;;
15 c)
16 echo "Exécution des commandes de l'option c"
17 echo "Indice de la prochaine option à traiter : $OPTIND"
18 ;;
19 d)
20 echo "Exécution des commandes de l'option d"
21 echo "Liste des arguments à traiter : $OPTARG"
22 echo "Indice de la prochaine option à traiter : $OPTIND"
23 ;;
24 e)
25 echo "Exécution des commandes de l'option e"
26 echo "Liste des arguments à traiter : $OPTARG"
27 echo "Indice de la prochaine option à traiter : $OPTIND"
28 ;;
29 esac
30 done
31 echo "Analyse des options terminée"
32 exit 0
$
L'appel à la commande getopts récupère l'option suivante et retourne un code vrai tant qu'il reste des options à analyser.
La liste des options utilisables avec ce script sont définies à la ligne 3 (getopts "abcd:e:" option). Il s'agit des options -a, -b, -c, -d et -e.
Le caractère ":" inscrit après les options "d" et "e" (getopts "abcd:e:" option) indique que ces options doivent être suivies obligatoirement d'un argument.
La variable "option" (getopts "abcd:e:" option) permet de récupérer la valeur de l'option en cours de traitement par la boucle while.
La variable réservée "$OPTIND" contient l'indice de la prochaine option à traiter.
La variable réservée "$OPTARG" contient l'argument associé à l'option.
Exécution du script avec des options valides :
$ ./test_getopts_1.sh -a -b -c -d toto -e tata,tutu
getopts a trouvé l'option a
Exécution des commandes de l'option a
Indice de la prochaine option à traiter : 2
getopts a trouvé l'option b
Exécution des commandes de l'option b
Indice de la prochaine option à traiter : 3
getopts a trouvé l'option c
Exécution des commandes de l'option c
Indice de la prochaine option à traiter : 4
getopts a trouvé l'option d
Exécution des commandes de l'option d
Liste des arguments à traiter : toto
Indice de la prochaine option à traiter : 6
getopts a trouvé l'option e
Exécution des commandes de l'option e
Liste des arguments à traiter : tata,tutu
Indice de la prochaine option à traiter : 8
Analyse des options terminée
$
Lorsque la commande getopts détecte une option invalide, la variable option est initialisée avec la caractère "?" et un message d'erreur est affiché à l'écran.
Les options suivantes sont analysées.
Exemple :
L'option -z ne fait pas partie de la liste des options attendues.
$ ./test_getopts_1.sh -a -z -b -c -d toto -e tata,tutu
getopts a trouvé l'option a
Exécution des commandes de l'option a
Indice de la prochaine option à traiter : 2
./test_getopts_1.sh : option non permise -- z
getopts a trouvé l'option ?
getopts a trouvé l'option b
Exécution des commandes de l'option b
Indice de la prochaine option à traiter : 4
getopts a trouvé l'option c
Exécution des commandes de l'option c
Indice de la prochaine option à traiter : 5
getopts a trouvé l'option d
Exécution des commandes de l'option d
Liste des arguments à traiter : toto
Indice de la prochaine option à traiter : 7
getopts a trouvé l'option e
Exécution des commandes de l'option e
Liste des arguments à traiter : tata,tutu
Indice de la prochaine option à traiter : 9
Analyse des options terminée
$
Si le caractère ":" est placé en première position dans la liste des options à traiter (ligne 3), les erreurs sont gérées différemment.
En cas d'option invalide :
- getopts n'affichera pas de message d'erreur.
- la variable OPTARG sera initialisée avec la valeur de l'option incorrecte (ligne 29).
Exemple :
$ nl test_getopts_1.sh
1 #!/bin/bash
2
3 while getopts ":abcd:e:" option
4 do
5 echo "getopts a trouvé l'option $option"
6 case $option in
7 a)
8 echo "Exécution des commandes de l'option a"
9 echo "Indice de la prochaine option à traiter : $OPTIND"
10 ;;
11 b)
12 echo "Exécution des commandes de l'option b"
13 echo "Indice de la prochaine option à traiter : $OPTIND"
14 ;;
15 c)
16 echo "Exécution des commandes de l'option c"
17 echo "Indice de la prochaine option à traiter : $OPTIND"
18 ;;
19 d)
20 echo "Exécution des commandes de l'option d"
21 echo "Liste des arguments à traiter : $OPTARG"
22 echo "Indice de la prochaine option à traiter : $OPTIND"
23 ;;
24 e)
25 echo "Exécution des commandes de l'option e"
26 echo "Liste des arguments à traiter : $OPTARG"
27 echo "Indice de la prochaine option à traiter : $OPTIND"
28 ;;
29 \?)
30 echo "$OPTARG : option invalide"
31 exit 1
32 ;;
33 esac
34 done
35 echo "Analyse des options terminée"
36 exit 0
$
Le message d'erreur généré automatiquement par getopts n'apparait plus et la variable $OPTARG a été substituée par la valeur de l'option incorrecte.
Ligne 29, le "?" doit être protégé par un anti-slash pour ne pas être interprété par le shell.
$ ./test_getopts_1.sh -a -z -b -c -d toto -e tata,tutu
getopts a trouvé l'option a
Exécution des commandes de l'option a
Indice de la prochaine option à traiter : 2
getopts a trouvé l'option ?
z : option invalide
$
Lorsque l'argument d'une option est absent, la variable option est initialisée avec le caractère ":" et OPTARG contient la valeur de l'option concernée (ligne 29).
Exemple :
$ nl test_getopts_1.sh
1 #!/bin/bash
2
3 while getopts ":abcd:e:" option
4 do
5 echo "getopts a trouvé l'option $option"
6 case $option in
7 a)
8 echo "Exécution des commandes de l'option a"
9 echo "Indice de la prochaine option à traiter : $OPTIND"
10 ;;
11 b)
12 echo "Exécution des commandes de l'option b"
13 echo "Indice de la prochaine option à traiter : $OPTIND"
14 ;;
15 c)
16 echo "Exécution des commandes de l'option c"
17 echo "Indice de la prochaine option à traiter : $OPTIND"
18 ;;
19 d)
20 echo "Exécution des commandes de l'option d"
21 echo "Liste des arguments à traiter : $OPTARG"
22 echo "Indice de la prochaine option à traiter : $OPTIND"
23 ;;
24 e)
25 echo "Exécution des commandes de l'option e"
26 echo "Liste des arguments à traiter : $OPTARG"
27 echo "Indice de la prochaine option à traiter : $OPTIND"
28 ;;
29 :)
30 echo "L'option $OPTARG requiert un argument"
31 exit 1
32 ;;
33 \?)
34 echo "$OPTARG : option invalide"
35 exit 1
36 ;;
37 esac
38 done
39 echo "Analyse des options terminée"
40 exit 0
$
Exécution du script en oubliant l'argument de l'option -e
$ ./test_getopts_1.sh -a -b -c -d toto -e
getopts a trouvé l'option a
Exécution des commandes de l'option a
Indice de la prochaine option à traiter : 2
getopts a trouvé l'option b
Exécution des commandes de l'option b
Indice de la prochaine option à traiter : 3
getopts a trouvé l'option c
Exécution des commandes de l'option c
Indice de la prochaine option à traiter : 4
getopts a trouvé l'option d
Exécution des commandes de l'option d
Liste des arguments à traiter : toto
Indice de la prochaine option à traiter : 6
getopts a trouvé l'option :
L'option e requiert un argument
$
Les options sont stockées dans les paramètres positionnels $1, $2 ..... $n.
Une fois que celles ci sont analysées, il est possible de s'en débarasser avec la commande shift.
Ceci est intéressant s'il reste des arguments à traiter derrière les options.
Exemple :
$ nl test_getopts_1.sh | tail
37 esac
38 done
39 echo "Analyse des options terminée"
40 echo "Avant shift : "
41 echo "Liste des arguments : $*"
42 echo "Indice de la prochaine option à traiter : $OPTIND"
43 shift $((OPTIND-1))
44 echo "Après shift : "
45 echo "Liste des arguments : $*"
46 exit 0
$
L'instruction à la ligne 43 permet de retirer les options de la liste des arguments.
L'expression OPTIND-1 représente le nombre d'options analysées, donc la valeur du décalage à réaliser.
Exécution du script avec des arguments supplémentaires :
$ ./test_getopts_1.sh -a -b -c -d toto -e tata,tutu arg1 arg2 arg3 arg4
getopts a trouvé l'option a
Exécution des commandes de l'option a
Indice de la prochaine option à traiter : 2
getopts a trouvé l'option b
Exécution des commandes de l'option b
Indice de la prochaine option à traiter : 3
getopts a trouvé l'option c
Exécution des commandes de l'option c
Indice de la prochaine option à traiter : 4
getopts a trouvé l'option d
Exécution des commandes de l'option d
Liste des arguments à traiter : toto
Indice de la prochaine option à traiter : 6
getopts a trouvé l'option e
Exécution des commandes de l'option e
Liste des arguments à traiter : tata,tutu
Indice de la prochaine option à traiter : 8
Analyse des options terminée
Avant shift :
Liste des arguments : -a -b -c -d toto -e tata,tutu arg1 arg2 arg3 arg4
Indice de la prochaine option à traiter : 8
Après shift :
Liste des arguments : arg1 arg2 arg3 arg4
$
La commande wait permet au shell d'attendre la terminaison d'un processus lancé en arrière-plan.
Syntaxes :
Attendre la terminaison du processus dont le PID est donné en argument :
wait pid1
Attendre la terminaison de tous les processus lancés en arrière-plan à partir du shell courant :
wait
Attendre la terminaison du processus dont le numéro de job est donné en argument :
wait %job
Exemples :
La commande find est lançée en arrière-plan et a pour PID 2878 :
$ find / -name /etc/passwd 1>/tmp/resu 2>/dev/null &
[1] 2878
$ jobs
[1]+ Running find / -name /etc/passwd > /tmp/resu 2> /dev/null &
$
Le shell s'endort en attendant la terminaison du processus 2878 :
$ wait 2878 # Ou wait %1
Le shell est réveillé lorsque le processus 2878 est terminé :
$ .
[1]+ Exit 1 find / -name /etc/passwd > /tmp/resu 2> /dev/null
$
Le PID de la dernière commande lançée en arrière-plan est contenu dans la variable spéciale $!
Le script suivant lance en arrière-plan une recherche du fichier /etc/passwd (ligne 3).
D'autres actions peuvent être exécutées en attendant (ligne 5 à 8) puis le shell attend la fin de la recherche (ligne 9) avant d'afficher à l'écran le contenu du fichier /tmp/resu (ligne 12).
$ nl test_wait_1.sh
1 #!/bin/bash
2
3 find / -name /etc/passwd 1>/tmp/resu 2>&1 &
4 echo "Le PID du script lancé en arrière-plan est le : $!"
5 echo "Début des autres commandes"
6
7 echo "Fin des autres commandes"
8 echo "Recherche en cours - Attente de la fin de la recherche"
9 wait $!
10 echo "La recherche est terminée"
11 echo "Affichage du résultat"
12 most /tmp/resu
13 exit 0
$
$ ./test_wait_1.sh
Le PID du script lancé en arrière-plan est le : 11697
Début des autres commandes
Fin des autres commandes
Recherche en cours - Attente de la fin de la recherche
..... # Le shell s'endort
.....
La recherche est terminée
Affichage du résultat
$
Pré-requis pour que le script suivant fonctionne :
1 - Paramétrer sur le poste à sauvegarder un accès ssh au serveur de sauvegarde via un système d'authentification par clé privée / publique.
Tout est expliqué ici : http://quennec.fr/linux/utilisation/connexion-a-une-machine-distante-sans-saisir-le-mot-de-passe
2 - Le programme bzip2 doit être installé sur le poste à sauvegarder.
$ apt-get install bzip2
3 - Rendre les scripts exécutables
$ chmod +x uploadBackup.sh && chmod +x fonctions.inc.sh
Script d'archivage incrémental et transfert sftp automatique
Ce script permet d'effectuer une sauvegarde incrémentale d'un répertoire vers un serveur de sauvegarde.
Il utilise un système de niveau correspondant au jour du mois - 1.
Tous les 1er de chaque mois, une sauvegarde totale (niveau 0) est effectuée.
Une sauvegarde incrémentale est effectuée les jours suivants.
Le script est composé de 2 fichiers.
Un fichier comportant le script principal (uploadBackup.sh) et un fichier comportant les fonctions personnalisées utiles au script principal (fonctions.inc.sh).
Les fichiers à sauvegarder (variable DATADIR) sont enregistrés dans une archive CPIO compressée avec BZIP2 et stockée dans un répertoire local sur le poste à sauvegarder (variable ARCHIVEDIR).
Avant chaque sauvegarde, un fichier de niveau est créé dans le répertoire local.
Toutes les logs sont enregistrées dans un fichier stocké également dans le répertoire local.
La sauvegarde incrémentale utilise le fichier niveau précédent afin de sauvegarder tous les fichiers modifiés depuis la sauvegarde précédente grâce à la commande find et l'option -newer.
Enfin, l'archive CPIO est envoyée sur le serveur de sauvegarde (variable serveur_sauvegarde) via SFTP et stockée dans des sous-répertoires correspondant à l'année et au mois de la sauvegarde dans le dossier de sauvegarde (variable dossier_distant) .
Détail du fichier uploadBackup.sh
$ nl uploadBackup.sh
1 #!/bin/bash
2 # set -x
3 # Répertoire des scripts shell
4 SCRIPTDIR="/root/script_archivage_incremental"
5 # Répertoire des fichiers à sauvegarder
6 DATADIR="/root/dossier_a_sauvegarder"
7 # Répertoire local des archives
8 ARCHIVEDIR="/root/local_backup"
9 # Inclure les fonctions
10 . $SCRIPTDIR/fonctions.inc.sh
11 # Fichier de log
12 LOG=$ARCHIVEDIR/`getDate`.log
13 # Redirection de toutes les sorties du script vers le fichier de log
14 exec 1>$LOG 2>&1
15 # Déterminer le niveau de sauvegarde
16 # Le 1er du mois => niveau 0
17 jourDuMois=`getDayForCalcul`
18 ((niveau=$jourDuMois-1))
19 case $niveau in
20 0) # Sauvegarde totale
21 # Nettoyage du répertoire d'archive
22 rm -i $ARCHIVEDIR/*.bz2 $ARCHIVEDIR/niveau*
23 # Création du fichier de niveau (niveau 0)
24 touch $ARCHIVEDIR/niveau0
25 archive="$ARCHIVEDIR/`getDate`_niveau0.cpio"
26 find $DATADIR | cpio -ocv | bzip2 -c > $archive.bz2
27 ;;
28 *)
29 # Creation du fichier de niveau
30 touch $ARCHIVEDIR/niveau$niveau
31 archive="$ARCHIVEDIR/`getDate`_niveau${niveau}.cpio"
32 # Determination du niveau precedent
33 ((niveauPrec=$niveau-1))
34 # Test si le fichier de niveau precedent existe
35 if [[ ! -f $ARCHIVEDIR/niveau$niveauPrec ]]
36 then
37 # Si il n'existe pas, sauvegarde integrale du repertoire
38 echo "Fichier de niveau $niveauPrec inexistant"
39 echo "Execution d'une sauvegarde integrale en cours de mois"
40 find $DATADIR | cpio -ocv | bzip2 -c > $archive.bz2
41 else
42 # Sauvegarde incrementale
43 find $DATADIR -newer $ARCHIVEDIR/niveau$niveauPrec | cpio -ocv | bzip2 -c > $archive.bz2
44 fi
45 ;;
46 esac
47 # Vérification de la validité de l'archive
48 if isArchiveInvalide $archive.bz2 ; then
49 echo "Archive $archive.bz2 INVALIDE - Fichier non transfere"
50 exit 1
51 fi
52 # Transfert du fichier vers le serveur de sauvegarde
53 if transfert ${archive}.bz2 ; then
54 echo "Transfert realise avec succes"
55 exit 0
56 fi
57 # Si le transfert a echoue
58 echo "Echec de transfert"
59 exit 1
$
Détail du fichier fonctions.inc.sh
$ nl fonctions.inc.sh
1 #!/bin/bash
2 function transfert {
3 typeset mois
4 typeset annee
5 # Recuperation de la valeur du premier argument passe a la fonction
6 # Recuperation du nom de l'archive a envoyer au serveur de sauvegarde
7 typeset ficATransferer=$1
8 # Adresse du serveur de sauvegarde
9 typeset serveur_sauvegarde="192.168.1.113"
10 # Compte utilise pour se connecter au serveur de sauvegarde
11 typeset user="root"
12 # Chemin absolu du dossier ou sera stocke la sauvegarde
13 typeset dossier_distant="/root/sauvegarde_dossier"
14 mois=`getMonth`
15 annee=`getYear`
16 # Test si le dossier de sauvegarde existe sur le serveur de sauvegarde
17 # Connexion au serveur avec le user root
18 ssh $user@$serveur_sauvegarde test -d $dossier_distant/$annee/$mois
19 # Test sur le code retour de la commande precedente
20 case $? in
21 0)
22 ;;
23 255)
24 echo "Echec de la commande SSH"
25 return 1
26 ;;
27 *)
28 # Si code retour different de 0 et 255
29 # Creation du repertoire de la sauvegarde
30 ssh $user@$serveur_sauvegarde mkdir -p $dossier_distant/$annee/$mois
31 ;;
32 esac
33 # Connexion au serveur de sauvegarde en FTP sécurisé
34 # Ne pas oublier les doubles chevrons << avant le mot cle FIN
35 # Ne pas mettre d'espace entre << et FIN
36 sftp -b - $user@$serveur_sauvegarde <<FIN
37 # Positionnement dans le repertoire de la sauvegarde
38 cd $dossier_distant/$annee/$mois
39 pwd
40 # Envoi de la sauvegarde
41 put $ficATransferer
42 FIN
43 # Ne pas mettre de tabulation ou d'espace devant le mot clé FIN
44 # Sinon celui-ci n'est pas reconnu
45 # Test sur le code retour de la commande SFTP
46 # Si le code retour est different de 0
47 # Une anomalie a ete rencontree
48 (( $? != 0 )) && return 1
49 # Tester si archive valide sur serveur de sauvegarde
50 ficSurMachineCible=$(basename $ficATransferer)
51 ssh $user@$serveur_sauvegarde bzip2 -t $dossier_distant/$annee/$mois/$ficSurMachineCible
52 case $? in
53 0)
54 ;;
55 255)
56 echo "Echec de la commande SSH"
57 return 1
58 ;;
59 *)
60 # Si code retour different de 0 et 255
61 # Alors l'archive est invalide
62 echo "Archive $dossier_distant/$annee/$mois/$ficSurMachineCible INVALIDE"
63 return 1
64 ;;
65 esac
66 return 0
67 }
68 function isArchiveInvalide {
69 typeset archive=$1
70 bzip2 -t $archive 2>/dev/null && return 1
71 return 0
72 }
73 function getDate {
74 date '+%Y_%m_%d'
75 }
76 function getYear {
77 date '+%Y'
78 }
79 function getMonth {
80 date '+%m'
81 }
82 function getDayForCalcul {
83 date '+%e' | sed 's/ //'
84 }
$