Chapitre 8. Ecrire des scripts interactifs

Table des matières

1. Afficher les messages utilisateurs
1.1. Interactif ou pas?
1.2. Utiliser la commande intégrée echo
2. Récupérer la saisie utilisateur
2.1. L'emploi de la commande intégrée read
2.2. Demander une entrée utilisateur
2.3. Redirection et descripteurs de fichiers
2.4. Fichier d'entrée et fichier de sortie
3. Résumé
4. Exercices

Résumé

Dans ce chapitre nous expliquerons comment interagir avec les utilisateurs de nos scripts:

  • En affichant des messages et des explications conviviaux à l'intention des utilisateurs

  • Récupérer la saisie utilisateur

  • Demander une entrée utilisateur

  • Utiliser les descripteurs de fichiers pour lire et écrire depuis et sur de multiples fichiers

1. Afficher les messages utilisateurs

1.1. Interactif ou pas?

Certains scripts s'exécutent sans aucune interaction avec l'utilisateur. Les avantages des scripts non interactifs comprennent:

  • Le script s'exécute d'une façon prédictible chaque fois.

  • Le script peut s'exécuter en tâche de fond.

Beaucoup de scripts, cependant, demandent des entrées de la part de l'utilisateur, ou donnent des résultats à l'utilisateur alors que le script tourne. Les avantages des scripts interactifs sont, parmi d'autres:

  • Scripts plus flexibles

  • L'utilisateur peut adapter le script pendant qu'il est lancé ou plutôt faire en sorte qu'il agisse de diverses façons.

  • Le script peut afficher la progression du traitement.

Quand vous écrivez un script interactif, ne jamais être avare de commentaires. Un script qui affiche des messages appropriés est bien plus convivial et peut être plus facilement débuggé. Un script peut effectuer un traitement tout à fait correct, mais vous aurez bien plus d'appels d'utilisateurs si il ne les informe pas de l'état d'avancement. Donc inclure des messages qui demandent à l'utilisateur d'attendre le résultat parce que le traitement est en cours. Si possible, essayer d'indiquer combien de temps l'utilisateur aura à attendre. Si l'attente doit régulièrement durer un bon moment à l'exécution de certaines tâches, vous pouvez considérer l'intérêt d'intégrer la situation du processus dans le résultat du script.

Quand vous sollicitez de l'utilisateur une saisie, c'est mieux aussi de donner plutôt trop que pas assez d'informations au sujet du type de données attendues. Ceci s'applique au contrôle des paramètres et au message mode d'emploi l'accompagnant.

Bash a les commandes echo et printf pour fournir des commentaires aux utilisateurs, et bien que vous devriez être familiarisé maintenant au moins avec echo, nous verrons d'autres exemples dans les sections suivantes.

1.2. Utiliser la commande intégrée echo

La commande intégrée echo affiche ses arguments, séparés par des espaces, et termine par un saut de ligne. Le statut renvoyé est toujours zéro. echo a plusieurs options:

  • -e: interprète les caractères protégés.

  • -n: supprime les sauts de ligne résiduels de la fin.

Comme exemple d'ajout de commentaires, nous écrirons le feed.sh et le penguin.sh de la Section 2.1.2, « Contrôle des paramètres de la ligne de commande » de façon améliorée:

michel ~/test> cat penguin.sh
#!/bin/bash

# Ce script vous laisse présenter divers menus à Tux.  Il ne sera heureux que
# quand il aura du poisson.  Pour s'amuser un peu, nous ajoutons quelques animaux.

if [ "$menu" == "poisson" ]; then
  if [ "$animal" == "pingouin" ]; then
    echo -e "Hmmmmmm poisson... Tux heureux!\n"
  elif [ "$animal" == "dauphin" ]; then
    echo -e "\a\a\aPweetpeettreetppeterdepweet!\a\a\a\n"
  else
    echo -e "*prrrrrrrt*\n"
  fi
else
  if [ "$animal" == "pingouin" ]; then
    echo -e "Tux déteste ça.  Tux veut du poisson!\n"
    exit 1
  elif [ "$animal" == "dauphin" ]; then
    echo -e "\a\a\a\a\a\aPweepwishpeeterdepweet!\a\a\a"
    exit 2
  else
    echo -e "Voulez-vous lire cette affiche?!  Ne pas nourrir les "$animal"s!\n"
    exit 3
  fi
