Programmation Shell

ArticleCategory: [Es gibt verschiedene Artikel = Kategorien]

UNIX Basics

AuthorImage:[Ein Bild von Dir]

[Photo of the Authors]

TranslationInfo:[Autor und Übersetzer]

original in en Katja and Guido Socher 

en to fr Éric Jullien

AboutTheAuthor:[Eine kleine Biographie über den Autor]

Katja est l'éditeur allemand de LinuxFocus. Elle aime Tux, les films, la photographie et la mer. Sa page personnelle se trouve ici.

Guido est un vieux fan de Linux. Il apprécie Linux car il est conçu par des gens honnètes et ouverts. C'est d'ailleurs la raison pour laquelle on l'appelle "Logiciel Ouvert"). Sa page personnelle se trouve à linuxfocus.org/~guido.

Abstract:[Hier sollte eine kleine Zusammenfassung stehen]

Nous expliquerons dans cet article comment écrire de petits scripts shell et nous donnerons de nombreux exemples.

ArticleIllustration:[Das Titelbild des Artikels]

[Illustration]

ArticleBody:[Der eigentliche Artikel. Überschriften innerhalb des Artikels sollten h2 oder h3 sein.]

Pourquoi Programmer en Shell ?

Bien que de nombreuses interfaces graphiques soient disponibles pour Linux, le shell reste un très bon outil. En effet, ce n'est pas seulement un ensemble de commandes, mais c'est aussi un véritable langage de programmation. Son utilisation permet d'automatiser de nombreuses tâches et il est particulièrement adapté a l'administration système. Programmer en shell permet en outre de vérifier rapidement le bien fondé de vos idées, ce qui facilite le prototypage. Il est également très utile pour l'écriture de petits utilitaires destinés à accomplir des tâches relativement simples, où l'efficacité est moins importante que la facilité de configuration, la maintenance et la portabilité.
Examinons maintenant le fonctionnement du shell :

Écrire un script shell

De nombreux shells sont accessibles sous Linux, mais c'est généralement le bash (Bourne Again SHell) qui est utilisé pour la programmation shell car il est facile d'emploi, libre de droit et gratuit. Aussi, tous les scripts que nous écrirons dans cet article utiliseront le shell bash (la plupart du temps, ils fonctionneront également avec leur ancêtre le bourne shell).
Pour écrire nos programmes shell nous utilisons un éditeur de texte, tel que nedit, kedit, emacs, vi... comme pour tout autre langage de programmation.
Le programme doit commencer par la ligne suivante (c'est obligatoirement la première ligne du fichier) :
    #!/bin/sh 
   
Les caractères #! indiquent au système que l'argument suivant est le programme à utiliser pour exécuter ce fichier. Ici, nous utilisons le shell /bin/sh.
Lorsque votre script est terminé et enregistré, vous devez le rendre exécutable pour pouvoir l'utiliser. Pour cela tapez :
chmod +x filename
Vous pouvez maintenant lancer votre script en tapant simplement : ./filename

Les Commentaires

Les commentaires, en langage shell, commencent par le caractère # et vont jusqu'à la fin de la ligne. Nous vous conseillons vivement d'utiliser des commentaires. Si vous commentez vos scripts, vous saurez toujours à quoi ils servent et comment ils fonctionnent, même après une longue période d'inutilisation.

Les Variables

Comme dans tout langage de programmation, vous ne pouvez vivre sans les variables. En langage shell, toutes les variables sont de type "chaîne de caractères" et vous ne devez pas les déclarer.
Pour affecter une valeur à une variable il suffit d'écrire :
varname=value
Pour récupérer la valeur de la variable, il suffit d'écrire le signe dollar devant le nom de la variable :
#!/bin/sh
# affecte une valeur a la variable :
a="Bonjour le Monde"
# affiche le contenu de la variable "a" :
echo "La valeur de A est :"
echo $a
Tapez ces lignes dans un éditeur de texte et enregistrez-les sous le nom "premier". Rendez ensuite le script exécutable en tapant chmod +x premier dans un shell et lancez-le par ./premier
Le script affichera alors :
La valeur de A est :
Bonjour le Monde
Il peut arriver qu'il y ait confusion entre les noms de variables et le reste du texte :
num=2
echo "ceci est le $numeme"
Ce script ne va pas afficher "ceci est le 2eme" mais "ceci est le " car le shell recherche une variable nommée numeme qui n'a pas de valeur définie. Pour indiquer au shell que nous faisons référence à la variable num il faut utiliser les accolades :
num=2
echo "ceci est le ${num}eme"

Cette fois le script affiche ce que nous attendons :
ceci est le 2eme

Plusieurs variables sont automatiquement définies. Nous les expliquerons de manière plus approfondie lors de leur première utilisation.

La manipulation d'expression arithmétique requiert l'utilisation de programmes tels que expr (voir le tableau ci-dessous).
En dehors des variables shell normales, seulement actives à l'intérieur du programme shell, il existe aussi des variables d'environnement. Une variable précédée du mot-clé export est une variable d'environnement. Nous ne nous attarderons pas sur de telles variables car elles ne sont normalement, utilisées que dans les scripts de login.

Les commandes Shell et les structures de contrôle

Trois catégories de commandes peuvent être utilisées dans les scripts shell :

1) Les commandes Unix :
Bien qu'un script shell script puisse faire appel à n'importe quelle commande Unix, certaines d'entre elles sont plus souvent utilisées que d'autres. Ces commandes peuvent être décrites comme des commandes de manipulation de textes et de fichiers

