IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Introduction à la programmation en Bash


précédentsommairesuivant

I. Introduction à bash

I-1. Les shells

Sous Unix, on appelle shell l’interpréteur de commandes qui fait office d'interface entre l'utilisateur et le système d’exploitation. Les shells sont des interpréteurs : cela signifie que chaque commande saisie par l’utilisateur (ou lue à partir d’un fichier) est syntaxiquement vérifiée puis exécutée.

Il existe de nombreux shells qui se classent en deux grandes familles :

  • la famille C shell (ex : csh, tcsh)
  • la famille Bourne shell (ex : sh, bash, ksh).

zsh est un shell qui contient les caractéristiques des deux familles précédentes. Néanmoins, le choix d’utiliser un shell plutôt qu’un autre est essentiellement une affaire de préférence personnelle ou de circonstance. En connaître un, permet d’accéder aisément aux autres. Lorsque l’on utilise le système GNU/Linux (un des nombreux systèmes de la galaxie Unix), le shell par défaut est bash (Bourne Again SHell). Ce dernier a été conçu en 1988 par Brian Fox dans le cadre du projet GNU. Aujourd’hui, les développements de bash sont menés par Chet Ramey.

Un shell possède un double aspect :

  • un aspect environnement de travail
  • un aspect langage de programmation.

I-1-1. Un environnement de travail

En premier lieu, un shell doit fournir un environnement de travail agréable et puissant. Par exemple, bash permet (entre autres) :

  • le rappel des commandes précédentes (gestion de l’historique) ; cela évite de taper plusieurs fois la même commande
  • la modification en ligne du texte de la commande courante (ajout, retrait, remplacement de caractères) en utilisant les commandes d’édition de l’éditeur de texte vi ou emacs
  • la gestion des travaux lancés en arrière-plan (appelés jobs) ; ceux-ci peuvent être démarrés, stoppés ou repris suivant les besoins
  • l’initialisation adéquate de variables de configuration (chaîne d’appel de l’interpréteur, chemins de recherche par défaut) ou la création de raccourcis de commandes (commande alias).

Illustrons cet ajustement de configuration par un exemple. Le shell permet d’exécuter une commande en mode interactif ou bien par l'intermédiaire de fichiers de commandes (scripts). En mode interactif, bash affiche à l’écran une chaîne d’appel (appelée également prompt ou invite), qui se termine par défaut par le caractère # suivi d’un caractère espace pour l’administrateur système (utilisateur root) et par le caractère $ suivi d’un caractère espace pour les autres utilisateurs. Cette chaîne d’appel peut être relativement longue.

 
Sélectionnez
sanchis@jade:/bin$

Celle-ci est constituée du nom de connexion de l’utilisateur (sanchis), du nom de la machine sur laquelle l’utilisateur travaille (jade) et du chemin absolu du répertoire courant de l’utilisateur (/bin). Elle indique que le shell attend que l’utilisateur saisisse une commande et la valide en appuyant sur la touche entrée. Bash exécute alors la commande puis réaffiche la chaîne d’appel. Si l’on souhaite raccourcir cette chaîne d’appel, il suffit de modifier la valeur de la variable prédéfinie du shell PS1 (Prompt Shell 1).

 
Sélectionnez
sanchis@jade:/bin$ PS1='$ '
$ pwd
  /home/sanchis 	=> pwd affiche le chemin absolu du répertoire courant
$

La nouvelle chaîne d’appel est constituée par le caractère $ suivi d’un caractère espace.

I-1-2. Un langage de programmation

Les shells ne sont pas seulement des interpréteurs de commandes mais également de véritables langages de programmation. Un shell comme bash intègre :

  • les notions de variable, d’opérateur arithmétique, de structure de contrôle, de fonction, présentes dans tout langage de programmation classique, mais aussi
  • des opérateurs spécifiques (ex : |, ;)
 
Sélectionnez
$ a=5			=> affectation de la valeur 5 à la variable a
$
$ echo $((a +3 ))	=> affiche la valeur de l’expression a+3
8
$

L’opérateur |, appelé tube, est un opérateur caractéristique des shells et connecte la sortie d’une commande à l’entrée de la commande suivante.

 
Sélectionnez
$ ruptime
iutbis        up
jade          up
mobile1       up
mobile2       up
quartz        up
sigma         up
$
$ ruptime | wc -l
6
$

