Bash : Soyez plus à l'aise dans votre coquille

Vous utilisez tous (peut être sans le savoir) le shell bash. Dans cet article vous allez apprendre un certain nombre d'astuces qui vous permettrons d'utiliser au mieux ce programme. C'est l'occasion pour les nouveaux venus à Linux d'apprendre et aux vieux routards de rafraîchir leurs souvenirs.

Le shell sert d'interface entre le système d'exploitation et l'utilisateur. Pour les utilisateurs qui sont venus récemment à Linux, le shell est cette fenêtre principalement utilisée pour lancer les commandes en mode texte. Pour les "vieux de la vieille", le shell est l'unique, la vraie, la seule interface possible entre le noyau et l'utilisateur ;-)
Avant la généralisation d'interfaces graphiques comme GNOME ou KDE, les utilisateurs n'avaient guère d'autres possibilités pour exécuter une commande que d'utiliser un shell. Aujourd'hui, cette interface a tendance à être considérée comme obsolète et la plupart des nouveaux utilisateurs n'en connaissent pas toutes les subtilités. Cet article tente de réparer cette injustice malheureuse.

Historiquement, le premier shell est dû à Steve Bourne au début des années 1970. Un certain nombre d'autres shells ont depuis été écrits, mais le plus courrament utilisé aujourd'hui est GNU bash (pour 'Bourne Again SHell'). Il existe bien d'autres programmes ayant à peu de choses près les mêmes fonctionnalités (ash, zsh, tcsh, pdksh, etc), mais bash étant installé par défaut sur tous les systèmes Linux, c'est celui-ci que nous allons étudier de plus près.

    Maitrisez l'historique

Bash contient un mécanisme d'hishorique extremement pratique. Lancez simplement un shell, soit en vous loggant sur le système si vous étes en mode texte, soit en cliquant dans votre barre de menu sur l'icône qui représente soit un coquillage (si vous utilisez KDE), soit un pingouin à coté d'un écran (si vous utilisez  GNOME).

Il est souvent pénible d'avoir à taper plusieurs fois de suite quasiment la même commande à cause d'une faute de frappe. Pour cela, bash offre un mécanique d'historique qui permet d'éditer les commandes déjà entrées. Cet historique peut être utilisé grâce à plusieurs méthodes.
Voyons de plus près chacune d'entre elles.

La méthode de gestion d'historique la plus simple utilise les fléches de directions. Essayez d'entrer successivement les trois commandes suivantes dans un shell :
cd /
ls -al /etc/passwd
tail /etc/resolv.conf

Maintenant tapez sur la flèche curseur 'haut'. La dernière commande est apparue au prompt du shell. Tapez encore sur la fléche 'haut' et c'est la commande ls qui devrait la remplacer. Pour repasser à la commande suivante, tapez sur la fléche 'bas'. Il vous suffit maintenant de taper sur entrée pour réexécuter cette commande. Notons au passage que dans le cas où vous ne voulez ou ne pouvez pas utiliser les fléches 'haut' et 'bas', vous pouvez utiliser à la place les combinaisons de touches Ctrl-P (pour "Previous command") et Ctrl-N (pour "Next command").
En plus de cette méthode séquentielle pour "reprendre" des commandes, il existe un moyen d'effectuer une recherche dans les commandes effectuées. Pour essayer cette méthode, tapez Ctrl-R. Le prompt a été remplacé par la chaine (reverse-i-search)`'. Si vous desirez retrouver la dernière commande qui contient la chaine 'etc', il vous suffit de taper cette chaîne et la commande 'tail /etc/resolv.conf' apparaitra. A ce stade, vous pouvez soit décider d'exécuter à nouveau cette commande en tapant sur entrée, soit decider que vous vous vouliez en fait la dernière commande opérant sur le fichier /etc/passwd. Dans ce dernier cas, il suffit de continuer à taper, donc d'ajouter '/p', et déjà le prompt devrait à la place montrer la commande ls -al /etc/passwd.