Syntaxe de la commande But
echo "un texte" affiche un texte sur l'écran
ls liste des fichiers
wc -l fichier
wc -w fichier
wc -c fichier
compte les lignes du fichier, ou
compte les mots du fichier, ou
compte le nombre de caractères
cp fichier_source fichier_dest copie le fichier_source vers le fichier_dest
mv ancien_nom nouveau_nom renomme ou déplace le fichier
rm fichier efface le fichier
grep 'pattern' fichier cherche des chaînes de caratères dans un fichier
Exemple: grep 'searchstring' file.txt
cut -c num_colonne fichier récupère les données issues de colonnes de textes à largeur fixe.
Exemple : récupère les caractères des positions 5 à 9
cut -b5-9 file.txt
Ne pas confondre cette commande avec la commande cat, qui a une utilisation totalement différente.
cat file.txt écrit le contenu du fichier file.txt sur la sortie standard stdout (qui est par défaut votre écran)
file fichier décrit le type de fichier
read var attend une frappe de l'utilisateur en entrée et stocke cette valeur dans une variable (var)
sort file.txt trie les lignes du fichier file.txt
uniq, retire les lignes en double, utilisé parallèlement à sort, puisque uniq ne retire que les doublons sur des lignes consécutives.
Exemple : sort file.txt | uniq
expr pour faire des maths dans le shell
Exemple: ajouter 2 et 3
expr 2 "+" 3
find recherche de fichier
Exemple : rechercher un nom :
find . -name nom_fichier -print
Cette commande possède énormément d'options et de possibilités différentes, rendant leur description impossible dans cet article.
tee écrit simultanément des données vers stdout (l'écran) et dans un fichier.
Elle s'utilise habituellement de cette manière :
commande | tee fichier_de_sortie
Affiche la sortie de la commande à l'écran et l'écrit dans le fichier_de_sortie.
basename fichier retourne le nom du fichier sans le chemin d'accès.
Exemple : basename /bin/tux
retourne seulement tux
dirname fichier renvoie le nom du répertoire d'un fichier sans préciser le nom de ce fichier
Exemple: dirname /bin/tux
retourne /bin
head fichier affiche les premières lignes du début d'un fichier.
tail fichier affiche les dernières lignes de la fin d'un fichier
sed sed est à la base un programme de recherche et de remplacement . Il lit le texte de l'entrée standard (depuis un "pipe", par exemple) et écrit le résultat sur la sortie standard (stdout, l'écran). Le modèle à rechercher est une expression régulière (voir la section Réferences).
Ce modèle de recherche ('pattern') ne doit pas être confondu avec la syntaxe des 'wildcards' du shell. Par exemple, pour remplacer dans un texte la chaîne de caractères linuxfocus par la chaîne LinuxFocus, exécutez :
cat text.file | sed 's/linuxfocus/LinuxFocus/' > newtext.file
Cette commande remplace la première occurrence de la chaîne linuxfocus par la chaîne LinuxFocus. S'il y a des lignes contenant plusieurs fois la chaîne linuxfocus et que vous vouliez toutes les remplacer, tapez :
cat text.file | sed 's/linuxfocus/LinuxFocus/g' > newtext.file
awk La plupart du temps, awk est utilisé pour extraire des blocs d'une ligne de texte. Le séparateur par défaut est l'espace. Utilisez l'option -F pour définir un autre séparateur.
   cat fichier.txt | awk -F, '{print $1 "," $3 }'  