fi

michel ~/test> cat feed.sh
#!/bin/bash
# Ce script agit en fonction du statut d'exécution renvoyé par penguin.sh

if [ "$#" != "2" ]; then
  echo -e "Utilisation du script feed:\t$0 nourriture nom-animal \n"
  exit 1
else

  export menu="$1"
  export animal="$2"

  echo -e "Nourrissage $menu to $animal...\n"

  feed="/nethome/anny/testdir/penguin.sh"

  $feed $menu $animal

result="$?"

  echo -e "Nourrissage fait.\n"

case "$result" in

  1)
    echo -e "Gaffe: \"Vous feriez mieux de lui donner du poisson, Sinon il s'énerve...\"\n"
    ;;
  2)
    echo -e "Gaffe: \"Pas étonnant qu'il fuit notre planète...\"\n"
    ;;
  3)
    echo -e "Gaffe: \"Achetez la nourriture fournie par le zoo à l'entrée, i***\"\n"
    echo -e "Gaffe: \"Vous voulez les empoisonner?\"\n"
    ;;
  *)
    echo -e "Gaffe: \"N'oubliez pas le guide!\"\n"
    ;;
  esac

fi

echo "Fin..."
echo -e "\a\a\aMerci de votre visite. En espérant vous revoir bientôt!\n"

michel ~/test> feed.sh apple camel
Nourrir le chameau avec des pommes...

Avez-vous vu l'affiche?!  Ne pas nourrir les chameaux!

Nourrissage fait.

Gaffe: "Achetez la nourriture que le zoo fournie à l'entrée, i ***"

Gaffe: "Vous voulez les empoisonner?"

Fin...
Merci de votre visite. En espérant vous revoir bientôt!

michel ~/test> feed.sh apple
Utilisation du script feed:       ./feed.sh menu nom-animal

Plus d'informations sur les caractères d'échappement à la Section 3.2, « Le caractère Echap (escape) ». Le tableau suivant donne un aperçu des séquences reconnues par la commande echo:

Tableau 8.1. Séquences d'échappement reconnues par la commande echo

Séquencesens
\aAlerte (sonnerie).
\bRetour arrière.
\cSupprime les saut de lignes résiduel à la fin.
\eEscape.
\fSaut de page.
\nSaut de ligne.
\rRetour chariot.
\tTabulation horizontale.
\vTabulation verticale.
\\Slash inversé.
\ONNNLe caractère sur 8 bits dont la valeur en base octal est NNN (de 0 à 3 chiffres en octal).
\NNNLe caractère sur 8 bits dont la valeur en base octal est NNN (de 1 à 3 chiffres en octal).
\xHHLe caractère sur 8 bits avec la valeur en base hexadécimale (de 1 à 2 chiffres en hexadécimal).

Pour plus d'information sur la commande printf et la façon dont elle permet de formater les résultats, voir les pages info de Bash.

2. Récupérer la saisie utilisateur

2.1. L'emploi de la commande intégrée read

La commande intégrée read est la contrepartie de echo et printf. La syntaxe de la commande read est la suivante:

read [options] NAME1 NAME2 ... NAMEN

Une ligne est lue depuis l'entrée standard, ou depuis le fichier dont le descripteur est fourni en argument à l'option -u . Le premier mot de la ligne est affecté au premier nom NAME1, le second mot au second nom, et ainsi de suite, avec les mots résiduels et leurs séparateurs affectés au dernier nom NAMEN. Si il y a moins de mots lus sur le flot d'entrée qu'il y a de noms, les noms résiduels sont valorisés à vide.

Les caractères de la valeur de la variable IFS sont employés pour découper l'entrée en mots ou jetons; voir la Section 4.8, « Le découpage de mots ». Le caractère slash inversé peut être utilisé pour inhiber le sens particulier du caractère lu suivant et pour la continuation de la ligne.

Si aucun nom n'est fourni, la ligne lue est affectée à la variable REPLY.

La commande read renvoie un code à zéro, sauf si un caractère de fin de fichier est rencontré, si read dépasse son temps imparti ou si un invalide descripteur de fichier est fourni en argument à l'option -u .

Les options suivantes sont supportées par l'intégrée Bash read:

Tableau 8.2. Options de l'intégrée read

Optionsens
-a ANAMELes mots sont affectés séquentiellement aux éléments de la variable tableau ANAME, en commençant par l'index 0. Tous les éléments sont supprimés de ANAME avant l'affectation. Les autres arguments NAME sont ignorés.
-d DELIMLe premier caractère de DELIM est utilisé pour terminer la ligne entrée, plutôt que le saut de ligne.
-ereadline est utilisé pour obtenir la ligne.
-n NCHARSread s'arrête après avoir lu NCHARS caractères plutôt que d'attendre une ligne entrée complète.
-p PROMPTAffiche le PROMPT, sans saut de ligne final, avant de tenter de lire tout autre entrée. L'invite est affichée seulement si l'entrée vient d'un terminal.
-rSi cette option est donnée, le slash inversé n'agit pas comme caractère d'échappement. Le slash inversé est considéré comme faisant parti de la ligne. En particulier, un couple "saut de ligne-slash inversé" ne devrait pas être utilisé comme symbole de continuation de ligne.
-sMode Silencieux. Si l'entrée provient d'un terminal, les caractères n'y sont pas renvoyés.
-t TIMEOUTDonne un temps imparti à read et renvoie une erreur si une ligne complète n'a pas été lue avant TIMEOUT secondes. Cette option n'a pas d'effet si read n'a pas son entrée depuis un terminal ou un tube.
-u FDObtenir les lignes entrées depuis le fichier de descripteur FD.

Ceci est un rapide exemple, améliorant le script leaptest.sh du chapitre précédent:

michel ~/test> cat leaptest.sh
#!/bin/bash
# Ce script teste si vous avez saisi une année bissextile ou pas.

echo "Saisissez une année que vous voulez tester  (4 chiffres), puis appuyer sur [ENTREE]:"

read year

if (( ("$year" % 400) == "0" )) || (( ("$year" % 4 == "0") && ("$year" % 100 !=
"0") )); then
  echo "$année bissextile."
else
  echo "année non bissextile."
fi

michel ~/test> leaptest.sh
Saisissez une année que vous voulez tester  (4 chiffres), puis appuyer sur [ENTREE]:
2000
2000 année bissextile.

2.2. Demander une entrée utilisateur

L'exemple suivant montre comment vous pouvez vous servir de l'invite pour expliquer ce que l'utilisateur devrait saisir.

michel ~/test> cat friends.sh
#!/bin/bash

# Ce programme garde votre carnet d'adresse à jour.

friends="/var/tmp/michel/friends"

echo "Bonjour, "$USER".  Ce script vous enregistrera dans la base de données des amis de Michel."

echo -n "Saisir votre nom et appuyer sur  [ENTREE]: "
read name
echo -n "Saisir votre sexe et appuyer sur [ENTREE]: "
read -n 1 gender
echo

grep -i "$name" "$friends"

if  [ $? == 0 ]; then
  echo "Vous êtes déjà enregistré, terminé."
  exit 1
elif [ "$gender" == "m" ]; then
  echo "Vous êtes ajouté à la liste des amis de Michel."
  exit 1
else
  echo -n "Quel âge avez-vous? "
  read age
  if [ $age -lt 25 ]; then
    echo -n "De quelle couleur sont vos cheveux? "
    read colour
    echo "$name $age $colour" >> "$friends" 
    echo "Vous êtes ajouté à la liste des amis de Michel.  Merci beaucoup!"
  else
    echo "Vous êtes ajouté à la liste des amis de Michel."
    exit 1
  fi
fi

michel ~/test> cp friends.sh /var/tmp; cd /var/tmp

michel ~/test> touch friends; chmod a+w friends

michel ~/test> friends.sh
Bonjour, michel.  Ce script vous enregistrera dans la base de données des amis de Michel.
Saisir votre nom et appuyer sur  [ENTREE]: michel
Saisir votre sexe et appuyer sur [ENTREE]: m
Vous êtes ajouté à la liste des amis de Michel.

michel ~/test> cat friends

Remarquez qu'aucun affichage n'est omis ici. Le script enregistre seulement les informations sur les gens qui intéressent Michel, mais il avertira toujours que vous avez été ajouté à la liste, sauf si vous y êtes déjà.