Bash permet également de référencer les commandes par numéro d'ordre.
Pour vous en convaincre, exécutez la commande "history". Celle-ci affiche la liste des dernières commandes utilisées. Il est possible que cette instruction liste des commandes qui ont été utilisées lors de sessions précédentes. En effet, bash enregistre la liste des commandes tapées durant la session dans un fichier ".bash_history" situé dans le répertoire racine de l'utilisateur. Pour rappeler une commande, il suffit d'entrer son numéro d'ordre précédé d'un point d'exclamation. Par exemple, "!30" rappelle la trentième commande. Il est également possible d'utiliser la syntaxe  '!!' pour demander la réexécution de la dernière commande entrée.
Ce mécanisme d'historique permet également de ne récupérer que certains paramétres des dernières commandes :
# cat ~/articles/bash.html ~/articles/py4.html
[...]
# vi !-1:2

Cette dernière notation indique à bash qu'il doit exécuter la commande 'vi' suivie du deuxième paramétre de la derniere commande.

L'utilisateur averti pourra parfois avoir envie d'utiliser son éditeur favori (que ce soit vim ou emacs) pour éditer la ligne de commande. Cela est fort heureusement possible grâce à la commande 'fc'.
Celle-ci peut être utilisée avec plusieurs paramètres différents qui permettent de gérer au mieux l'historique de votre session en cours. Notons au passage que 'fc' n'est pas une commande que vous pourrez trouver sur votre disque dur, c'est une commande interne ("built-in") du shell, au même titre que 'cd' par exemple.
fc peut être invoqué aussi bien pour lister une partie de l'historique :
fc -l -20 : affiche les 20 dernières commandes.
que pour l'éditer :
fc -e vi : édite la dernière commande tapée avec vi et exécute la commande modifiée lors de la sortie de l'éditeur.
D'autre part, l'éditeur utilisé par fc par défaut peut être spécifié une fois pour toutes en donnant à la variable FCEDIT le nom de votre éditeur de prédilection.

    Écrivez vos propres scripts

Si vous avez tendance à utiliser souvent la même séquence de commandes, l'écriture d'un script peut se réveler plus pratique que d'utiliser le mécanisme d'historique. Un script est tout simplement une liste de commandes shell enregistrées dans un fichier qui seront exécutées ensemble lorsque le script sera appelé. Commençons par un exemple simple; supposons que vous désiriez écrire un script qui soit capable de remplacer une chaîne de caractères donnée par une autre dans un fichier texte.
Commencez par enregistrer les lignes suivantes à l'aide de votre éditeur de texte favori dans un fichier nommé 'script.sh' :

#!/bin/sh
cp $1 /tmp/replace.$$
sed -e "s/$2/$3/g" < /tmp/replace.$$ > $1

Puis exécutez la commande 'chmod +x script.sh' pour rendre le script directement exécutable.

Pour le tester, créez un fichier 'test.txt' contenant simplement la ligne de texte :
Red Hat est la meilleure distribution Linux!
 

Maintenant, testons si notre programme fonctionne correctement :
# ./script.sh test.txt "Red Hat" Debian
# cat test.txt
Debian est la meilleure distribution Linux!
#

Ça marche! ;-)

Examinons maintenant le code de notre script plus en détail :
La première ligne indique au noyau le nom de l'interpréteur à utiliser pour exécuter le script, en l'occurence /bin/sh qui est un lien vers /bin/bash.
La deuxième ligne est utilisée pour faire une copie de notre fichier d'origine. A l'exécution, les variables $1, $2, $3, etc sont substituées par les premier, deuxième et troisième paramètres passés sur la ligne de commande.
La variable $$ est également spéciale : celle-ci correspond au numéro de processus (PID) de l'instance du shell qui exécutera le script. Cette variable est très souvent utilisée, comme elle l'est ici, pour déterminer un nom de fichier temporaire.
La troisième et dernière ligne est celle qui fait réellement ce que l'on attend du script, à savoir le remplacement de chaînes. Le fichier temporaire est utilisé comme source de données et le résultat de la substitution et remis dans le fichier d'origine.