Ici, la virgule (,) est utilisée comme séparateur et nous affichons les première et troisième ($1$3) colonnes. Si fichier.txt contient les lignes suivantes :
Adam Bor, 34, India
Kerry Miller, 22, USA

Le résultat sera :
Adam Bor, India
Kerry Miller, USA

Vous pouvez faire beaucoup plus avec awk mais ce qui précède est l'usage le plus courant.


2) Concepts: pipes (tuyaux), redirection et backtick (guillemet simple inversé)
Ce ne sont pas des commandes à proprement parler, mais ce sont des concepts essentiels.

Les "pipes" (|) redirigent la sortie standard (stdout) d'un programme vers l'entrée standard (stdin) d'un autre programme.
    grep "hello" file.txt | wc -l
Cette commande recherche les lignes contenant la chaîne de caractères hello dans le fichier file.txt et compte le nombre de lignes concernées.
La sortie de la commande grep est utilisée comme entrée de la commande wc. On peut ainsi concaténer autant de commandes que l'on souhaite (dans des limites raisonnables).
La redirection écrit la sortie d'une commande dans un fichier ou ajoute des données à un fichier
> écrit la sortie dans un fichier, en écrasant celui-ci s'il existe déja.
>> ajoute la sortie de la commande à la fin du fichier (ou, crée le fichier s'il n'existe pas, mais il n'écrase jamais de données).

Backtick
La sortie d'une commande peut être utilisée comme argument d'une ligne de commande pour une autre commande (A la différence de stdin comme ci-dessus, les arguments d'une ligne de commande sont des chaînes de caractères spécifiées après une commande, comme, par exemple, un nom de fichier ou des options). On peut également s'en servir pour affecter la sortie d'une commande à une variable.
La commande
find . -mtime -1 -type f -print
recherche tous les fichiers qui ont été modifiés dans les dernières 24 heures ( -mtime -2 aurait signifié 48 heures). Si on veut regrouper tous ces fichiers dans une archive 'tar' (fichier.tar), la syntaxe de la commande tar est :
tar xvf fichier.tar fich1 fich2 ...
Au lieu de taper la totalité, vous pouvez combiner ces deux commandes (find et tar) en utilisant les backticks. Tar va alors regrouper tous les fichiers trouvés par find :
#!/bin/sh
# Les ticks sont des guillemets simples inversés (`) 
# et non des guillemets simples (')
tar -zcvf lastmod.tar.gz `find . -mtime -1 -type f -print`

3) Les Structures de Contrôle
L'instruction if teste si une condition est "vraie" (le code de retour est "0" qui signifie succès). Si c'est le cas, la partie "then" est alors exécutée :
if ....; then
   ....
elif ....; then
   ....
else
   ....
fi
Très souvent, une commande particulière, appelée test, est utilisée à l'intérieur d'une instruction "if" pour comparer des chaînes de caractères ou tester l'existence d'un fichier, ses permissions,etc.
La commande test est écrite entre crochets "[" et "]" Notez que l'espace est ici, significatif. Vous devez impérativement avoir un espace autour de chaque crochet. Exemples :
[ -f "un_fichier" ]  : Teste si "un_fichier" est un fichier
[ -x "/bin/ls" ]     : Teste si "/bin/ls" existe et est exécutable
[ -n "$var" ]        : Teste si la variable $var n'est pas vide
[ "$a" = "$b" ]      : Teste si les variables "$a" et "$b" sont égales
Exécutez la commande man test et vous obtiendrez la longue liste de tous les opérateurs de test sur les comparaisons et les fichiers.
L'utilisation de l'instruction test dans un script shell est simple :
#!/bin/sh
if [ "$SHELL" = "/bin/bash" ]; then
  echo "Votre shell de login est le bash (bourne again shell)"
else
  echo "Votre shell de login n'est pas bash mais $SHELL"
fi
La variable $SHELL contient le nom du shell de login et c'est ce que nous testons ici en la comparant avec la chaîne de caractères "/bin/bash"

Les Opérateurs Condensés (Shortcut Operators)
Les personnes familières avec le langage C apprécieront l'expression suivante :
[ -f "/etc/shadow" ] && echo "Cet ordinateur utilise les shadow passwords"
La syntaxe && peut être utilisée comme un raccourci de l'instruction if. La partie droite de l'instruction est exécutée si la partie gauche est vraie. On peut lire cette instruction comme une instruction "AND" (ET). Ainsi, l'exemple devient : "le fichier /etc/shadow existe ET la commande echo est exécutée". L'opérateur "OR" (OU) (dont le symbole est ||) est également disponible. Voici un exemple :
#!/bin/sh
mailfolder=/var/spool/mail/james
[ -r "$mailfolder" ] || { echo "Je ne peux pas lire $mailfolder" ; exit 1; }
echo "$mailfolder a recu un courrier de :"
grep "^From " $mailfolder
Ce script teste tout d'abord s'il peut lire un mailfolder ("dossier de courrier") précis. Si c'est le cas, il affiche les lignes "From" du dossier. S'il ne peut lire le fichier $mailfolder, alors l'opérateur "OR" entre en action. En bon français, vous liriez cette ligne de code comme "Dossier de courrier lisible ou sortie du programme" Le problème, ici, c'est qu'après OR on ne peut mettre qu'une seule commande, mais il nous en faut deux :
- afficher un message d'erreur
- sortir du programme
Pour les transformer en une seule commande, nous pouvons les regrouper dans une fonction anonyme en utilisant des accolades. Les fonctions dans leur ensemble sont décrites plus loin.
Vous pouvez tout faire sans les opérateurs AND et OR, uniquement en utilisant les instructions if, mais souvent les raccourcis des opérateurs ci-dessus sont plus pratiques.

L'instruction case peut être utilisée pour faire correspondre (en utilisant les méta-caractères du shell, tels que * et ?) une chaîne de caractères donnée avec une liste de possibilités
case ... in
...) action ;;
esac
Prenons un exemple. La commande file peut indiquer le type d'un fichier :
file lf.gz
retourne :
lf.gz: gzip compressed data, deflated, original filename, 
last modified: Mon Aug 27 23:09:18 2001, os: Unix
Nous allons l'utiliser pour écrire un script nommé smartzip qui peut décompresser automatiquement des fichiers bzip2, gzip et zip :
#!/bin/sh
ftype=`file "$1"`
case "$ftype" in
"$1: Zip archive"*)
    unzip "$1" ;;