D'autres gens peuvent maintenant lancer le script:

[anny@octarine tmp]$ friends.sh
Bonjour, anny.  Ce script vous enregistrera dans la base de données des amis de Michel.
Saisir votre nom et appuyer sur  [ENTREE]: anny
Saisir votre sexe et appuyer sur [ENTREE]:f
Quel âge avez-vous? 22
De quelle couleur sont vos cheveux? noir
Vous êtes ajouté à la liste des amis de Michel.

Finalement, la liste friends ressemble à ceci:

tille 24 noir
anny 22 noir
katya 22 blonde
maria 21 noir
--output omitted--

Bien sûr, cette situation n'est pas idéale, parce que chacun peut renseigner (mais pas se retirer) du fichier de Michel. Vous pouvez palier ce défaut en utilisant des accès privilégiés au fichier de script, voir SUID et SGID dans le guide d'introduction à Linux.

2.3. Redirection et descripteurs de fichiers

2.3.1. Généralité

Comme vous avez pu le réaliser à la suite d'une utilisation rudimentaire du Shell, l'entrée et la sortie d'une commande peuvent être redirigées avant son exécution, grâce à la notation appropriée - les opérateurs de redirection - interprétée par le Shell. La redirection peut aussi être employée pour ouvrir et fermer des fichiers dans l'environnement d'exécution du Shell.

La redirection peut aussi se produire dans le script, de sorte qu'il puisse recevoir son entrée depuis un fichier, par exemple, ou qu'il puisse envoyer les résultats vers un fichier. Ultérieurement, l'utilisateur peut visualiser le fichier de résultat, ou il peut être exploité en entrée par un autre script.

Les fichiers d'entrée et de sortie sont désignés par des entiers indicateurs qui repèrent tout les fichiers ouverts d'un processus donné. Ces valeurs numériques sont connues sous le nom de descripteurs de fichiers. Les descripteurs les plus courant sont stdin, stdout et stderr, avec les numéros 0, 1 et 2, respectivement. Ces numéros et leur entité respective sont réservés. Bash peut aussi considérer des ports TCP ou UDP sur les hôtes du réseau en tant que descripteur.

L'affichage ci-dessous montre comment les descripteurs réservés pointent sur des entités concrètes:

michel ~> ls -l /dev/std*
lrwxrwxrwx  1 root    root     17 Oct  2 07:46 /dev/stderr -> ../proc/self/fd/2
lrwxrwxrwx  1 root    root     17 Oct  2 07:46 /dev/stdin -> ../proc/self/fd/0
lrwxrwxrwx  1 root    root     17 Oct  2 07:46 /dev/stdout -> ../proc/self/fd/1

michel ~> ls -l /proc/self/fd/[0-2]
lrwx------  1 michel  michel   64 Jan 23 12:11 /proc/self/fd/0 -> /dev/pts/6
lrwx------  1 michel  michel   64 Jan 23 12:11 /proc/self/fd/1 -> /dev/pts/6
lrwx------  1 michel  michel   64 Jan 23 12:11 /proc/self/fd/2 -> /dev/pts/6

Vous pouvez consulter info MAKEDEV et info proc pour plus de détails sur le sous-répertoire /proc et la façon dont votre système gère les descripteurs standards de chaque processus lancé.

Quand vous lancez un script depuis la ligne de commande, rien ne change tellement parce que le processus Shell enfant utilisera les mêmes descripteurs que son parent. Quand le parent n'existe pas, par exemple quand vous lancez un script par l'outil cron les descripteurs standards sont des tubes et autres fichiers (temporaires), à moins qu'un autre moyen de redirection soit employé. Ceci est démontré dans l'exemple ci-dessous, lequel produit le résultat avec un simple script at:

michel ~> date
Fri Jan 24 11:05:50 CET 2003

michel ~> at 1107
avertissement: les commandes seront exécutées avec (par ordre) 
a) $SHELL b) login shell c)/bin/sh
at> ls -l /proc/self/fd/ > /var/tmp/fdtest.at
at> <EOT>
job 10 at 2003-01-24 11:07

