Les commandes fondamentales de Linux/Programmation bash (script)

De Linux France

Introduction à la programmation en bash[modifier]

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[modifier]

Débogage[modifier]

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[modifier]

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[modifier]

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[modifier]

Un débogueur existe.

Exemple[modifier]

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 aide à 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[modifier]

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[modifier]

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[modifier]

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.