"$1: gzip compressed"*)
    gunzip "$1" ;;
"$1: bzip2 compressed"*)
    bunzip2 "$1" ;;
*) error "Le fichier $1 ne peut pas etre décompressé avec smartzip";;
esac
Vous remarquerez que nous avons utilisé ici une nouvelle variable appelée $1. Cette variable contient le premier argument donné au programme. Supposons que nous exécutions
smartzip articles.zip
alors la variable $1 contient la chaîne de caractères "article.zip".

L'instruction select est spécifique à bash et elle est très pratique pour une utilisation interactive. L'utilisateur peut faire son choix parmi une liste de valeurs diférentes :
select var in ... ; do
  break
done
.... maintenant $var peut être utilisé ....
Voici un exemple :
#!/bin/sh
echo "Quel est votre OS préféré ?"
select var in "Linux" "Gnu Hurd" "Free BSD" "Other"; do
        break
done
echo "Vous avez sélectionné $var"
Voici ce que fait ce script :
Quel est votre OS préféré ?
1) Linux
2) Gnu Hurd
3) Free BSD
4) Other
#? 1
Vous avez sélectionné Linux
Dans le shell, vous disposez des instructions de boucles suivantes :
while ...; do
 ....
done
La boucle while va être exécutée tant que l'expression testée est vraie. Le mot clé break peut être utilisé pour quitter la boucle n'importe quand. Avec le mot clé continue, la boucle se poursuit à l'itération suivante en ignorant le reste du corps de la boucle.

