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.
Si vous voulez écrire un programme sh, vous avez deux possibilités :
À 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.
« Mais comment on exécute ce script ? »
Votre script est un simple fichier texte, par défaut il s'ouvre donc avec l'éditeur de texte définit par défaut (ex : Gedit dans une session Unity ou Gnome).
Pour qu'il soit autorisé à se lancer en temps que programme, il faut modifier ses propriétés.
Pour cela faites un clic droit sur son icône, et dans l'onglet "Permissions" des "Propriétés", cochez la case autorisant l'exécution du script.
Par la suite, un double-clic sur l’icône vous laissera le choix entre afficher le fichier (dans un éditeur de texte) et le lancer (directement ou dans un terminal pour voir d'éventuels messages d'erreurs)
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
Il peut être intéressant d'ajouter un répertoire au "PATH" pour pouvoir exécuter ses scripts sans avoir à se placer dans le bon dossier. Je m'explique, quand vous tapez une commande ("ls" par exemple), le shell regarde dans le PATH qui lui indique où chercher le code de la commande.
Pour voir à quoi ressemble votre PATH, tapez dans votre console:
echo $PATH
Cette commande chez moi donnait initialement :
/usr/local/bin:/usr/bin:/bin:/usr/bin/X11:/usr/games
C'est à dire que le shell va aller voir si la définition de la commande tapée ("ls" pour continuer sur le même exemple) se trouve dans /usr/local/bin puis dans /usr/bin… jusqu'à ce qu'il la trouve.
Ajouter un répertoire au PATH peut donc être très pratique. En effet, chez moi, j'ai créé un dossier contenant mes scripts bash utilisés en console : /home/user/scripts_console. Pour pouvoir utiliser mes scripts en tapant directement leur nom (sans le "./") depuis n'importe quel répertoire de mon ordinateur, il me suffit d'indiquer au shell de chercher aussi dans /home/user/scripts_console en l'ajoutant au PATH. Pour ceci, il suffit de faire :
export PATH=$PATH:/home/user/scripts_console
La commande
echo $PATH
retourne maintenant
/usr/local/bin:/usr/bin:/bin:/usr/bin/X11:/usr/games:/home/user/scripts_console
et je peux lancer le script appelé "monScript" situé dans "/home/user/scripts_console" en tapant directement
monScript
Comme vous avez sûrement dû l'entendre, il existe différents types de shells ou en bon français, interpréteurs de commandes :
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 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
Il existe des variables un peu spéciales
| Nom | fonction |
|---|---|
| $* | contient tous les arguments passés à la fonction |
| $# | contient le nombre d'argument |
| $? | contient le code de retour de la dernière opération |
| $0 | contient le nom du script |
| $n | contient l'argument n, n étant un nombre |
| $! | contient le PID de la dernière commande lancée |
Exemple : créer le fichier arg.sh avec le contenu qui suit
#!/bin/bash echo "Nombre d'argument "$# echo "Les arguments sont "$* echo "Le second argument est "$2 echo "Et le code de retour du dernier echo est "$?
Lancez ce script avec un ou plusieurs arguments et vous aurez
./arg.sh 1 2 3 Nombre d'argument 3 Les arguments sont 1 2 3 Le second argument est 2 Et le code de retour du dernier echo est 0
Exemple: Un sleep interactif pour illustrer $!( cf les fonctions )
Pour déclarer un tableau, plusieurs méthodes :
Premiere méthode (compatible bash, zsh, et ksh93 mais pas ksh88)
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[*]}
Pour afficher un élément :
echo ${tab[1]}
Pour afficher tous les éléments :
echo ${tab[@]}
ou bien (en bash ou en ksh93 mais pas en ksh88):
for i in ${!tab[@]}; do echo ${tab[i]}; done
ou encore ( C style )
for (( i=0; i < ${#tab[@]}; i++ )); do echo ${tab[i]}; done
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.
La commande test existe sous tous les unix, elle permet de faire un test et de renvoyer 0 si tout s'est bien passé ou 1 en cas d'erreur.
En mode console, faites man test pour connaître tous les opérateurs, en voici quelques uns :
| Syntaxe | Fonction réalisée |
|---|---|
| -e fichier | renvoie 0 si fichier existe. |
| -d fichier | renvoie 0 si fichier existe et est un répertoire. |
| -f fichier | renvoie 0 si fichier existe et est un fichier 'normal'. |
| -w fichier | renvoie 0 si fichier existe et est en écriture. |
| -x fichier | renvoie 0 si fichier existe et est exécutable. |
| f1 -nt f2 | renvoie 0 si f1 est plus récent que f2. |
| f1 -ot f2 | renvoie 0 si f1 est plus vieux que f2. |
| Syntaxe | Fonction réalisée |
|---|---|
| $A -lt 5 | renvoie 0 si $A est strictement inférieur à 5 |
| $A -le 5 | renvoie 0 si $A est inférieur ou égal à 5 |
| $A -gt 5 | renvoie 0 si $A est strictement supérieur à 5 |
| $A -ge 5 | renvoie 0 si $A est supérieur ou égal à 5 |
| $A -eq 5 | renvoie 0 si $A est égal à 5 |
| $A -ne 5 | renvoie 0 si $A est différent de 5 |
On peut raccourcir la commande test par des crochets. Exemple :
test -f /etc/passwd echo $? 0 [ -f /etc/passwd ] echo $? 0
Affichera la valeur 0 : ce fichier existe, 1 dans le cas où le fichier /etc/passwd n'existe pas. Sous unix, le code de retour est par convention et en général 0 s'il n'y a aucune erreur et différent de 0 dans les autres cas
La syntaxe la plus appropriée dans de la programmation shell moderne est le double crochet :
[[ -f /etc/passwd ]]
Cela gère bien mieux les problèmes d'espace dans les noms de fichiers, les erreurs etc… C'est une structure propre à bash (ksh, ?) qui est le shell par défaut dans la plupart des distributions Linux, et de Ubuntu en particulier. On garde en général des simples crochets pour les scripts shell qui doivent être à tout prix POSIX. ( Utilisation sur des Unix sans installation préalables de bash, comme BSD, Solaris… )
Il y a en 3 :
Exemple :
echo "renverra 0 si les deux expressions sont vraies" test expr 1 -a expr2 [ expr1 -a expr2 ]
| 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.
| 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.
Exemple plus complet :
#!/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é.
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 « $ ».
#!/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
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. La condition doit bien être séparée des crochets par un espace ! 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 exécute la commande suivante si la commande précédente n'a pas renvoyé 0. Il existe aussi l'opérateur && qui exécute la commande suivante si commande précédente a renvoyé 0, et enfin ; qui exécute l'opération suivante dans tous les cas.
Exemple si le répertoire toto n' existe pas , le créer
[ ! -d /tmp/toto ] && mkdir /tmp/toto [ -d /tmp/toto ] || mkdir /tmp/toto test ! -d /tmp/toto && mkdir /tmp/toto rm -rf /tmp/toto;mkdir /tmp/toto
Les « { » servent à bien délimiter le bloc d'instructions suivant le `then`, est une commande et donc si elle est sur la même ligne que le `if` les deux commandes doivent être séparées par un `;`
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.
Quelques petites commandes pratiques :
sh -n nom_du_fichier
ou
bash -x chemin_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.
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`). Vous allez dire :
`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`.
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 à 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
La seule chose qui mérite vraiment d'être expliquée est sans doute `* )`. 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…
Pour vous donner une idée précise de ce que peuvent réaliser toutes ces instructions, voici 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 s'est connecté dernièrement";; 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
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 il est préférable de bien structurer le code que vous écrivez.
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 *.txt; do echo "$var" done exit 0
On peut voir une syntaxe un peu particulière :
$(sort *.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 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.
Les fonctions peuvent être définies n'importe ou dans le code du moment qu'elle soit définie avant d'être utilisée.Même si en bash les variables sont gloabales, il est possible de les déclarer comme local au sein d'un fonction en la précédant du mot clé local: local ma_fonction
Exemple: Un sleep interactif.
#!/bin/bash function info(){ echo -e "$1\nBye" exit } test -z "$1" && info "requiere 1 argument pour le temps d'attente..." || PRINT=$(($1*500)) test -z $(echo "$1" | grep -e "^[0-9]*$") && info "'$1' est un mauvaise argument" test $1 -gt 0 || info "Je prend que les entiers > 0" function print_until_sleep(){ local COUNT=0 while [ -d /proc/$1 ]; do test $(($COUNT%$2)) -eq 0 && echo -n "*" COUNT=$(($COUNT+1)) done } sleep $1 & print_until_sleep $! $PRINT echo -e "\nBye"
Qui n’a jamais voulu faire un script avec des couleurs pour pouvoir différencier les titres des paramètres et les paramètres de leur valeur par exemple…
Comme toute commande sous Linux, il faut utiliser une syntaxe par défaut et y passer quelques paramètres. Pour les couleurs au sein de scripts shell, c’est le même principe.
echo -e '\033[A;B;Cm toto \033[0m'
Dans la commande passée ci-dessus, nous pouvons constater qu’il y a 3 paramètres présents, A, B et C. A : correspond à un effet affecté au texte affiché B : correspond à la couleur du texte C : identifie la couleur du fond du texte affiché
Et enfin on termine notre affichage avec « \033[0m », qui spécifie au terminal de revenir aux couleurs définies par défaut.
Nous allons commencer par les différents effets possible :
| Code | Effet |
|---|---|
| 0 | Normal |
| 1 | Gras |
| 21 | Non-gras |
| 4 | Souligné |
| 24 | Non souligné |
| 5 | Clignotant |
| 25 | Non-clignotant |
| 7 | Inversé |
| 27 | Non-inversé |
Maintenant que nous avons présenté les différents effets possibles d’attribuer à du texte, nous allons nous attaquer aux couleurs. Chaque couleur à 2 valeurs,la première utilisée pour la couleur du texte, et la seconde pour la couleur du fond.
| Couleur | Couleur texte | Couleur fond |
|---|---|---|
| Noir | 30 | 40 |
| Rouge | 31 | 41 |
| Vert | 32 | 42 |
| Jaune | 33 | 43 |
| Bleu | 34 | 44 |
| Magenta | 35 | 45 |
| Cyan | 36 | 46 |
| Blanc | 37 | 47 |
echo -e '\033[1;30;47m toto \033[0;32m est sur \033[1;33m un bateau \033[0m'
Comme indiqué dans la section liens de cette page, de très bon exemples et exercices illustrent le cours disponible sur cette page :
Guide avancé d'écriture des scripts Bash - Une exploration en profondeur de l'art de la programmation shell
Aux structures décrites ci-dessus, il est nécessaire, pour réaliser des scripts poussés, de connaître les commandes shell les plus usitées.
Vous en trouverez une présentation sur cette autre page du wiki : initiation_au_shell
La programmation de script shell étant ouverte à tous, cela permet de bénéficier de nombreux scripts pour des applications très variées ; cependant, la plupart sont proposés sans aucune garantie.
Vous pourrez trouver une liste de scripts pouvant servir d'exemple sur la page scripts utiles du wiki.
Une fois vos armes faites, proposez vos contributions sur le topic du forum [VOS SCRIPTS UTILES] et rajoutez un lien dans la page du wiki ci-dessus.
#!/bin/bash # Version du script
exit 0;
Ce qui indique que votre script s'est exécuté correctement.
nom_de_la_fonction() { ... }
$CHEMIN_DU_DOSSIER/$NOM_DU_FICHIER
nom_de_la_fonction $1 $2 $3 ....
exit 100; exit 101; exit 102; ....
Ça permettra d'identifier d'où vient l'erreur.
erreur() { tab=( ${PIPESTATUS[@]} ) for (( i=0; i < ${#tab[@]}; i++ )); do ((i+=i)); done if ((i > 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…
ou
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.
—- Contributeurs: Gapz, Gloubiboulga ,sparky et deax_one