Nous allons maintenant essayer de rendre ce script un peu plus robuste. En effet, si le nombre de paramètres passés est incorrect ou s'il y a une erreur lors de la création du fichier temporaire, l'erreur ne sera pas correctement détectée ce qui risque de laisser l'utilisateur perplexe dans la mesure ou l'absence de message d'erreur est censé indiquer un bon déroulement des opérations.

Pour commencer, nous devons nous assurer que le nombre de paramètres passés au script est bien correct.

Nous allons préalablement définir une fonction dont le but est d'afficher un message mentionnant les paramètres attendus par le programme et de terminer l'exécution du script.

usage() {
    echo "Usage : script.sh fichier.txt chaine1 chaine2"
    exit -1
}

Maintenant, on teste que les 3 paramètres existent et on exécute la fonction usage le cas échéant.

[ -z "$1" -o -z "$2" -o -z "$3" ] && usage

Comme vous l'avez probablement déjà deviné, l'opérateur '-o' correspond au "ou logique", alors que '-z' retourne la valeur vraie si la chaîne de caractères qui suit est vide.

Maintenant, pour que notre programme soit vraiment beau, il ne reste plus qu'à définir le nom du fichier temporaire dans une variable, plutôt que de le désigner directement chaque fois que l'on en a besoin. En effet, une des règles de base de la programmation stipule qu'une définition ne doit jamais être faite à deux endroits différents, ou si vous préférez, vous devez "factoriser" votre code le plus possible.
Cela se fait simplement en ajoutant la définition suivante au début du script :
tmp=/tmp/replace.$$
 et par la suite on utilise $tmp pour faire référence au nom du fichier temporaire.
Nous devons également tester qu'il n'existe pas déjà un fichier ayant le même nom.
Pour cela, on effectue un test avec l'opérateur '-e' qui teste l'existence d'un fichier :
if [ -e "$tmp" ]
then
    echo "Le fichier $tmp existe deja."
    exit -1
fi

Enfin, la touche finale : on ajoute l'instruction 'set -e' au début du script pour qu'il s'interrompe dès qu'une commande retourne une valeur de retour non nulle, ce qui indique son échec (par exemple, l'impossibilité de créer le fichier temporaire dans l'éventualité de l'échec de la commande 'cp').

Et voici la version finale de notre script :

#!/bin/sh

tmp=/tmp/replace.$$
if [ -f "$tmp" ]
then
        echo "le fichier $tmp existe deja."
        exit -1
fi

usage() {
        echo Usage : script.sh fichier.txt chaine1 chaine2
        exit -1
}

[ -z "$1" -o -z "$2" -o -z "$3" ] && usage

cp $1 $tmp
sed -e "s/$2/$3/g" < $tmp > test2.txt

Voilà, maintenant vous devriez en savoir assez pour être à l'aise avec la ligne de commande, ainsi qu'être capable d'écrire vos propres programmes shell pour réaliser des tâches simples. Si vous désirez en savoir plus sur le sujet (que nous n'avons qu'effleuré ici), vous pourrez consulter les documents cités en références. Bons scripts!
 
Références
Bash-Prog-Intro-HOWTO :
    http://www.linuxdoc.org/HOWTO/Bash-Prog-Intro-HOWTO.html
Bash2 reference manual :
    http://www.svn.net/helpdesk/bashref.html

 
Utilisateur de GNU/Linux depuis 1993, Vincent Renardias est activement impliqué dans son développement depuis 1996 : Développeur de la distribution Debian, auteur de la traduction Française de l'environnement GNOME, créateur du groupe d'utilisateurs Linux de Marseille (PLUG). Il continue aujourd'hui activement a promouvoir le système GNU/Linux en tant que responsable technique de la société Echo (créatrice du moteur de recherche www.voila.fr).
Vincent Renardias <vincent@echo.fr>


Copyright (c) 2001 Linux Magazine France. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; A copy of the license is included in the section entitled "GNU Free Documentation License".