La boucle for prend une liste de chaînes de caractères (séparées par des espaces) et les affecte à une variable :
for var in ....; do
  ....
done
Le code suivant affiche les lettres de A à C à l'écran :
#!/bin/sh
for var in A B C ; do
  echo "var est $var"
done
Voici un exemple de script plus utile, appelé showrpm, qui affiche un résumé du contenu de plusieurs paquetages RPM :
#!/bin/sh
# liste un résumé du contenu de plusieurs paquetages RPM
# USAGE: showrpm rpmfile1 rpmfile2 ...
# EXEMPLE: showrpm /cdrom/RedHat/RPMS/*.rpm
for rpmpackage in $*; do
  if [ -r "$rpmpackage" ];then
    echo "=============== $rpmpackage ==============="
    rpm -qi -p $rpmpackage
  else
    echo "ERREUR: showrpm ne peut pas lire le fichier $rpmpackage"
  fi
done
Vous noterez ci-dessus, l'apparition d'une nouvelle variable spéciale, $*, qui contient la liste de tous les arguments de la ligne de commande. Si vous exécutez
showrpm openssh.rpm w3m.rpm webgrep.rpm
la variable $* contient alors les trois chaînes de caractères openssh.rpm, w3m.rpm et webgrep.rpm.

Le bash GNU connaît également les boucles "until", mais généralement, les boucles while et for suffisent.

"Quoter" (mettre une expression entre guillemets simples)
Avant de passer un argument à un programme, le shell essaye d'étendre les méta-caractères et les variables. Étendre les méta-caractères (ex *) signifie qu'ils sont remplacés par les noms de fichiers appropriés ou qu'une variable est remplacée par sa valeur.Pour modifier ce comportement vous pouvez utiliser les guillemets simples. Supposons que nous ayons plusieurs fichiers dans le répertoire courant. Deux d'entre eux sont des fichiers jpg, mail.jpg et tux.jpg.
#!/bin/sh
echo *.jpg
Ceci affichera "mail.jpg tux.jpg".
Les guillemets (simple et double) vont empêcher cette extension des méta-caractères :
#!/bin/sh
echo "*.jpg"
echo '*.jpg'
Ce programme va afficher deux fois "*.jpg".
Les guillemets simples sont beaucoup plus stricts. Ils empêchent également l'extension des variables.Les guillemets doubles empêchent l'extension des méta-caractères mais autorisent celle des variables :
#!/bin/sh
echo $SHELL
echo "$SHELL"
echo '$SHELL'
Ce programme affichera :
/bin/bash
/bin/bash
$SHELL
Enfin il est possible de retirer la signification particulière de n'importe quel caractère en le faisant précéder d'un anti-slash :
echo \*.jpg
echo \$SHELL
Qui affichera :
*.jpg
$SHELL
Les "Here documents"
Les "Here documents" sont un moyen pratique pour envoyer plusieurs lignes de texte à une commande. C'est utile pour écrire un texte d'aide dans un script sans avoir à mettre une commande echo au début de chaque ligne. Un "here document" commence par << suivi par une chaîne de caractères qui doit également apparaître à la fin du "here document". Voici un script d'exemple, nommé ren, qui renomme de nombreux fichiers et utilise un "here document" dans son texte d'aide :
#!/bin/sh
# Si nous avons moins de 3 arguments, on affiche le message d'aide
if [ $# -lt 3 ] ; then
cat <<HELP
ren -- renomme plusieurs fichiers en utilisant des expressions regulières de sed

USAGE: ren 'regexp' 'replacement' files...

EXEMPLE: renomme tous les fichiers *.HTM en *.html:
  ren 'HTM$' 'html' *.HTM

HELP
  exit 0
fi
OLD="$1"
NEW="$2"
# la command shift retire un argument de la liste des arguments de 
# la ligne de commande
shift
shift
# la variable $* contient maintenant tous les fichiers.
for file in $*; do
    if [ -f "$file" ] ; then
      newfile=`echo "$file" | sed "s/${OLD}/${NEW}/g"`
      if [ -f "$newfile" ]; then
        echo "ERREUR: $newfile existe deja"
      else
        echo "fichier $file renommé en $newfile ..."
        mv "$file" "$newfile"
      fi
    fi
done
Ce script est le plus complexe écrit jusqu'ici. Étudions-le plus en détail. La première instruction if teste si nous avons bien fourni au programme un minimum de trois arguments de ligne de commande (la variable spéciale $# contient le nombre d'arguments). Si ce n'est pas le cas, le texte d'aide est envoyé à la commande cat, qui, à son tour, l'affiche sur l'écran. Une fois cette aide affichée, nous sortons du programme. Si trois arguments ou plus, sont présents, le premier est affecté à la variable OLD et le second à la variable NEW. Nous éliminons ensuite ces deux arguments de la ligne de commande, par la commande shift, pour placer le troisième argument en première position dans $*. Avec $* nous entrons dans la boucle. Les arguments dans $* sont affectés un par un à la variable $file. Nous vérifions d'abord que le fichier existe puis nous construisons un nouveau nom de fichier en utilisant le "chercher-remplacer" de sed. Les "backticks" sont utilisés pour affecter le résultat à la variable newfile. Nous avons maintenant tout le néssaire : l'ancien nom de fichier et le nouveau. Ceux-ci vont être utilisés par la commande mv pour renommer le fichier.