La commande unix ruptime affiche les noms et autres informations relatives aux machines visibles sur le réseau. La commande unix wc munie de l’option l affiche le nombre de lignes qu’elle a été en mesure de lire. (La commande ruptime est contenue dans le paquetage rwho. Ce paquetage n’est pas systématiquement installé par les distributions Linux.)
En connectant avec un tube la sortie de ruptime à l’entrée de la commande wc –l, on obtient le nombre de machines visibles du réseau.

Même si au fil du temps de nouveaux types de données comme les entiers ou les tableaux ont été introduits dans certains shells, ces derniers manipulent essentiellement des chaînes de caractères :
ce sont des langages de programmation orientés chaînes de caractères. C’est ce qui rend les shells à la fois si puissants et si délicats à utiliser.

L’objet de ce document est de présenter de manière progressive les caractéristiques de bash comme langage de programmation.

I-1-3. Atouts et inconvénients des shells

L’étude d’un shell tel que bash en tant que langage de programmation possède plusieurs avantages :

  • c’est un langage interprété : les erreurs peuvent être facilement localisées et traitées ; d’autre part, des modifications de fonctionnalités sont facilement apportées à l’application sans qu’il soit nécessaire de recompiler et faire l’édition de liens de l’ensemble
  • le shell manipule essentiellement des chaînes de caractères: on ne peut donc construire des structures de données complexes à l’aide de pointeurs, ces derniers n’existant pas en shell. Ceci a pour avantage d’éviter des erreurs de typage et de pointeurs mal gérés. Le développeur raisonne de manière uniforme en termes de chaînes de caractères
  • le langage est adapté au prototypage rapide d’applications : les tubes, les substitutions de commandes et de variables favorisent la construction d’une application par assemblage de commandes préexistantes dans l’environnement Unix
  • c’est un langage « glu » : il permet de connecter des composants écrits dans des langages différents. Ces derniers doivent uniquement respecter quelques règles particulièrement simples. Le composant doit être capable : de lire sur l’entrée standard, d’accepter des arguments et options éventuels, d’écrire ses résultats sur la sortie standard, d’écrire les messages d’erreur sur la sortie standard dédiée aux messages d’erreur.

Cependant, bash et les autres shells en général ne possèdent pas que des avantages :

  • issus d’Unix, système d’exploitation écrit à l’origine par des développeurs pour des développeurs, les shells utilisent une syntaxe « ésotérique » d’accès difficile pour le débutant
  • l’oubli ou l’ajout d’un caractère espace provoque facilement une erreur de syntaxe
  • bash possède plusieurs syntaxes pour implanter la même fonctionnalité, comme la substitution de commande ou l’écriture d’une chaîne à l’écran. Cela est principalement dû à la volonté de fournir une compatibilité ascendante avec le Bourne shell, shell historique des systèmes Unix
  • certains caractères spéciaux, comme les parenthèses, ont des significations différentes suivant le contexte ; en effet, les parenthèses peuvent introduire une liste de commandes, une définition de fonction ou bien imposer un ordre d’évaluation d’une expression arithmétique. Toutefois, afin de rendre l’étude de bash plus aisée, nous n’aborderons pas sa syntaxe de manière exhaustive ; en particulier, lorsqu’il existera plusieurs syntaxes pour mettre en oeuvre la même fonctionnalité, une seule d’entre elles sera présentée.

I-1-4. Shell utilisé

La manière la plus simple de connaître le shell que l’on utilise est d’exécuter la commande unix ps qui liste les processus de l’utilisateur :

 
Sélectionnez
$ ps
PID   TTY   TIME    CMD
6908 pts/4 00:00:00 bash       => l’interpréteur utilisé est bash
6918 pts/4 00:00:00 ps
$

Comme il existe plusieurs versions de bash présentant des caractéristiques différentes, il est important de connaître la version utilisée. Pour cela, on utilise l'option –-version de bash.

 
Sélectionnez
$ bash --version
GNU bash, version 3.1.14(1)-release (i486-pc-linux-gnu)
Copyright (C) 2005 Free Software Foundation, Inc.
$

La dernière version majeure de bash est la version 3. C’est celle qui sera étudiée.

I-2. Syntaxe d’une commande

La syntaxe générale d'une commande (unix ou de bash) est la suivante :

 
Sélectionnez
         [ chemin/]nom_cmd [ option ... ] [ argument ... ]

