[Tutoriel] Programmation Shell - Bonnes pratiques
Dernière réponse : dans Le monde de Linux
Programmation Shell
Bonnes pratiques
Bonnes pratiques
A l'heure des Ubuntu et des Mandriva, les Linuxiens se targuent de ne plus avoir besoin de se servir de la ligne de commande. Pire, on trouve de moins en moins sur les forums de scripts correctement écrits, faute de pratique d'une part, de bons exemples d'autre part.
Je me propose dans ce topic, de compiler un certain nombre de ces bonnes pratiques qu'il conviendrait de ne pas perdre.
Ce topic est aussi le vôtre. N'hésitez pas à y contribuer, pour tout ce qui concerne les bonnes pratiques de la programmation en shell.
_____________________
En revanche, il n'a pas vocation à recueillir les questions particulières à vos scripts. Pour cela, veuillez créer un [nouveau sujet].
Autres pages sur : tutoriel programmation shell bonnes pratiques
zeb a édité ce message
Lassé par la pub ? Créez un compte
Shell exécutant
#!
Tout programme qui démarre instancie un processus. Si le programme n’est pas un exécutable natif, c’est un shell qui est instancié. En dehors de toute précision, le type de shell est le même que celui de la session en cours. Pour préciser un type de shell en particulier, soit on le précise sur la ligne de commande, soit on le précise dans le hashbang, sur la première ligne du script qui commence alors par #!, immédiatement suivi par le chemin complet vers le programme.
Sur la ligne de commande :
Dans le hashbang :
#!
Tout programme qui démarre instancie un processus. Si le programme n’est pas un exécutable natif, c’est un shell qui est instancié. En dehors de toute précision, le type de shell est le même que celui de la session en cours. Pour préciser un type de shell en particulier, soit on le précise sur la ligne de commande, soit on le précise dans le hashbang, sur la première ligne du script qui commence alors par #!, immédiatement suivi par le chemin complet vers le programme.
$> /usr/bin/ksh monscript.sh
$> /bin/perl monscript.pl
#!/usr/bin/ksh
..
#!/ bin/perl
..
Aide
Un programme correctement écrit doit proposer un minimum d’aide sur ce qu’il peut faire et sur comment le lui faire faire. En particulier, il doit renseigner sur les options possibles. Par convention, ce sont les options --help, -H ou -h qui permettent d’obtenir ces informations.
Voici un exemple de code à mettre au début de chaque script :
Comme la syntaxe est une information pertinente à rappeler à chaque erreur sur la ligne de commande par exemple, on met ces informations dans une variable pour pouvoir facilement s'en re-servir.
_____________________
N.B. Je donne mes exemples en korn shell. La transposition en Bourne again Shell devrait être triviale.
Un programme correctement écrit doit proposer un minimum d’aide sur ce qu’il peut faire et sur comment le lui faire faire. En particulier, il doit renseigner sur les options possibles. Par convention, ce sont les options --help, -H ou -h qui permettent d’obtenir ces informations.
Voici un exemple de code à mettre au début de chaque script :
local USAGE="usage: $(basename $0) [options] parametres" if [ "$1" == "--help" ] ; then print "$USAGE" print "Explications sur la commande" print " option1 explication sur l’option" print " option2 explication sur l’option" print " parametres explication sur les paramètres" fi
Comme la syntaxe est une information pertinente à rappeler à chaque erreur sur la ligne de commande par exemple, on met ces informations dans une variable pour pouvoir facilement s'en re-servir.
_____________________
N.B. Je donne mes exemples en korn shell. La transposition en Bourne again Shell devrait être triviale.
Retour
Par convention, tout processus qui s’exécute doit renvoyer au processus qui l’a lancé un niveau d’erreur rendant compte de son exécution par le code de retour. Si tout s’est bien passé, le code de retour doit être nul. Si une erreur s’est produite, un code strictement supérieur à zéro doit être renvoyé. Une nomenclature de codes de retour peut être définie pour renseigner sur le type d’erreur rencontrée. Sinon, on peut se contenter d’un code à deux voire à trois niveaux.
Exemples
Code/Signification
0/Ok
1/Erreur
Code/Signification
0/Ok
1/Avertissement
2/Erreur
Un programme bien écrit doit traiter les cas particuliers et gérer les erreurs. En Pascal, un imbriquement de clauses IF THEN ELSE permet de distinguer les cas. En Java, le système de trap/catch récupère les cas exceptionnels. Vu la simplicité voulue (voir chapitre Entrée/Sortie) des traitements shell, on repère les cas limites dès le début du programme. Si tel cas ne peut être traité, on affiche un message d’erreur et on sort en fixant le niveau d’erreur à la valeur adéquate. La commande pour sortir d’un script est exit, celle pour sortir d’une fonction est return. En dehors d’une fonction, return se comporte comme exit.
La variable $? permet de récupérer la valeur du code retour de la dernière commande exécutée.
Sur une erreur d'options ou de paramètres de la ligne de commande, on utilisera plutôt :
En traitant un par un de cette manière tous les cas particuliers on est sûr en fin de script de se retrouver dans le cas général qu’il suffit alors de traiter.
Si on ne précise explicitement pas de code retour à la fin d’un script, c’est le code retour de la dernière commande du script qui est renvoyé.
Les commandes pouvant s’enchaîner, il est important d’écrire sur la sortie en erreur d’une part, et de préciser systématiquement le nom de la commande d’autre part.
Par convention, tout processus qui s’exécute doit renvoyer au processus qui l’a lancé un niveau d’erreur rendant compte de son exécution par le code de retour. Si tout s’est bien passé, le code de retour doit être nul. Si une erreur s’est produite, un code strictement supérieur à zéro doit être renvoyé. Une nomenclature de codes de retour peut être définie pour renseigner sur le type d’erreur rencontrée. Sinon, on peut se contenter d’un code à deux voire à trois niveaux.
Exemples
Code/Signification
0/Ok
1/Erreur
Code/Signification
0/Ok
1/Avertissement
2/Erreur
Un programme bien écrit doit traiter les cas particuliers et gérer les erreurs. En Pascal, un imbriquement de clauses IF THEN ELSE permet de distinguer les cas. En Java, le système de trap/catch récupère les cas exceptionnels. Vu la simplicité voulue (voir chapitre Entrée/Sortie) des traitements shell, on repère les cas limites dès le début du programme. Si tel cas ne peut être traité, on affiche un message d’erreur et on sort en fixant le niveau d’erreur à la valeur adéquate. La commande pour sortir d’un script est exit, celle pour sortir d’une fonction est return. En dehors d’une fonction, return se comporte comme exit.
La variable $? permet de récupérer la valeur du code retour de la dernière commande exécutée.
if [ $? –ne 0 ] ; then print "$(basename $0): une erreur est survenue." >&2 exit 1 fi
Sur une erreur d'options ou de paramètres de la ligne de commande, on utilisera plutôt :
(La variable $USAGE a été définie dans la section Aide.)
if [ .. ] ; then print "$USAGE" >&2 exit 1 fi
En traitant un par un de cette manière tous les cas particuliers on est sûr en fin de script de se retrouver dans le cas général qu’il suffit alors de traiter.
Si on ne précise explicitement pas de code retour à la fin d’un script, c’est le code retour de la dernière commande du script qui est renvoyé.
Les commandes pouvant s’enchaîner, il est important d’écrire sur la sortie en erreur d’une part, et de préciser systématiquement le nom de la commande d’autre part.
Entrées/Sorties standards
Le principe des commandes shell est de faire de petits exécutables très simples, mais très précis, et très faciles à interfacer. De leur simplicité découle leur fiabilité, alors que leur facilité à s’interfacer permet de réaliser les tâches les plus complexes sans que cela soit compliqué.
Par convention, un programme lit les données en entrée sur l’entrée standard (descripteur de fichier 0) et restitue les données sur la sortie standard (descripteur de fichier 1). Les avertissements et les erreurs sont affichés sur la sortie d’erreur (descripteur de fichier 2).
De nombreux programmes dérogent à cette règle, préférant lire les données en entrée dans un fichier à préciser sur la ligne de commande et/ou écrire les données en sortie dans un fichier dont on précise le nom, dont le nom est fixe (par exemple, la commande cc compile vers le fichier a.out) ou dont le nom est déduit du fichier en entrée (par exemple, voir la commande compress).
Pour un programme qui lit habituellement ses données dans un fichier dont on donne le nom sur la ligne de commande, par convention, on utilisera le tiret (-) pour spécifier que les données sont à lire sur l’entrée standard (par exemple, voir les commandes cat, tar.)
Pour un programme qui restitue habituellement ses données dans un fichier, on implémente une option pour préciser que l’on veut que ces données soient envoyées sur la sortie standard. C’est en général l’option -c, mais sans qu’il y ait là la moindre convention. Par ailleurs, pour un programme qui restitue habituellement ses données dans un fichier dont le nom est fixe ou déduit, on implémente une option pour préciser le nom du fichier de sortie. C’est en général l’option -o, mais sans qu’il y ait là la moindre convention.
Sur la ligne de commande, pour rediriger une entrée depuis un fichier ou une sortie vers un fichier, on utilise les symboles suivants :
![]()
Donc pour non plus lire les données de sortie à l’écran mais pour les mettre dans un fichier :
La grande force des petites commandes UNIX, c’est qu’on peut les enchaîner facilement grâce à leurs entrées/sorties.
Attention, le fichier system est toujours là, il faut penser à le supprimer. Pour enchaîner deux commandes en prenant comme entrée de la seconde la sortie de la première, comme dans l’exemple précédent, on utilise le caractère |, appelé pipe.
Plutôt que de rediriger l’entrée d’une commande depuis un fichier, on peut donner l’entrée à la suite de la commande, comme si tout ce qui suit, jusqu’à un certain marqueur de fin, était tapé au clavier. On utilise pour cela le symbole << que l’on fait suivre d’un mot qui sert de marqueur. Par convention, on utilise le mot EOF, pour end of file. Ce mot doit être seul sur la dernière ligne sans espace ni devant, ni derrière. Si le caractère - est précisé entre les caractères << et le marqueur, des tabulations sont tolérées devant le marqueur de fin.
Pour rediriger la sortie normale (descripteur de fichier 1) vers la sortie en erreur (descripteur de fichier 2), on utilise le code 1>&2 que l’on peut abréger en >&2. De la même manière, pour rediriger la sortie en erreur vers la sortie normale, on utilise le code 2>&1.
(Les espaces autour des caractères >, < et| ne sont pas obligatoires, c’est juste pour faire joli).
Le principe des commandes shell est de faire de petits exécutables très simples, mais très précis, et très faciles à interfacer. De leur simplicité découle leur fiabilité, alors que leur facilité à s’interfacer permet de réaliser les tâches les plus complexes sans que cela soit compliqué.
Par convention, un programme lit les données en entrée sur l’entrée standard (descripteur de fichier 0) et restitue les données sur la sortie standard (descripteur de fichier 1). Les avertissements et les erreurs sont affichés sur la sortie d’erreur (descripteur de fichier 2).
De nombreux programmes dérogent à cette règle, préférant lire les données en entrée dans un fichier à préciser sur la ligne de commande et/ou écrire les données en sortie dans un fichier dont on précise le nom, dont le nom est fixe (par exemple, la commande cc compile vers le fichier a.out) ou dont le nom est déduit du fichier en entrée (par exemple, voir la commande compress).
Pour un programme qui lit habituellement ses données dans un fichier dont on donne le nom sur la ligne de commande, par convention, on utilisera le tiret (-) pour spécifier que les données sont à lire sur l’entrée standard (par exemple, voir les commandes cat, tar.)
Pour un programme qui restitue habituellement ses données dans un fichier, on implémente une option pour préciser que l’on veut que ces données soient envoyées sur la sortie standard. C’est en général l’option -c, mais sans qu’il y ait là la moindre convention. Par ailleurs, pour un programme qui restitue habituellement ses données dans un fichier dont le nom est fixe ou déduit, on implémente une option pour préciser le nom du fichier de sortie. C’est en général l’option -o, mais sans qu’il y ait là la moindre convention.
Sur la ligne de commande, pour rediriger une entrée depuis un fichier ou une sortie vers un fichier, on utilise les symboles suivants :
Donc pour non plus lire les données de sortie à l’écran mais pour les mettre dans un fichier :
$> uname
AIX
AIX
$> uname > system
$> cat system
AIX
$> cat system
AIX
La grande force des petites commandes UNIX, c’est qu’on peut les enchaîner facilement grâce à leurs entrées/sorties.
$> uname > system $> tr [A-Z] [a-z] < system aix
Attention, le fichier system est toujours là, il faut penser à le supprimer. Pour enchaîner deux commandes en prenant comme entrée de la seconde la sortie de la première, comme dans l’exemple précédent, on utilise le caractère |, appelé pipe.
$> uname | tr [A-Z] [a-z] aix
Plutôt que de rediriger l’entrée d’une commande depuis un fichier, on peut donner l’entrée à la suite de la commande, comme si tout ce qui suit, jusqu’à un certain marqueur de fin, était tapé au clavier. On utilise pour cela le symbole << que l’on fait suivre d’un mot qui sert de marqueur. Par convention, on utilise le mot EOF, pour end of file. Ce mot doit être seul sur la dernière ligne sans espace ni devant, ni derrière. Si le caractère - est précisé entre les caractères << et le marqueur, des tabulations sont tolérées devant le marqueur de fin.
sqlplus /NOLOG <<-EOF > liste_des_tables connect user/password@database select * from tabs ; quit EOF
Pour rediriger la sortie normale (descripteur de fichier 1) vers la sortie en erreur (descripteur de fichier 2), on utilise le code 1>&2 que l’on peut abréger en >&2. De la même manière, pour rediriger la sortie en erreur vers la sortie normale, on utilise le code 2>&1.
(Les espaces autour des caractères >, < et| ne sont pas obligatoires, c’est juste pour faire joli).
Déclarations des variables
Spécial Kornshell
Les variables n’ont pas besoin d’être déclarées avant d’être utilisées, même si cela est très fortement recommandé. On utilise la commande typeset. Plusieurs alias sur cette commande sont définis pour mieux gérer les cas d’utilisation :
functions='typeset -f' Pour déclarer une fonction ;
autoload='typeset -fu' Pour que la définition de la fonction soit donc relue entre chaque appel ;
integer='typeset -i' Pour déclarer un entier ;
local=typeset Pour déclarer une variable locale ;
export='typeset -x' Pour déclarer une variable exportée.
Spécial Kornshell
Les variables n’ont pas besoin d’être déclarées avant d’être utilisées, même si cela est très fortement recommandé. On utilise la commande typeset. Plusieurs alias sur cette commande sont définis pour mieux gérer les cas d’utilisation :
Données temporaires
Spécial UNIX. Certains Linux (debian) disposent de commandes ad hoc.
Les données temporaires utiles au programme peuvent être stockées dans des variables. Il arrive souvent que ces données soient trop volumineuses ou qu’il soit plus pratique de passer par un fichier temporaire. Le répertoire /tmp est fait pour accueillir ces fichiers. Pour créer un fichier dans ce répertoire, il faut se garantir qu’aucun autre fichier ne porte déjà le même nom. Une tradition veut qu’on utilise le nom du processus suivi du numéro de PID du processus (/tmp/$(basename $0).$$) pour créer un nom de fichier unique. Or si à un instant donné, ce numéro est unique dans la liste des processus, rien de garantit que le nom de fichier n’existe pas déjà. De plus, si plusieurs fichiers doivent être créés, il est plus judicieux de créer un répertoire temporaire dans lequel tous les fichiers temporaires de l’application seront stockés. Il ne faut plus trouver un nom unique pour chaque fichier, mais un seul pour le répertoire. La façon traditionnelle de nommage reste intéressante, à condition de lui ajouter une valeur aléatoire, en bouclant sur la génération de l’aléa, jusqu’à obtenir un nom de répertoire qui n’existe pas encore :
Le mode 700 imposé au répertoire créé permet d’ajouter un certain niveau de confidentialité. Personne d'autre que son créateur ne pourra entrer dans le répertoire pour y lire le contenu des fichiers temporaires, ni même ne pourra présumer de l’avancement du processus en analysant la présence ou la taille des fichiers.
Il faut maintenant ne surtout pas oublier de supprimer les fichiers temporaires créés. La tâche est rendue pour le moins triviale car il suffit de supprimer le répertoire :
Pour éviter d’avoir à gérer tous les cas de sortie du programme pour y mettre la suppression de nos données temporaires, il est judicieux de capturer la sortie du programme. C’est le rôle de la commande trap. Un processus qui se termine s’envoie le signal 0. En plus de celui-ci, les principaux signaux à capturer sont SIGHUP (signal 1) et SIGTERM (signal 15). SIGKILL (signal 9) n’est pas capturable. Après un kill -9, il faut donc nettoyer le répertoire /tmp.
Spécial UNIX. Certains Linux (debian) disposent de commandes ad hoc.
Les données temporaires utiles au programme peuvent être stockées dans des variables. Il arrive souvent que ces données soient trop volumineuses ou qu’il soit plus pratique de passer par un fichier temporaire. Le répertoire /tmp est fait pour accueillir ces fichiers. Pour créer un fichier dans ce répertoire, il faut se garantir qu’aucun autre fichier ne porte déjà le même nom. Une tradition veut qu’on utilise le nom du processus suivi du numéro de PID du processus (/tmp/$(basename $0).$$) pour créer un nom de fichier unique. Or si à un instant donné, ce numéro est unique dans la liste des processus, rien de garantit que le nom de fichier n’existe pas déjà. De plus, si plusieurs fichiers doivent être créés, il est plus judicieux de créer un répertoire temporaire dans lequel tous les fichiers temporaires de l’application seront stockés. Il ne faut plus trouver un nom unique pour chaque fichier, mais un seul pour le répertoire. La façon traditionnelle de nommage reste intéressante, à condition de lui ajouter une valeur aléatoire, en bouclant sur la génération de l’aléa, jusqu’à obtenir un nom de répertoire qui n’existe pas encore :
En utilisant la variable $TMPDIR, on obtient le résultat escompté.
local TMPDIR
while true ; do
TMPDIR="/tmp/$(basename $0).$$.$RANDOM"
[ ! -e "$TMPDIR" ] && mkdir -m 700 -p "$TMPDIR" && break || exit 2
done
Le mode 700 imposé au répertoire créé permet d’ajouter un certain niveau de confidentialité. Personne d'autre que son créateur ne pourra entrer dans le répertoire pour y lire le contenu des fichiers temporaires, ni même ne pourra présumer de l’avancement du processus en analysant la présence ou la taille des fichiers.
Il faut maintenant ne surtout pas oublier de supprimer les fichiers temporaires créés. La tâche est rendue pour le moins triviale car il suffit de supprimer le répertoire :
rm –fr $TMPDIR
Pour éviter d’avoir à gérer tous les cas de sortie du programme pour y mettre la suppression de nos données temporaires, il est judicieux de capturer la sortie du programme. C’est le rôle de la commande trap. Un processus qui se termine s’envoie le signal 0. En plus de celui-ci, les principaux signaux à capturer sont SIGHUP (signal 1) et SIGTERM (signal 15). SIGKILL (signal 9) n’est pas capturable. Après un kill -9, il faut donc nettoyer le répertoire /tmp.
La commande cd ~ se justifie par la nécessité que le répertoire courant ne soit pas dans l’arborescence $TMPDIR. Ne pas oublier de mettre la commande exit sans paramètre à la fin de la capture, pour que le processus se termine correctement.
trap "cd ~ ; rm -rf '$TMPDIR' 2>/dev/null ; exit" 0 1 15
Lassé par la pub ? Créez un compte
- Contenus similaires :
Tags :
- ForumTutorial programmation shell
- ForumBonnes pratiques pour batterie ordinateur portable
- ForumTutoriel programmation vba excel
- ForumJavascript objet et bonnes pratiques
- ForumBonnes pratiques pour un portable sous win 7 avec ssdhdd dusine
- ForumBlablaphp faq et bonnes pratiques page 1
- ForumTutoriel shell
- ForumCles usb virus bonnes pratiques informatiques
- ForumShell tuto programmation
- ForumShell ou
- Voir plus