Les Fonctions
A partir du moment où votre programme devient de plus en plus complexe, vous réalisez très vite que vous utilisez le même code dans plusieurs endroits et vous trouvez qu'il devient pratique de lui donner une certaine structure. Voici à quoi ressemble une fonction :
nom_de_la_fonction()
{
 # dans le corps de la fonction, $1 est le 1er argument passé à celle-ci
 # $2 le second ...
 corps de la fonction
}
Une fonction doit être déclarée au début du script, avant toute utilisation.
Voici un script nommé xtitlebar, que vous pouvez utiliser pour changer le nom d'une fenêtre de terminal. Si vous en avez plusieurs d'ouvertes , elles sont plus faciles à trouver. Le script envoie une séquence d'échapement interprétée par le terminal qui lui fait changer le nom dans la barre de titre. Le script utilise une fonction appelée help. Comme vous pouvez le constater, cette fonction n'est définie qu'une seule fois mais appelée deux fois :
#!/bin/sh
# vim: set sw=4 ts=4 et:

help()
{
    cat <<HELP
xtitlebar -- change le nom d'un xterm, gnome-terminal ou d'une konsole kde

USAGE: xtitlebar [-h] "string_for_titelbar"

OPTIONS: -h help texte

EXEMPLE: xtitlebar "cvs"

HELP
    exit 0
}

# en cas d'erreur ou si l'option -h est passée en argument, nous appelons la
# fonction help :
[ -z "$1" ] && help
[ "$1" = "-h" ] && help

# envoie la sequence d'échappement pour changer la barre de titre du xterm :
echo -e "\033]0;$1\007"
#
Une bonne habitude à prendre est de toujours avoir une aide la plus complète possible dans les scripts. Ceci permet à d'autres personnes (vous compris) de comprendre et d'utiliser le script.

Les arguments de la ligne de commande
Nous avons vu que les variables $*, $1, $2 ...$9 contiennent les arguments que l'utilisateur a spécifiés sur la ligne de commande (les chaînes de caractères écrites derrière le nom du programme). Jusqu'ici nous n'avons eu que des exemples plutôt simples de la syntaxe de la ligne de commande (quelques arguments requis et l'option -h pour l'aide). Cependant, vous allez bientôt réaliser que vous avez besoin d'une sorte d'analyseur qui vous permettra d'écrire des programmes plus complexes dans lesquels vous définirez vos propres options. La convention est que tout paramètre optionnel soit précédé d'un signe moins, et doit intervenir avant tout autre argument (tel qu'un nom de fichier).
Il existe de nombreuses manières de créer un analyseur. La boucle while qui suit, combinée à une instruction case est une excellente solution pour un analyseur générique :
#!/bin/sh
help()
{
  cat <<HELP
ceci est un exemple d'analyseur générique de ligne de commande
USAGE EXEMPLE: cmdparser -l hello -f -- -somefile1 somefile2
HELP
  exit 0
}

