[[tutoriel:script_shell]]
Piste: » script_shell
Introduction aux scripts shell
Présentation
Un script shell permet d'automatiser une série d'opérations. Il se présente sous la forme d'un fichier contenant une ou plusieurs commandes qui seront exécutées de manière séquentielle.
L'art d'écrire un script
- Des vérifications approfondies doivent être effectuées sur TOUTES les commandes utilisées.
- Des commentaires détaillés doivent apparaître lors de chaque étape. De même, chaque étape doit être suivie d'un "echo <voici ce que je fais>" (particulièrement utile notamment lors du débogage).
- Lors d'une mise à jour, un fil de discussion doit être précisé pour tracer les bugs éventuels.
- Avertir les utilisateurs des dégâts que peuvent causer les commandes utilisées. (Ces deux dernières remarques ne concernent bien sûr que les scripts que l'on souhaite diffuser.)
- Commencer par :
#!/bin/bash # Version du script
- Écrire les variables en majuscule et NE PAS choisir des noms de commandes (ping , ls, …) de même pour les noms de fonctions
- À la fin de vos scripts, ajouter impérativement :
exit 0;
Ce qui indique que votre script s'est exécuté correctement.
- Créer des fonctions pour des actions précises :
function nom_de_la_fonction { ... } - Utiliser des chemins absolu pour les dossiers précis et relatif pour les nom de fichiers
$CHEMIN_DU_DOSSIER/$NOM_DU_FICHIER
- Utiliser les entrées de commandes pour les fonctions :
nom_de_la_fonction $1 $2 $3 ....
- Si votre script doit s'arrêter à cause d'une erreur, d'une variable qui ne correspond pas a vos attentes utiliser des numéros exit différents :
exit 100; exit 101; exit 102; ....
Ça permettra d'identifier d'où vient l'erreur.
- Utiliser la variable $PIPESTATUS pour récupérer les états des autres commandes. $PIPESTATUS est égal à 0 si la commande précédente s'est terminé correctement.
- Écrire au moins une fonction erreur du type :
function erreur { if [ "$PIPESTATUS" != "0" ]; then zenity --error --title="Une erreur est survenue" --text="Une erreur est survenue " exit 100; fi }ainsi après chaque commande vous pouvez donner des codes d'exécutions différents.
Évidemment ces conseils sont utiles au débutant…
Exemple : Supposons que vous avez une base de données, avec 3 catégories d'enregistrements possibles : éléphant bleu, éléphant blanc, éléphant rose ayant chacun 30 individus. Votre script doit compter le nombre d'éléphants bleus et blancs. Deux possibilités s'offrent à vous :
- calculer le nombre d'éléphants bleus + éléphants blancs
ou
- calculer le nombre total d'éléphants - nombre d'éléphants roses
Quel algorithme choisissez-vous ?
Résultat : Le premier car dans le deuxième il faut d'abord calculer le nombre total d'éléphants, donc un calcul en plus.
Écrire un script
Si vous voulez écrire un programme sh, vous avez deux possibilités :
- soit vous tapez dans un shell toutes les commandes
- ou alors vous rassemblez toutes les instructions copiées par ci par là dans un fichier sh.
À titre d'exemple, saisissez ces quelques lignes dans votre éditeur préféré :
#!/bin/bash # indique au système que l'argument qui suit est le programme utilisé pour exécuter ce fichier. # En cas général les "#" servent à faire des commentaires comme ici echo Mon premier script echo Liste des fichiers : ls -la exit 0
Le résultat de ce script est d'écrire à l'écran « Mon premier script », puis
en dessous « Liste des fichiers : », et enfin la liste des fichiers avec la
commande `ls -la`.
Comme vous l'avez compris, la commande `echo` sert à écrire
quelque chose à l'écran.
Exécuter un script
D'ici, j'entends déjà les débutants dire : « Mais comment on exécute ce script ? »
Et bien il suffit de se placer dans le dossier où est le script, et de lancer
bash nom_du_script
Si vous voulez l'exécuter avec « . », il faut le rendre exécutable avec `chmod`. Pour ceci tapez dans le shell la commande qui suit :
chmod +x nom_du_script
Puis vous pouvez exécuter le script en faisant :
./nom_du_script
Les différents types de shells
Comme vous avez sûrement dû l'entendre, il existe différents types de shells ou en bon français, interpréteurs de commandes :
- dash :
le shell par défaut sur Ubuntu ; - bash (Bourne Again SHell) : conçu par le projet GNU, shell linux ; le shell par défaut sur Ubuntu ;
- rbash : un shell restreint basé sur bash. Il existe de nombreuses variantes de bash ;
- csh, tcsh : shells C, créés par Bill Joy de Berkeley ;
- zsh, shell C écrit par Paul Falstad ;
- ksh,pdksh : shells korn, écrits par David Korn ;
- rc : shell C, lui aussi conçu par le projet GNU ;
- tclsh : shell utilisant Tcl ;
- wish : shell utilisant Tk ;
Il existe bien entendu beaucoup d'autres types de shells.
La commande sh est en fait un lien symbolique vers l'interpréteur de commandes par défaut : /bin/dash pour Ubuntu, /bin/bash est également très répandu.
Les variables
Pour appeller une variable par exemple HOME il suffit de mettre un $ devant, par exemple :
echo $HOME
Ce petit code va afficher la variable HOME à l’écran.
Pour affecter une valeur à une variable c’est très simple.
MSG=salut echo $MSG
Les tableaux
Pour déclarer un tableau, plusieurs méthodes :
tab=("John Smith" "Jane Doe")
ou bien
tab[0]=John Smith tab[1]=Jane Doe
Pour compter le nombre d'éléments du tableau :
len=${#tab[*]}
echo len
Pour afficher un élément :
echo ${tab[1]}
Pour afficher tous les éléments :
len=${#tab[*]}
i=0
while [ "$i" -lt "$len" ]
do
echo ${tab[$i]}
let "i = $i + 1"
done
Les arguments en ligne de commande
Pour passer des arguments en ligne de commande c’est encore une fois très simple. Chaque argument est numéroté et ensuite on l’appelle par son numéro :
./test.sh powa noplay
Voici notre test.sh
#!/bin/sh echo $2 echo $1
Notez que $0 est le nom du fichier.
Les structures de contrôle
Les tests : `if`
Avant de commencer à faire des scripts de 1000 lignes, il serait intéressant
de voir comment se servir des variables, et des instructions if, then, elif, else, fi. Cela permet par exemple de faire réagir le script de manière différente, selon la réponse de l'utilisateur à une question.
En bash, les variables ne se déclarent généralement pas avant leur utilisation, on les utilise directement et elles sont créées lors de sa première mise en œuvre.
Pour pouvoir voir la valeur d'une variable il faut faire précéder son nom du caractère « $ ».
Exemple
#!/bin/sh
echo -n "Voulez-vous voir la liste des fichiers Y/N : "
read ouinon
if [ "$ouinon" = "y" ] || [ "$ouinon" = "Y" ]; then
{
echo "Liste des fichiers :"
ls -la
}
elif [ "$ouinon" = "n" ] || [ "$ouinon" = "N" ]; then
{
echo "Ok, bye! "
}
else
{
echo "Il faut taper Y ou N!! Pas $ouinon"
}
fi
Explication
Ce script peut paraître simple à première vue mais certaines choses prêtent à confusion et ont besoin d'être expliquées en détail.
Tout abord, le `echo -n` permet de laisser le curseur sur la même ligne, ce
qui permet à l'utilisateur de taper la réponse après la question (question
d'esthétique).
L'instruction `read` permet d'affecter une valeur ou un caractère à une variable quelconque, en la demandant à l'utilisateur.
Ensuite vient l'instruction conditionnelle `if`. Elle est suivie d'un « [ » pour délimiter la condition. Attention, la variable est mise entre guillemets car dans le cas où la variable est vide, le shell ne retourne pas d'erreur, mais en cas contraire, l'erreur produite ressemble à :
[: =: unaryoperator expected
L'opérateur `||` signifie « ou » (il existe aussi `&&` pour « et »).
On peut définir une table de vérité pour ces deux opérateurs, où 1 représente une assertion vraie et 0 une assertion fausse :
Table de vérité de « || »
| Comparaison | Résultat | Calcul |
|---|---|---|
| 0 ou 0 | 0 | 0 + 0 = 0 |
| 0 ou 1 | 1 | 0 + 1 = 1 |
| 1 ou 0 | 1 | 1 + 0 = 1 |
| 1 ou 1 | 1 | 1 + 1 = 1 |
Dès que l'une des deux assertions est vérifiée, la condition globale l'est aussi.
Table de vérité de « && »
| Comparaison | Résultat | Calcul |
|---|---|---|
| 0 et 0 | 0 | 0 × 0 = 0 |
| 0 et 1 | 0 | 0 × 1 = 0 |
| 1 et 0 | 0 | 1 × 0 = 0 |
| 1 et 1 | 1 | 1 × 1 = 1 |
Les deux assertions doivent être vérifiées pour que la condition le soit aussi.
Enfin, on ferme le crochet, suivi d'un point virgule ou d'un saut de ligne pour exécuter la commande `then` qui applique ce qui vient après, si la condition est respectée.
Les « { » servent à bien délimiter le bloc d'instructions suivant le `then`, cela permet juste de facilement lire le code, de mieux se repérer.
Ensuite, `elif` sert à exécuter une autre série d'instructions, si la condition décrite par `if` n'est pas respectée, et si celle fournie après ce `elif` l'est.
Enfin, `else` sert à exécuter un bloc si les deux conditions précédentes ne sont pas respectées (ah les jeunes, ils respectent plus rien de nos jours
).
`fi` indique la fin de notre bloc d'instructions `if`, ce qui permet de voir où se termine toute notre portion de code soumise à une condition.
J'ai quelques petites commandes pratiques à vous donner :
sh -n nom_du_fichier
Cette commande vérifie la syntaxe de toutes les commandes du script, pratique quand on débute et pour les codes volumineux.
sh -u nom_du_fichier
Celle-ci sert à montrer les variables qui n'ont pas été utilisées pendant l'exécution du programme.
Voici le tableau des opérateurs de comparaison, ceux-ci peuvent s'avérer utiles pour diverses raisons, nous verrons un peu plus loin un exemple :
Opérateurs de comparaison
| Syntaxe | Fonction réalisée |
|---|---|
| -e fichier | Vrai si fichier existe. |
| -d fichier | Vrai si fichier existe et est un répertoire. |
| -f fichier | Vrai si fichier existe et est un fichier 'normal'. |
| -w fichier | Vrai si fichier existe et est en écriture. |
| -x fichier | Vrai si fichier existe et est exécutable. |
| f1 -nt f2 | Vrai si f1 est plus récent que f2. |
| f1 -ot f2 | Vrai si f1 est plus vieux que f2. |
Exemple, vérifier qu'un fichier existe :
#!/bin/sh echo -n "Entrez un nom de fichier: " read file if [ -e "$file" ]; then { echo "Le fichier existe!" } else { echo "Le fichier n'existe pas, du moins n'est pas dans le répertoire d'exécution du script" } fi exit 0
La seule chose qui prête à confusion est que l'on vérifie seulement si le fichier « file » est dans le répertoire où le script à été exécuté.
La commande `while`
La commande while exécute ce qu'il y a dans son bloc tant que la condition
est respectée :
#!/bin/sh
cmpt=1
cm=3
echo -n "Mot de passe : "
read mdp
while [ "$mdp" != "ubuntu" ] && [ "$cmpt" != 4 ]; do
echo -n "Mauvais mot de passe, plus que "$cm" chance(s): "
read mdp
cmpt=$(($cmpt+1))
cm=$(($cm-1))
done
echo "Non mais, le brute-force est interdit en France !!"
exit 0
On retrouve des choses déjà abordées avec `if`. Le `&&` sert à symboliser un "et", cela implique que deux conditions sont à respecter. Le `do` sert à exécuter ce qui suit si la condition est respectée. Si elle ne l'est pas, cela saute tout le bloc (jusqu'à `done`). Je vous vois d'ici dire :
Cette partie du code sert tout simplement à réaliser une opération arithmétique. A chaque passage, 'cmpt = cmpt+1' et 'cm = cm-1'.
`while` permet de faire exécuter la portion de code un nombre déterminé de fois. La commande `until` fait la même chose que la commande `while` mais en inversant. C'est-à-dire qu'elle exécute le bloc jusqu'à ce que la condition soit vraie, donc elle s'emploie exactement comme la commande `while`.
La commande `case`
Regardons la syntaxe de cette commande, qui n'est pas une des plus simples :
case variable in
modèle [ | modèle] ...) instructions;;
modèle [ | modèle] ...) instructions;;
...
esac
Cela peut paraître complexe mais on s'y habitue quand on l'utilise.
Mais a quoi sert cette commande ?
Elle sert à comparer le contenu d'une variable à des modèles différents. Les ;; sont indipensables car il est possible de placer plusieurs instructions entre un modèle et le suivant. Les ;; servent donc à identifier clairement la fin d'une instruction et le début du modèle suivant.
Exemple :
#!/bin/sh echo -n "Etes-vous fatigué ? " read on case "$on" in oui | o | O | Oui | OUI ) echo "Allez faire du café !";; non | n | N | Non | NON ) echo "Programmez !";; * ) echo "Ah bon ?";; esac exit 0
Je crois que la seule chose qui mérite vraiment d'être expliquée est `* )`. Cela indique tout simplement l'action à exécuter si la réponse donnée n'est aucune de celles données précédemment.
Il existe aussi plusieurs structures pour les modèles, telles que :
case "$truc....." in [nN] *) echo "Blablabla...";; n* | N* ) echo "Bla....";;
Et plein d'autres encore…
On mélange tout ça
Pour vous donner une idée précise de ce que peuvent réaliser toutes ces instructions, j'ai fait un petit script censé refaire un prompt avec quelques commandes basiques :
#!/bin/bash clear echo echo #################### Script ############################ echo echo ############################# echo -n LOGIN: read login echo -n Hôte: read hote echo ############################# echo echo ### Pour l'aide tapez help ### echo while [ 1 ]; do # permet une boucle infinie echo -n ""$login"@"$hote"$ " # qui s'arrête avec break read reps
case $reps in help | hlp ) echo A propos de TS --> about echo ls --> liste les fichiers echo rm --> détruit un fichier (guidé) echo rmd --> efface un dossier (guidé) echo noyau --> version du noyau linux echo connect --> savoir qui est c'est dernièrement connecté;; ls ) ls -la;; rm ) echo -n Quel fichier voulez-vous effacer : read eff rm -f $eff;; rmd | rmdir ) echo -n Quel répertoire voulez-vous effacer : read eff rm -r $eff;; noyau | "uname -r" ) uname -r;; connect ) last;; about | --v | vers ) echo Script simple pour l'initiation aux scripts shell;; quit | "exit" ) echo Au revoir!! break;; * ) echo Commande inconnue;; esac done exit 0
Remarque
Comme vous l'avez remarqué, l'indentation a une place importante dans ce programme. En effet, celui-ci est plus lisible et cela évite aussi de faire des erreurs. C'est pourquoi je vous recommande de bien structurer le code que vous écrivez.
La commande for
L'instruction `for` exécute ce qui est dans son bloc un nombre de fois prédéfini. Sa syntaxe est la suivante :
for variable in valeurs; do instructions done
Comme vous l'aurez sans doute remarqué, on assigne une valeur différente à variable à chaque itération. On peut aussi très facilement utiliser des fichiers comme "valeur". Rien ne vaut un exemple :
#!/bin/sh for var in $(ls *.txt); do echo $var done exit 0
On peut voir une syntaxe un peu particulière : `$(ls *.txt)`. Ceci sert à indiquer que ce qui est entre les parenthèses est une commande à exécuter.
On peut aussi utiliser cette instruction simplement avec des nombres, cela permet de connaître le nombre d'itérations :
#!/bin/sh for var in 1 2 3 4 5 6 7 8 9; do echo $var done exit 0
On peut très bien aussi utiliser d'autres types de variables, comme par exemple des chaînes de caractères :
#!/bin/sh for var in Ubuntu Breezy 5.10; do echo $var done exit 0
Il faut quand même faire attention au fait que Ubuntu Breezy 5.10 est différent de "Ubuntu Breezy 5.10" dans ce cas. En effet, tous les mots placés entre "" sont considérés comme faisant partie de la même chaîne de caractères. Sans les "", sh considèrera qu'il y a une liste de trois chaînes de caractères.
Les fonctions
Les fonctions sont indispensables pour bien structurer un programme mais aussi pouvoir le simplifier, créer une tâche, la rappeler… Voici la syntaxe générale de 'déclaration' d'une fonction :
nom_fonction(){
instructions
}
Cette partie ne fait rien en elle même, elle dit juste que quand on appellera nom_fonction, elle fera instruction. Pour appeler une fonction (qui ne possède pas d'argument, voir plus loin) rien de plus simple :
nom_fonction
Rien ne vaut un petit exemple :
#!/bin/sh
#Definition de ma fonction
mafonction(){
echo 'La liste des fichiers de ce répertoire'
ls -l
}
#fin de la définition de ma fonction
echo 'Vous allez voir la liste des fichiers de ce répertoire:' mafonction #appel de ma fonction exit 0
Comme vous l'avez sans doute remarqué, quand on appelle la fonction, on exécute simplement ce qu'on lui a défini au début, dans notre exemple, echo… et ls -l, on peut donc faire exécuter n'importe quoi à une fonction.
Liens
- (fr) http://marcg.developpez.com/ksh/ : Pour ceux qui souhaitent aller plus loin dans la conception de script shell, je vous recommande ce cours de chez developpez.com.
- (fr) http://abs.traduc.org/abs-5.3-fr/index.html : Un très bon tutoriel concernant la réalisation du script shell. C'est le plus complet et le mieux détaillé que je connaisse en français. Il contient également des exemples de script complets, une carte de référence (variables, tests…). Ce site est un site vaut réellement le détour pour tous ceux qui cherchent à créer des scripts complets en utilisant au mieux les performances du shell.
- (en) Bash parameters and parameter expansions. En anglais mais contient de nombreux exemples concernant la gestion et l'analyse des paramètres.
Contributeurs: Gapz et Gloubiboulga
