Les commandes fondamentales de Linux/Programmation bash (script)
Sommaire
Introduction à la programmation en bash
- Premier auteur : Nat Makarevitch
Lire au préalable « Les commandes fondamentales de Linux ».
Nous n'exposerons pas tous les aspects de la programmation bash. Consulter pour cela le manuel: man bash.
Le shell bash, comme les autres (korn shell, C shell), permet de lancer des exécutables et des commandes internes, d'interconnecter des processus au moyen de pipes...
Lors de traitements répétitifs on peut éviter de saisir de nombreuses fois un groupe de commandes donné. Il suffit pour cela d'en faire un script, qui les rassemble. S'il s'agit d'une commande simple mieux vaut employer un alias. Dans tous les cas une nouvelle commande simple déclenchera l'exécution du script ou de l'alias, donc des traitements souhaités grâce à de la plomberie.
Développer
Débogage
Voici des dispositions facilitant la mise au point. Elles sont cumulables, donc ne pas hésiter à les employer toutes.
Vérifier le respect de la syntaxe
bash -n ''NomDuScript''
Pour que ce soit fait lors de chaque exécution du script y placer une ligne:
set -o noexec
Ce n'est pas recommandé car le script ne fonctionne dès lors plus, l'invoquer ne déclenche qu'une vérification syntaxique.
Obtenir une modeste trace
Chaque commande apparaît lors de son exécution.
bash -v ''NomDuScript''
Pour que ce soit fait lors de chaque exécution du script y placer une ligne:
set -o verbose
Debugger
Exemple
Imaginons par exemple que nous souhaitions disposer d'une nouvelle commande nommée "p1" qui produira la liste des processus correspondants à une commande donnée. On souhaite que "p1" soit générique, donc qu'elle puisse chercher n'importe quelle commande. En résumé on souhaite pouvoir saisir "p1 nom_de_commande" au lieu de ps auxw | grep nom_de_commande, donc par exemple "p1 bash" plutôt que, comme décrit ci-devant, ps auxw | grep bash. L'utilitaire classique nommé « pgrep » assure bien cette fonction, mais développons afin d'explorer.
Règles communes à tous les scripts bash:
- la première ligne de tout script bash doit invoquer le shell ainsi: #! /bin/bash (l'espace entre le "!" et le "/" n'est pas obligatoire mais il améliore la lisibilité)
- les paramètres passés par l'utilisateur du script lorsqu'il l'invoquera (via une ligne de commande) sont, au sein du script, des variables nommées "$1" pour le premier, "$2" pour le deuxième, "$3" pour le troisième, etc ... "$@" recèle l'ensemble de ces arguments et "$0" le nom de la commande.
Il suffit donc de créer un fichier nommé "p1" qui contiendra:
#! /bin/bash set -o verbose set -o nounset ps auxw | grep $1
(nounset sera expliqué un peu plus loin)
"$1" est pour bash, par convention, une variable contenant le premier paramètre passé au script lors de son invocation.
Rendre ce script exécutable: chmod +x p1
Il sera dès lors possible de l'invoquer. Exemple: ./p1 bash
Améliorons-le de sorte qu'il détecte une utilisation incorrecte car sans argument. "$#" contient le nombre d'arguments et "$0" contient le nom du script invoqué. Résultat:
#! /bin/bash set -o verbose set -o nounset if [[ $# -ne 1 ]] ; then echo "Pour m'utiliser invoquer: $0 NomDuProgramme" exit 4 fi
Compléter la trace
Chaque résultat de commande apparaît.
bash -x ''NomDuScript''
Pour que ce soit fait lors de chaque exécution du script y placer une ligne:
set -o xtrace
Contraindre à déclarer toute variable
Cela facilite la détection de nombreux bugs.
Pour que ce soit fait lors de chaque exécution du script y placer une ligne:
set -o nounset
Autre exemple
Le script suivant montre ce que contient un fichier ".tar.gz" dont le nom lui est communiqué en argument:
#!/bin/bash tar tvzf "$1"
Il serait plus utile de pouvoir décider de déclencher le désarchivage:
#!/bin/bash tar tvzf "$1" echo -n "Voulez vous désarchiver $1? (o/n): " read archi if [ "$archi" = o ] || [ "$archi" = O ] || [ "$archi" = oui ] || [ "$archi" = OUI ] ; then tar xzpf "$1" fi
Une commande employée dans un programme, par exemple (ici!) un script, est parfois appelée instruction.
L'instruction echo affiche un message et son option "n" lui interdit de produire un retour chariot en fin de ligne.
L'instruction read attend une réponse de l'utilisateur et la stocke dans la variable dont le nom lui est fourni en argument (ici: archi).
Les crochets ([ ]) encadrent tous types d'expressions.
L'instruction if permet de tester la valeur de la réponse donnée par l'utilisateur.
Voici la construction typique d'une l'instruction if (qui forme une « clause conditionnelle »):
if (condition) then instruction else instruction fi
fi (if lu à l'envers) marque la fin de la clause.
Si vous souhaitez insérer plusieurs conditions:
if (condition) then instruction elif (condition) then instruction else instruction fi
elif est la forme contractée de "else if" (exprimant « sinon, si ... »).
Le script peut proposer un menu offrant de choisir entre une décompression immédiate ou un examen du contenu:
#!/bin/bash PS3='votre choix ?' select choix in "tar tvzf" "tar xvzf" do $choix "$1"; done
select facilite la création de menus.
"PS3" est une variable stockant un prompt utilisé par select.
"choix" est le nom de la variable qui contiendra un des éléments de la suite qui suit le mot-clé in. Dans notre cas, "choix" contiendra soit la chaîne "tar tvzf" ou la chaîne "tar xvzf".
Dans la construction do... done, nous plaçons les instructions que nous voulons exécuter. Tout ensemble d'instructions ainsi groupées, par exemple (mais pas uniquement) par do... done, est appelé un bloc. Elle sont ici exécutées en boucle, pour sortir utilisez la combinaison de touches Ctrl-c (maintenir la touche "Ctrl" ou "Control" enfoncée et taper sur la touche du caractère 'c').
"$choix" contiendra donc soit "tar tvzf" soit "tar xvzf" et "$1" contiendra l'argument (ici le nom du fichier compressé) fourni lors de l'invocation du script.
Si notre script s'appelle "ctgz", son exécution se déroulera ainsi: ./ctgz nom_de_fichier.tar.gz
1) tar tvzf 2) tar xvzf votre choix ?
L'utilisateur n'a plus qu'à taper "1" ou "2".
Voici une amélioration par ajout de 4 lignes (les premières) visant à s'assurer que l'argument fourni est bien le nom d'un fichier et qu'il est possible de lire ce dernier:
#!/bin/bash if [ ! -r "$1" ] ; then echo Je ne peux lire le fichier "$1" exit 4 fi PS3='votre choix ?' select choix in "tar tvzf" "tar xvzf" do $choix "$1"; done
Le point d'exclamation marque la négation, donc la première ligne se lit:
if [ ! -r "$1" ] ; then si ( NON de (fonction déterminant si existe en tant que fichier et est lisible) appliquée à l'argument_fourni_par_l'utilisateur ) , alors
for permet de répéter l'exécution d'un bloc. Sa syntaxe est très proche de celle de select:
for nom [in liste] do instructions utilisant $nom done
Exemple: Le script suivant liste le contenu de plusieurs fichiers compressés. La variable "$@" contient les arguments communiqués par l'utilisateur lors de l'invocation du script (en théorie il s'agit de la liste des noms des fichiers):
#!/bin/bash for nomfichier in "$@" do echo "$nomfichier" tar tvzf "$nomfichier" done
while exécute le bloc tant que la condition reste vraie. until exécute le bloc jusqu'à ce qu'elle le devienne.
Exemple:
#!/bin/bash until tar tvzf "$1"; do echo "tentative de décompression" read done
Avec cette boucle, tant que le fichier n'aura pas pu être décompressé et désarchivé, tar sera exécuté indéfiniment ... pour en sortir utilisez la combinaison de touches Ctrl-c.