while [ -n "$1" ]; do
case $1 in
    -h) help;shift 1;; # appel de la fonction help
    -f) opt_f=1;shift 1;; # la variable opt_f est définie
    -l) opt_l=$2;shift 2;; # -l prend un argument -> décalé de 2
    --) shift;break;; # fin des options
    -*) echo "erreur: il n'existe pas d'option $1. -h pour l'aide";exit 1;;
    *)  break;;
esac
done

echo "opt_f est $opt_f"
echo "opt_l est $opt_l"
echo "premier arg est $1"
echo "2nd arg est $2"
Essayez-le ! Vous pouvez l'exécuter comme suit :
cmdparser -l hello -f -- -somefile1 somefile2
Vous obtenez :
opt_f est 1
opt_l est hello
premier arg est -somefile1
2nd arg est somefile2
Comment ça marche ? Le script boucle sur la liste de tous les arguments et les compare aux conditions de test de l'instruction case. S'il trouve un argument qui correspond, il positionne alors la variable et décale la ligne de commande d'une position. La convention Unix veut que les options (ce qui commence par un signe moins) soient placées en premier. Vous pouvez indiquer la fin d'une option en inscrivant deux signes moins (--). grep le réclame, par exemple, pour rechercher une chaîne de caractères commençant par un signe moins :
Recherche de -xx- dans le fichier f.txt:
grep -- -xx- f.txt
Notre analyseur d'option peut également gérer l'option -- comme vous pouvez le voir dans la liste ci-dessus.

Exemples

Un squelette généraliste
Nous avons abordé pratiquement tous les composants nécessaires à l'écriture d'un script. Tout bon script se doit d'avoir une aide ainsi qu'un analyseur générique d'option même si le script n'a qu'une seule option. De ce fait, il devient pratique d'avoir une maquette de script nommée framework.sh, que l'on utilise comme ossature pour les autres scripts. Si vous voulez écrire un nouveau script vous n'avez plus qu'à en faire une copie :
cp framework.sh myscript
et inclure les nouvelles fonctionnalités dans "myscript".

Prenons deux nouveaux exemples :
Un convertisseur de nombre binaire en nombre décimal
Le script b2d convertit un nombre binaire (tel que 1101) en nombre décimal. Cet exemple montre qu'on peut aisément faire des calculs simples avec expr :
#!/bin/sh
# vim: set sw=4 ts=4 et:
help()
{
  cat <<HELP
b2h -- convertit un binaire en décimal.

USAGE: b2h [-h] nombre_binaire

OPTIONS: -h message d'aide

EXAMPLE: b2h 111010
will return 58
HELP
  exit 0
}

error()
{
    # affiche une erreur et quitte le programme
    echo "$1"
    exit 1
}

lastchar()
{
    # retourne le dernier caractère d'une chaîne dans $rval
    if [ -z "$1" ]; then
        # chaîne vide
        rval=""
        return
    fi
    # wc place des espaces derrière la sortie, c'est pourquoi nous utilisons sed
    numofchar=`echo -n "$1" | wc -c | sed 's/ //g' `
    # nous coupons le dernier caractère
    rval=`echo -n "$1" | cut -b $numofchar`
}

chop()
{
    # retire le dernier caractère de la chaîne qui est renvoyé dans $rval
    if [ -z "$1" ]; then
        # empty string
        rval=""
        return
    fi
    # wc place des espaces derrière la sortie, c'est pourquoi nous utilisons sed
    numofchar=`echo -n "$1" | wc -c | sed 's/ //g' `
    if [ "$numofchar" = "1" ]; then
        # un seul caractère dans la chaîne
        rval=""
        return
    fi
    numofcharminus1=`expr $numofchar "-" 1` 
    # conserve tout, sauf le dernier caractère 
    rval=`echo -n "$1" | cut -b 0-${numofcharminus1}`
}
    

while [ -n "$1" ]; do
case $1 in
    -h) help;shift 1;; # appel de la fonction help
    --) shift;break;; # fin des options
    -*) error "error: pas d'option $1. -h pour l'aide";;
    *)  break;;
esac
done

# Programme principal
sum=0
weight=1
# un arg doit être fourni
[ -z "$1" ] && help
binnum="$1"
binnumorig="$1"

while [ -n "$binnum" ]; do
    lastchar "$binnum"
    if [ "$rval" = "1" ]; then
        sum=`expr "$weight" "+" "$sum"`
    fi
    #retire la dernière position dans $binnum
    chop "$binnum"
    binnum="$rval"
    weight=`expr "$weight" "*" 2`