michel ~> cat /var/tmp/fdtest.at
total 0
lr-x------    1 michel michel  64 Jan 24 11:07 0 -> /var/spool/at/!0000c010959eb (deleted)
l-wx------    1 michel michel  64 Jan 24 11:07 1 -> /var/tmp/fdtest.at
l-wx------    1 michel michel  64 Jan 24 11:07 2 -> /var/spool/at/spool/a0000c010959eb
lr-x------    1 michel michel  64 Jan 24 11:07 3 -> /proc/21949/fd

Et un avec cron:

michel ~> crontab -l
# NE PAS EDITER CE FICHIER - éditer le modèle et le réinstaller.
# (/tmp/crontab.21968 installed on Fri Jan 24 11:30:41 2003)
# (Cron version -- $Id: chap8.xml,v 1.8 2005/09/05 12:39:22 tille Exp $)
32 11 * * * ls -l /proc/self/fd/ > /var/tmp/fdtest.cron

michel ~> cat /var/tmp/fdtest.cron
total 0
lr-x------    1 michel michel  64 Jan 24 11:32 0 -> pipe:[124440]
l-wx------    1 michel michel  64 Jan 24 11:32 1 -> /var/tmp/fdtest.cron
l-wx------    1 michel michel  64 Jan 24 11:32 2 -> pipe:[124441]
lr-x------    1 michel michel  64 Jan 24 11:32 3 -> /proc/21974/fd

2.3.2. Redirection des erreurs