C'est une suite de mots séparés par un ou plusieurs blancs. On appelle blanc un caractère tab (tabulation horizontale) ou un caractère espace.
Un mot est une suite de caractères non blancs. Cependant, plusieurs caractères ont une signification spéciale pour le shell et provoquent la fin d'un mot : ils sont appelés méta-caractères (ex : |, <).
Bash utilise également des opérateurs de contrôle (ex : (, ||) et des mots réservés pour son propre fonctionnement :

 
Sélectionnez
!    case  do       done elif else   esac
fi   fo    function if   in   select then
time until while    {    }    [[     ]]

Bash distingue les caractères majuscules des caractères minuscules.

Le nom de la commande est le plus souvent le premier mot.

Une option est généralement introduite par le caractère tiret (ex : -a) ou dans la syntaxe GNU par deux caractères tiret consécutifs (ex : --version). Elle précise un fonctionnement particulier de la commande.
La syntaxe [ elt ... ] signifie que l’élément elt est facultatif (introduit par la syntaxe [elt ]) ou bien présent une ou plusieurs fois (syntaxe elt ...). Cette syntaxe ne fait pas partie du shell ; elle est uniquement descriptive (méta-syntaxe).

Les arguments désignent les objets sur lesquels doit s’exécuter la commande.

 
Sélectionnez
ls -l RepC RepD : commande ls avec l’option l et les arguments RepC et RepD

Lorsque l’on souhaite connaître la syntaxe ou les fonctionnalités d’une commande cmd (ex : ls ou bash) il suffit d’exécuter la commande man cmd (ex : man bash). L’aide en ligne de la commande cmd devient alors disponible.

I-3. Commandes internes et externes

Le shell distingue deux sortes de commandes :

  • les commandes internes
  • les commandes externes.

I-3-1. Commandes internes

Une commande interne est une commande dont le code est implanté au sein de l’interpréteur de commande. Cela signifie que, lorsqu’on change de shell courant ou de connexion, par exemple en passant de bash au C-shell, on ne dispose plus des mêmes commandes internes. Exemples de commandes internes : cd , echo , for , pwd Sauf dans quelques cas particuliers, l’interpréteur ne crée pas de processus pour exécuter une commande interne. Les commandes internes de bash peuvent se décomposer en deux groupes :

  • les commandes simples (ex : cd, echo)
  • les commandes composées (ex : for, ((, {).
Commandes simples

Parmi l’ensemble des commandes internes, echo est l’une des plus utilisée :
echo : Cette commande interne affiche ses arguments sur la sortie standard en les séparant par un espace et va à la ligne.

 
Sélectionnez
$ echo bonjour     tout le      monde
bonjour tout le monde
$

Dans cet exemple, echo est invoquée avec quatre arguments : bonjour, tout, le et monde. On remarquera que les espacements entre les arguments ne sont pas conservés lors de l’affichage : un seul caractère espace sépare les mots affichés. En effet, le shell prétraite la commande, éliminant les blancs superflus.
Pour conserver les espacements, il suffit d’entourer la chaîne de caractères par une paire de guillemets :

 
Sélectionnez
$ echo “bonjour         tout le                                monde”
bonjour       tout le                                 monde
$

On dit que les blancs ont été protégés de l'interprétation du shell.
Pour afficher les arguments sans retour à la ligne, on utilise l’option -n de echo.

 
Sélectionnez
$ echo -n bonjour
bonjour$

La chaîne d’appel $ est écrite sur la même ligne que l’argument bonjour.

Commandes composées

Les commandes composées de bash sont :

 
Sélectionnez
case ... esac        if ... fi             for ... done   select ... done
until ... done       while ... done        [[ ... ]]       ( ... )
{ ... }              (( ... ))

Seuls le premier et le dernier mot de la commande composée sont indiqués. Les commandes composées sont principalement des structures de contrôle.

I-3-2. Commandes externes

Une commande externe est une commande dont le code se trouve dans un fichier ordinaire. Le shell crée un processus pour exécuter une commande externe. Parmi l’ensemble des commandes externes que l’on peut trouver dans un système, nous utiliserons principalement les commandes unix (ex : ls, mkdir, vi, sleep) et les fichiers shell.

La localisation du code d'une commande externe doit être connue du shell pour qu'il puisse exécuter cette commande. A cette fin, bash utilise la valeur de sa variable prédéfinie PATH. Celle-ci contient une liste de chemins séparés par le caractère : (ex : /bin:/usr/bin). Par exemple, lorsque l’utilisateur lance la commande unix cal, le shell est en mesure de l’exécuter et d’afficher le calendrier du mois courant car le code de cal est situé dans le répertoire /usr/bin présent dans PATH.

 
Sélectionnez
$ cal
   septembre 2006
di lu ma me je ve sa
                1  2
 3  4  5  6  7  8  9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
$

pour connaître le statut d’une commande, on utilise la commande interne type.

 
Sélectionnez
$ type sleep
sleep is /bin/sleep         => sleep est une commande externe
$ type echo
echo is a shell builtin
$

I-4. Modes d’exécution d’une commande

Deux modes d’exécution peuvent être distingués :

  • l’exécution séquentielle
  • l’exécution en arrière-plan.

I-4-1. Exécution séquentielle

Le mode d’exécution par défaut d’une commande est l’exécution séquentielle : le shell lit la commande entrée par l’utilisateur, l’analyse, la prétraite et si elle est syntaxiquement correcte, l’exécute.
Une fois l’exécution terminée, le shell effectue le même travail avec la commande suivante. L’utilisateur doit donc attendre la fin de l’exécution de la commande précédente pour que la commande suivante puisse être exécutée : on dit que l’exécution est synchrone.

Si on tape la suite de commandes :

 
Sélectionnez
                 sleep 3 entrée date entrée

où entrée désigne la touche entrée, l’exécution de date débute après que le délai de 3 secondes se soit écoulé.

Pour lancer l’exécution séquentielle de plusieurs commandes sur la même ligne de commande, il suffit de les séparer par un caractère ;

 
Sélectionnez
$ cd /tmp ; pwd; echo bonjour; cd ; pwd
/tmp           => affichée par l’exécution de pwd
bonjour        => affichée par l’exécution de echo bonjour
/home/sanchis  => affichée par l’exécution de pwd
$

Pour terminer l’exécution d’une commande lancée en mode synchrone, on appuie simultanément sur les touches CTRL et C (notées control-C ou ^C).
En fait, la combinaison de touches appropriée pour arrêter l’exécution d’une commande en mode synchrone est indiquée par la valeur du champ intr lorsque la commande unix stty est lancée :

 
Sélectionnez
$ stty -a
speed 38400 baud; rows 24; columns 80; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>;
...
$

Supposons que la commande cat soit lancée sans argument, son exécution semblera figée à un utilisateur qui débute dans l’apprentissage d’un système Unix. Il pourra utiliser la combinaison de touches mentionnée précédemment pour terminer l’exécution de cette commande.

 
Sélectionnez
$ cat
^C
$

I-4-2. Exécution en arrière-plan

L’exécution en arrière-plan permet à un utilisateur de lancer une commande et de récupérer immédiatement la main pour lancer « en parallèle » la commande suivante (parallélisme logique). On utilise le caractère & pour lancer une commande en arrière-plan.

Dans l’exemple ci-dessous, la commande sleep 5 (suspendre l’exécution pendant 5 secondes) est lancée en arrière-plan. Le système a affecté le numéro d’identification 696 au processus correspondant tandis que bash a affecté un numéro de travail (ou numéro de job) égal à 1 et affiché [1]. L’utilisateur peut, en parallèle, exécuter d’autres commandes (dans cet exemple, il s’agit de la commande ps). Lorsque la commande en arrière-plan se termine, le shell le signale à l'utilisateur après que ce dernier ait appuyé sur la touche entrée.

 
Sélectionnez
$ sleep 5 &
[1] 696
$ ps
PID TTY                TIME CMD
683 pts/0        00:00:00 bash
696 pts/0        00:00:00 sleep
697 pts/0        00:00:00 ps
$                => l’utilisateur a appuyé sur la touche entrée mais sleep n’était pas terminée
$                => l’utilisateur a appuyé sur la touche entrée et sleep était terminée
[1]+ Done                               sleep 5
$

Ainsi, outre la gestion des processus spécifique à Unix, bash introduit un niveau supplémentaire de contrôle de processus. En effet, bash permet de stopper, reprendre, mettre en arrière-plan un processus, ce qui nécessite une identification supplémentaire (numéro de job).
L’exécution en arrière-plan est souvent utilisée lorsqu’une commande est gourmande en temps CPU (ex : longue compilation d’un programme).

I-5. Commentaires

Un commentaire débute avec le caractère # et se termine avec la fin de la ligne. Un commentaire est ignoré par le shell.

 
Sélectionnez
$ echo bonjour        # l’ami
bonjour
$ echo coucou       #l’    ami
coucou
$ # echo coucou
$

Pour que le caractère # soit reconnu en tant que début de commentaire, il ne doit pas être inséré à l’intérieur d’un mot ou terminer un mot.

 
Sélectionnez
$ echo il est#10
il est#10 heures
$
$ echo bon# jour
bon# jour
$

I-6. Fichiers shell

Lorsqu’un traitement nécessite l’exécution de plusieurs commandes, il est préférable de les sauvegarder dans un fichier plutôt que de les retaper au clavier chaque fois que le traitement doit être lancé. Ce type de fichier est appelé fichier de commandes ou fichier shell ou encore script shell.

Exercice 1 :

1.) A l’aide d’un éditeur de texte, créer un fichier premier contenant les lignes suivantes :

 
Sélectionnez
                #!/bin/bash
                #    premier
                echo -n "La date du jour est: "
                date

La notation #! en première ligne d'un fichier interprété précise au shell courant quel interpréteur doit être utilisé pour exécuter le script shell (dans cet exemple, il s’agit de /bin/bash).
La deuxième ligne est un commentaire.

2.) Vérifier le contenu de ce fichier.

 
Sélectionnez
Ex :    $ cat premier
        #!/bin/bash
        #    premier
        echo -n "La date du jour est: "
        date
        $

3.) Pour lancer l’exécution d’un fichier shell fich, on peut utiliser la commande : bash fich

 
Sélectionnez
Ex :    $ bash premier
        la date du jour est : dimanche 3 septembre 2006, 15:41:26 (UTC+0200)
        $

4.) Il est plus simple de lancer l’exécution d’un programme shell en tapant directement son nom, comme on le ferait pour une commande unix ou une commande interne. Pour que cela soit réalisable, deux conditions doivent être remplies :

  • l’utilisateur doit posséder les permissions r (lecture) et x (exécution) sur le fichier shell
  • le répertoire dans lequel se trouve le fichier shell doit être présent dans la liste des chemins contenue dans PATH.
 