done

echo "le binaire $binnumorig est le décimal $sum"
#
L'algorithme, utilisé dans ce script, prend le poids décimal de chaque chiffre (1, 2, 4, 8, 16...), en partant du chiffre le plus à droite, et l'ajoute à la somme si le chiffre est un 1. Ainsi, "10" se décompose comme suit :
0 * 1 + 1 * 2 = 2
Pour récupérer les chiffres à partir de la chaîne, nous utilisons la fonction lastchar. Cette fonction utilise wc -c pour compter le nombre de caractères dans la chaîne, puis cut pour en extraire le dernier caractère. La fonction chop conserve la même logique mais retire le dernier caractère, c'est-à-dire elle garde tout depuis le début de la chaîne jusqu'à l'avant-dernier caractère.

Un programme de rotation de fichiers
Vous êtes peut-être de ceux qui enregistrent le courrier envoyé dans un fichier. Au bout de quelques mois ce fichier a fortement grossi et son chargement dans votre programme de messagerie s'en trouve ralenti. Le script suivant, rotatefile peut vous être utile. Il renomme le "dossier de courrier", appelons-le outmail, en outmail.1. S'il existe déjà un tel fichier, il le renomme en outmail.2, etc.
#!/bin/sh
# vim: set sw=4 ts=4 et: 
ver="0.1"
help()
{
    cat <<HELP
rotatefile -- effectue une rotation de nom de fichier

USAGE: rotatefile [-h]  nom_fichier

OPTIONS: -h affiche l'aide

EXEMPLE: rotatefile out
Ceci va, par exemple, renommer out.2 en out.3, out.1 en out.2, out en out.1
et créer un fichier vide out.

Le nombre maximum de fichier est 10

version $ver
HELP
    exit 0
}

error()
{
    echo "$1"
    exit 1
}
while [ -n "$1" ]; do
case $1 in
    -h) help;shift 1;;
    --) break;;
    -*) echo "erreur: pas d'option $1. -h pour l'aide";exit 1;;
    *)  break;;
esac
done

# input check:
if [ -z "$1" ] ; then
 error "ERREUR: vous devez spécifier un fichier, utilisez -h pour l'aide" 
fi
filen="$1"
# rename any .1 , .2 etc file:
for n in  9 8 7 6 5 4 3 2 1; do
    if [ -f "$filen.$n" ]; then
        p=`expr $n + 1`
        echo "mv $filen.$n $filen.$p"
        mv $filen.$n $filen.$p
    fi
done
# renomme le fichier original :
if [ -f "$filen" ]; then
    echo "mv $filen $filen.1"
    mv $filen $filen.1
fi
echo touch $filen
touch $filen

Comment fonctionne le programme ? Après avoir vérifié que l'utilisateur a fourni un nom de fichier, le programme entre dans une boucle décrémentale de 9 à 1. Le fichier 9, est alors renommé 10, le fichier 8 est renommé 9, et ainsi de suite. A la sortie de la boucle nous renommons le fichier original en 1 et nous créons un fichier vide avec le nom du fichier original.

Débogage

L'aide au débogage la plus simple est, bien sûr, la commande echo. Vous pouvez vous en servir pour afficher les variables aux alentours de l'endroit où vous suspectez l'erreur. C'est probablement ce que font les programmeurs en shell 80 % du temps pour chercher les erreurs. L'avantage d'un script shell est qu'il ne nécessite pas de recompilation et insérer une commande echo est très rapide.

Le shell a également, un mode debug. Si vous avez des erreurs dans le script "scriptetrange", vous pouvez le déboguer comme suit : :
sh -x scriptetrange
Cette commande lancera le script en affichant toutes les instructions exécutées ainsi que les valeurs des variables et des méta-caractères.
Le shell possède également un mode de vérification d'erreur de syntaxe sans exécution réelle du programme. Pour l'utiliser, exécutez :
sh -n votre_script
Si vous ne voyez rien sur votre écran, c'est que votre programme ne contient pas d'erreur de syntaxe.

Nous espérons que vous allez maintenant commencer à écrire vos propres scripts shell. Amusez-vous bien !

Références