Dans l'exemple précédent il apparaît clairement que vous pouvez fournir les fichiers d'entrée et de sortie à un script (voir Section 2.4, « Fichier d'entrée et fichier de sortie » pour plus de détails), mais certains oublient de rediriger les erreurs - un affichage dont peut dépendre la suite. Aussi, si vous êtes chanceux, les erreurs vous seront adressées par mail et d'éventuels dysfonctionnements pourront vous apparaître. Si vous n'êtes pas chanceux, les erreurs feront planter votre script et ne seront ni capturées ni adressées nulle part, par conséquent vous ne pourrez débugger.

Quand vous redirigez les erreurs, faites attention à l'ordre de préséance. Par exemple, cette commande lancée dans /var/spool

ls -l * 2 > /var/tmp/unaccessible-in-spool

redirigera l'affichage de la commande ls dans le fichier unaccessible-in-spool in /var/tmp. La commande

ls -l * > /var/tmp/spoollist 2 >& 1

redirigera et le standard d'entrée et le standard d'erreur vers le fichier spoollist. La commande

ls -l * 2 >& 1 > /var/tmp/spoollist

dirige seulement le standard de sortie vers le fichier de destination, parce que le standard d'erreurs est copié vers le standard de sortie avant que le standard de sortie soit redirigé.

Par commodité, les erreur sont souvent redirigées vers /dev/null, si il est sûr qu'elles n'ont pas d'intérêt. Des centaines d'exemples peuvent être trouvés dans les scripts de lancement de votre système.

Bash autorise à la fois le standard de sortie et le standard d'erreurs à être redirigés vers le fichier dont le nom est le résultat de l'expansion de FILE avec cette forme:

&> FILE

C'est l'équivalent de > FILE 2>&1, la forme employée dans les exemples précédents. C'est aussi combiné souvent avec la redirection vers /dev/null, par exemple quand vous voulez juste qu'une commande s'exécute, quelque soit le résultat ou le statut qu'elle donne.

2.4. Fichier d'entrée et fichier de sortie

2.4.1. Avec /dev/fd

Le répertoire /dev/fd contient des entrées nommées 0, 1, 2, etc. Ouvrir le fichier /dev/fd/N est équivalent à dupliquer le descripteur N. Si votre système possède /dev/stdin, /dev/stdout et /dev/stderr, vous verrez qu'ils sont équivalents à /dev/fd/0, /dev/fd/1 et /dev/fd/2, respectivement.

La principale utilisation des fichiers /dev/fd est faites par le Shell. Ce mécanisme permet aux programmes qui utilisent des chemins en paramètre de voir le standard d'entrée et le standard de sortie de la même façon que d'autres chemins. Si /dev/fd n'est pas disponible sur votre système, vous devrez trouver un moyen pour résoudre ce problème. Ce qui peut être fait, par exemple, avec un tiret (-) qui indique que le programme doit lire depuis un tube. Un exemple:

michel ~> filter body.txt.gz | cat header.txt - footer.txt
Ce texte est affiché au début de chaque travail d'affichage et merci à l'administrateur d'avoir mis en place une si bonne infrastructure d'affichage.

Texte à filtrer.

Ce texte est à afficher à la fin de chaque travail d'affichage.

La commande cat d'abord lit le fichier header.txt, puis son standard d'entrée lequel est le standard de sortie de la commande filter, et finalement le fichier footer.txt. Le sens spécial du tiret en tant que paramètre de ligne de commande pour se référer au standard d'entrée ou de sortie est une confusion qui a perduré dans beaucoup de programme. Il peut aussi y avoir des problèmes quand vous spécifiez le tiret en tant que premier paramètre, puisqu'il peut être interprété comme une option de la commande précédente. L'usage de /dev/fd permet l'uniformité et évite la confusion:

michel ~> filter body.txt | cat header.txt /dev/fd/0 footer.txt | lp

Dans cet exemple propre, toutes les sorties sont cumulées dans l'entonnoir lp pour les envoyer vers l'imprimante par défaut.

2.4.2. Read et exec

2.4.2.1. Assigner des descripteurs de fichiers

Une autre façon de considérer les descripteurs de fichiers est d'y penser comme un indicateur numérique assigné à un fichier. Au lieu d'employer le nom de fichier, vous pouvez employer le numéro de descripteur. L'intégrée exec sert à assigner un descripteur à un fichier. Employez

exec fdN> file

pour assigner le descripteur N au fichier file en sortie, et

exec fdN< file

pour assigne le descripteur N au fichier file en entrée. Après qu'un descripteur ait été assigné à un fichier, il peut être employé avec les opérateurs Shell de redirection, comme le montre l'exemple suivant:

michel ~> exec 4 > result.txt

michel ~> filter body.txt | cat header.txt /dev/fd/0 footer.txt >& 4

michel ~> cat result.txt
Ce texte est affiché au début de chaque travail d'affichage et merci à l'administrateur d'avoir mis en place une si bonne infrastructure d'affichage.

Texte à filtrer.

Ce texte est à afficher à la fin de chaque travail d'affichage.

Le descripteur 5

L'emploi de ce descripteur de fichier peut être cause de soucis, voir the Advanced Bash-Scripting Guide, chapitre 16. Il vous est sérieusement recommandé de ne pas l'employer.

2.4.2.2. Read dans un script

Ce qui suit est un exemple qui montre comment vous pouvez permuter l'entrée depuis un fichier sur l'entrée de la ligne de commande et vice-versa:

michel ~/testdir> cat sysnotes.sh
#!/bin/bash

# Ce script fait un index des fichiers de configuration importants, les sauvegarde tous dans
# un fichier et autorise l'ajout de commentaires à chaque fichier.

CONFIG=/var/tmp/sysconfig.out
rm "$CONFIG" 2>/dev/null

echo "Le résultat sera sauvegardé dans $CONFIG."

exec 7<&0

exec < /etc/passwd

# Lire la première ligne de /etc/passwd
read rootpasswd

echo "Sauvegarde des info de root..."
echo "Vos infos de compte root:" >> "$CONFIG"
echo $rootpasswd >> "$CONFIG"

exec 0<&7 7<&-

echo -n "Entrez des commentaires [ENTER] for no comment: "
read comment; echo $comment >> "$CONFIG"

echo "Sauvegarde des informations système..."

# d'abord préparer un fichier local qui ne contienne aucun commentaires
TEMP="/var/tmp/hosts.tmp"
cat /etc/hosts | grep -v "^#" > "$TEMP"

exec 7<&0
exec < "$TEMP"

read ip1 name1 alias1
read ip2 name2 alias2

echo "Votre configuration sur la machine locale:" >> "$CONFIG"

echo "$ip1 $name1 $alias1" >> "$CONFIG"
echo "$ip2 $name2 $alias2" >> "$CONFIG"

exec 0<&7 7<&-

echo -n "Entrez des commentaires [ENTER] for no comment: "
read comment; echo $comment >> "$CONFIG"
rm "$TEMP"

michel ~/testdir> sysnotes.sh
Le résultat sera sauvegardé dans /var/tmp/sysconfig.out.
Sauvegarde des info de root...
Entrez des commentaires [ENTER] for no comment: pense-bête pour mot de passe: vacance
Sauvegarde des informations système...
Entrez des commentaires [ENTER] for no comment: dans le DNS central

michel ~/testdir> cat /var/tmp/sysconfig.out
Vos infos de compte root:
root:x:0:0:root:/root:/bin/bash
pense-bête pour mot de passe: vacance
Votre configuration sur la machine locale:
127.0.0.1 localhost.localdomain localhost
192.168.42.1 tintagel.kingarthur.com tintagel
dans le DNS central

2.4.3. Fermer les descripteurs de fichiers

Parce que les processus enfants héritent des descripteurs ouverts, c'est une bonne pratique que de fermer les descripteurs quand on en a plus besoin. Ceci est fait avec la syntaxe suivante

exec fd<&-

syntax. Dans l'exemple ci-dessus, le descripteur 7, qui a été assigné au standard d'entrée, est fermé chaque fois que l'utilisateur a besoin d'utiliser le périphérique d'entrée standard, habituellement le clavier.

Suit un exemple simple de redirection du standard d'erreurs sur un tube:

michel ~> cat listdirs.sh
#!/bin/bash

# Ce script garde le standard de sortie, tandis qu'il redirige le standard d'erreurs 
# afin d'être traité par awk.

INPUTDIR="$1"

exec 6>&1

ls "$INPUTDIR"/* 2>&1 >&6 6>&- \
				# Closes fd 6 for awk, but not for ls.

| awk 'BEGIN { FS=":" } { print "YOU HAVE NO ACCESS TO" $2 }' 6>&-

exec 6>&-

2.4.4. Les documents intégrés (NdT: here documents, que l'on appele aussi 'document lié')

Fréquemment votre script peut avoir besoin d'appeler un autre programme ou script qui nécessite une entrée. Le document intégré fournit un moyen d'enjoindre au Shell de lire l'entrée de la source actuelle jusqu'à une ligne contenant seulement la chaîne ad hoc (pas de blancs résiduels). Toutes les lignes lues jusqu'à celle-là sont envoyées comme entrée standard de la commande.

Le résultat est que vous n'avez pas besoin de faire appel à différents fichiers; vous pouvez utiliser les caractères spéciaux du Shell, et c'est plus lisible qu'un flot d'echo:

michel ~> cat startsurf.sh
#!/bin/bash

# Ce script fournit aux usagers un moyen facile de choisir entre plusieurs navigateurs.

echo "Voici les navigateurs WEB de ce système:"
 
# Début du document 'intégré'
cat << BROWSERS
mozilla
links
lynx
konqueror
opera
netscape
BROWSERS
# Fin du document 'intégré'

echo -n "Lequel préférez-vous? "
read browser

echo "Démarrage de $browser, Merci de patienter..."
$browser &

michel ~> startsurf.sh
Voici les navigateurs WEB de ce système:
mozilla
links
lynx
konqueror
opera
netscape
Lequel préférez-vous? opera
Démarrage de opera, Merci de patienter...

Bien que nous parlions de document intégré, il doit être un bloc dedans le script. Voici un exemple qui installe un paquetage automatiquement, même si vous devriez normalement confirmer:

#!/bin/bash
 
# Ce script installe un paquetage automatiquement avec yum.
 
if [ $# -lt 1 ]; then
        echo "Utilisation: $0 package."
        exit 1
fi
 
yum install $1 << CONFIRM
y
CONFIRM

Et voici comment le script tourne. Quand la question « Is this ok [y/N] » apparaît, le script répond « y » automatiquement:

[root@picon bin]# ./install.sh tuxracer
Gathering header information file(s) from server(s)
Server: Fedora Linux 2 - i386 - core
Server: Fedora Linux 2 - i386 - freshrpms
Server: JPackage 1.5 for Fedora Core 2
Server: JPackage 1.5, generic
Server: Fedora Linux 2 - i386 - updates
Finding updated packages
Downloading needed headers
Resolving dependencies
Dependencies resolved
I will do the following:
[install: tuxracer 0.61-26.i386]
Is this ok [y/N]: EnterDownloading Packages
Running test transaction:
Test transaction complete, Success!
tuxracer 100 % done 1/1
Installed:  tuxracer 0.61-26.i386
Transaction(s) Complete

3. Résumé

Dans ce chapitre nous avons appris comment afficher des commentaires et comment solliciter une saisie de la part de l'utilisateur. Ce qui est effectué habituellement par la combinaison de echo/read. Nous avons aussi abordé comment les fichiers peuvent être employés en entrée et en sortie par le biais des descripteurs de fichiers et la redirection, et comment cela peut être combiné avec la saisie utilisateur.

Nous avons insisté sur l'importance des messages à destination des utilisateurs dans vos scripts. Comme d'habitude quand d'autres se servent de vos scripts, mieux faut donner trop d'informations que pas assez. Le document intégré est un type de construction Shell qui permet la création de liste, contenant des choix pour les utilisateurs. Cette construction peut aussi être employée pour exécuter d'autres sortes de tâches interactives en tâche de fond, sans intervention.

4. Exercices

Ces exercices sont des applications pratiques des constructions abordées dans ce chapitre. Quand vous écrivez un script, il est préférable de tester dans un répertoire qui ne contienne pas trop de données. Ecrire chaque étape, puis tester cette portion de code, plutôt que d'écrire tout d'un seul coup.

  1. Ecrire un script qui demande l'âge de l'usager. Si il est égal ou supérieur à 16, afficher un message indiquant que l'usager est autorisé à boire de l'alcool. Si l'usager est en dessous de 16 ans, afficher un message disant combien d'années l'usager devra attendre avant d'être autorisé légalement à boire de l'alcool.

    En plus, calculer combien de bière un usager de plus de 18 ans a bu statistiquement (100 litres/an) et donner cette information à l'usager.

  2. Ecrire un script qui prenne un fichier en paramètre. Servez-vous d'un document intégré qui présente à l'usager différents choix pour compresser ce fichier. Les choix possibles peuvent être gzip, bzip2, compress et zip.

  3. Ecrire un script appelé homebackup qui automatise tar afin que la personne qui lance le script ait toujours les options désirées (cvp) et sauvegarder le répertoire de destination (/var/backups) afin de faire une sauvegarde du répertoire racine de l'usager. Ajouter les fonctionnalités suivantes:

    • Tester le nombre de paramètres. Le script ne devrait pas en avoir. Si des paramètres sont présents, sortir après avoir afficher le message d'utilisation du script.

    • Déterminer si le répertoire backups a assez d'espace libre pour contenir la sauvegarde.

    • Demander à l'usager si il veut une sauvegarde complète ou incrémentale. Si l'usager n'a pas eu encore de sauvegarde complète, afficher un message disant qu'une complète sera faite. Dans le cas d'une sauvegarde incrémentale, ne la faire que si la complète n'est pas plus vieille que d'une semaine.

    • Comprimer la sauvegarde avec un outil de compression quelconque. Informer l'usager que le script va compresser, parce que ça peut prendre du temps, et l'usager pourrait s'inquiéter si aucun résultat n'apparaît à l'écran.

    • Afficher un message indiquant à l'usager la taille de la sauvegarde compressée.

    Voir info tar ou Introduction à Linux, chapitre 9: « Preparing your data » for background information.

  4. Ecrire un script appelé simple-useradd.sh qui ajoute un utilisateur au système local. Le script devrait:

    • Ne prendre qu'un seul paramètre, ou sinon sortir avec un message d'utilisation.

    • Contrôler /etc/passwd et sélectionner le premier identifiant non affecté. Afficher un message contenant l'identifiant.

    • Créer un groupe privé pour cet utilisateur, en contrôlant le fichier /etc/group. Afficher un message contenant l'identifiant du groupe.

    • Rassembler des informations sur l'utilisateur: un commentaire décrivant cet utilisateur, choix dans une liste de Shell (tester sa validité, si non sortir avec un message), la date d'expiration du compte, les autres groupes auxquels ce nouvel utilisateur peut appartenir.

    • Avec les informations obtenues, ajouter une ligne à /etc/passwd, /etc/group et /etc/shadow; créer le répertoire racine de l'utilisateur (avec les autorisations correctes!; ajouter l'utilisateur aux groupes secondaires désirés.

    • Définir le mot de passe de cet utilisateur à une chaîne connue.

  5. Réécrire le script de la Section 2.1.4, « Test de l'existence d'un fichier » afin qu'il lise son entrée depuis la saisie utilisateur plutôt que du premier paramètre.