Sélectionnez
Ex :    $ ls -l premier
        -rw-r--r-- 1 sanchis sanchis 63 sep              3 15:39 premier
        $

Seule la permission r est possédée par l’utilisateur.

Pour ajouter la permission x au propriétaire du fichier fich, on utilise la commande :

 
Sélectionnez
              chmod u+x fich
 
Sélectionnez
Ex :  $ chmod u+x premier
      $
      $ ls -l premier
      -rwxr--r-- 1 sanchis sanchis 63 sep             3 15:39 premier
      $

Si on exécute premier en l’état, une erreur d’exécution se produit.

 
Sélectionnez
Ex :  $ premier
                                                    => Problème !
      -bash: premier: command not found
      $

Cette erreur se produit car bash ne sait pas où se trouve le fichier premier.

 
Sélectionnez
Ex :                                        => affiche la valeur de la variable PATH
      $ echo $PATH
      /bin:/usr/bin:/usr/bin/X11
      $
      $ pwd
      /home/sanchis
      $

Le répertoire dans lequel se trouve le fichier premier (répertoire /home/sanchis) n’est pas présent dans la liste de PATH. Pour que le shell puisse trouver le fichier premier, il suffit de mentionner le chemin permettant d’accéder à celui-ci.

 
Sélectionnez
Ex :  $ ./premier
      la date du jour est : dimanche 3 septembre 2006, 15:45:28 (UTC+0200)
      $

Pour éviter d’avoir à saisir systématiquement ce chemin, il suffit de modifier la valeur de PATH en y incluant, dans notre cas, le répertoire courant (.).

 
Sélectionnez
Ex :                                => ajout du répertoire courant dans PATH
      $ PATH=$PATH:.
      $
      $ echo $PATH
      /bin:/usr/bin:/usr/bin/X11:.
      $
      $ premier
      la date du jour est : dimanche 3 septembre 2006, 15:47:38 (UTC+0200)
      $

Exercice 2 :

Ecrire un programme shell repcour qui affiche le nom de connexion de l'utilisateur et le chemin absolu de son répertoire courant de la manière suivante :

 
Sélectionnez
Ex :  $ repcour
      mon nom de connexion est : sanchis
      mon repertoire courant est : /home/sanchis
      $

précédentsommairesuivant

Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.