Chapitre 17. Documents en ligne

Un document en ligne utilise une forme spéciale de redirection d'E/S pour fournir une liste de commande à un programme ou une commande interactifs, tels que ftp, telnet ou ex. Une << chaîne de caractères délimite >> encadre la liste de commandes. Le symbôle spécial << désigne la chaîne de caractères de limite. Ceci a pour effet de rediriger la sortie d'un fichier vers le programme, de façon similaire à programme-interactif < fichier-commandes, où fichier-commandes contient
commande n°1
commande n°2
...

L'alternative au << document en ligne >> ressemble à ceci:
#!/bin/bash
programme-interactif <<ChaineLimite
commande #1
commande #2
...
ChaineLimite

Choisissez une chaîne de caractères de limite suffisamment inhabituelle pour qu'elle ne soit pas présente où que ce soit dans la liste de commandes et qu'aucune confusion ne puisse arriver.

Notez que les documents en ligne peuvent parfois être utilisés correctement avec des utilitaires et des commandes non interactifs.

Exemple 17-1. fichierstupide: Crée un fichier stupide de deux lignes

#!/bin/bash

# Utilisation non interactive de 'vi' pour éditer un fichier.
# Emule 'sed'.

E_MAUVAISARGS=65

if [ -z "$1" ]
then
  echo "Usage: `basename $0` nomfichier"
  exit $E_MAUVAISARGS
fi

FICHIERCIBLE=$1

# Insère deux lignes dans le fichier et le sauvegarde.
#--------Début document en ligne-----------#
vi $FICHIERCIBLE <<x23LimitStringx23
i
Ceci est la ligne 1 du fichier exemple.
Ceci est la ligne 2 du fichier exemple.
^[
ZZ
x23LimitStringx23
#--------Fin   document en ligne-----------#

#  Notez que ^[ ci-dessus est un échappement littéral, tapé par
#+ Control-V <Esc>.

#  Bram Moolenaar indique que ceci pourrait ne pas fonctionner avec 'vim',
#+ dû à de possibles problèmes avec l'interaction du terminal.

exit 0

Le script ci-dessus pourrait avoir été aussi efficacement implémenté avec ex, plutôt que vi. Les documents en ligne contenant une liste de commandes ex sont assez courant pour disposer de leur propre catégorie, connue sous le nom de scripts ex.

Exemple 17-2. broadcast: Envoie un message à chaque personne connectée

#!/bin/bash

wall <<zzz23EndOfMessagezzz23
Envoyez par courrier électronique vos demandes de pizzas à votre administrateur système.
(Ajoutez un euro supplémentaire pour les anchois et les champignons.)
# Un message texte supplémentaire vient ici.
# Note: Les lignes de commentaires sont affichées par 'wall'.
zzz23EndOfMessagezzz23

# Peut se faire plus efficacement avec
#         wall <message-file
# Néanmoins, sauvegardez un message modèle dans un script fonctionne.

exit 0

Exemple 17-3. Message multi-lignes en utilisant cat

#!/bin/bash

# 'echo' est bien pour afficher des messages sur une seule ligne, mais est
#  parfois problèmatique pour des blocs de message.
#  Un document en ligne style 'cat' permet de surpasser cette limitation.

cat <<Fin-du-message
-------------------------------------
Ceci est la ligne 1 du message.
Ceci est la ligne 2 du message.
Ceci est la ligne 3 du message.
Ceci est la ligne 4 du message.
Ceci est la dernière ligne du message.
-------------------------------------
Fin-du-message

exit 0


#--------------------------------------------
# Le code ci-dessous est désactivé, à cause du "exit 0" ci-dessus.

# S.C. indique que ce qui suit fonctionne aussi.
echo "-------------------------------------
Ceci est la ligne 1 du message.
Ceci est la ligne 2 du message.
Ceci est la ligne 3 du message.
Ceci est la ligne 4 du message.
Ceci est la dernière ligne du message.
-------------------------------------"
#  Néanmoins, le texte pourrait ne pas inclure les doubles guillemets sauf si
#+ ils sont échappés.

L'option - pour marquer la chaîne de caractères de limite d'un document en ligne (<<-ChaineLimite) supprime les tabulations (mais pas les espaces) lors de la sortie. Ceci est utile pour réaliser un script plus lisible.

Exemple 17-4. Message multi-lignes, aves les tabulations supprimées

#!/bin/bash
# Identique à l'exemple précédent, mais...

#  L'option - pour un document en ligne <<-
#  supprime les tabulations dans le corps du document, mais *pas* les
#+ espaces.

cat <<-FINDUMESSAGE
	Ceci est la ligne 1 du message.
	Ceci est la ligne 2 du message.
	Ceci est la ligne 3 du message.
	Ceci est la ligne 4 du message.
	Ceci est la dernière ligne du message.
FINDUMESSAGE
# La sortie du script sera poussée.
# Chaque tabulation sur chaque ligne ne s'affichera pas.

# Les cinq lignes du "message" préfacées par une tabulation, et non des espaces,
# Les espaces ne sont pas affectés par <<-  .


exit 0

Un document en ligne supporte la substitution de paramètres et de commandes. Il est donc possible de passer différents paramètres dans le corps du document en ligne, en changeant la sortie de façon appropriée.

Exemple 17-5. Document en ligne avec une substitution de paramètre

#!/bin/bash
# Autre document en ligne 'cat', utilisant la substitution de paramètres.

# Essayez-le sans arguments,     ./scriptname
# Essayez-le avec un argument,   ./scriptname Mortimer
# Essayez-le avec deux arguments entre guillemets,
#                                ./scriptname "Mortimer Jones"

CMDLINEPARAM=1     # Attendez au moins un paramètre en ligne de commande.

if [ $# -ge $CMDLINEPARAM ]
then
  NOM=$1          # Si plus d'un paramètre en ligne de commande, prendre
                  # seulement le premier.
else
  NOM="John Doe"  # Par défaut, si il n'y a pas de paramètres.
fi  

INTERLOCUTEUR="l'auteur de ce joli script"
  

cat <<FinDuMessage

Salut, $NOM.
Bienvenue à toi, $NOM, de la part de $INTERLOCUTEUR.

# Ce commentaire s'affiche dans la sortie (pourquoi?).

FinDuMessage

# Notez que les lignes blanches s'affichent. Ainsi que le commentaire.

exit 0

Mettre entre guillemets ou échapper la << chaîne de caractères de limite >> au début du document here désactive la substitution de paramètres en son corps. Ceci a un intérêt très limité.

Exemple 17-6. Substitution de paramètres désactivée

#!/bin/bash
#  Un document en ligne 'cat', mais avec la substitution de paramètres
#+ désactivée.

NOM="John Doe"
INTERLOCUTEUR="l'auteur de ce joli script"

cat <<'FinDuMessage'

Salut, $NOM.
Bienvenue à toi, $NOM, de la part de $INTERLOCUTEUR.

FinDuMessage

#  Pas de substitution de paramètres lorsque la chaîne de fin est entre
#+ guillemets ou échappée.
#  L'une des deux commandes ci-dessous à l'entête du document en ligne aura le
#+ le même effet.
#  cat <<"FinDuMessage"
#  cat <<\FinDuMessage

exit 0

C'est un script utile contenant un document en ligne avec une substitution de paramètres.

Exemple 17-7. upload: Envoie un fichier vers le répertoire incoming chez << Sunsite >>

#!/bin/bash
# upload.sh

# Téléchargement de fichiers par pair (Fichier.lsm, Fichier.tar.gz)
# pour le répertoire entrant de Sunsite (metalab.unc.edu).

E_ERREURSARGS=65

if [ -z "$1" ]
then
  echo "Usage: `basename $0` nomfichier"
  exit $E_ERREURSARGS
fi  


NomFichier=`basename $1`           # Supprime le chemin du nom du fichier.

Serveur="metalab.unc.edu"
Repertoire="/incoming/Linux"
# Ils n'ont pas besoin d'être codés en dur dans le script,
# mais peuvent être à la place changés avec un argument en ligne de commande.

MotDePasse="votre.addresse.email"   # A changer suivant vos besoins.

ftp -n $Serveur <<Fin-De-Session
# L'option -n désactive la connexion automatique

user anonymous "$MotDePasse"
binary
bell                # Sonne après chaque transfert de fichiers.
cd $Repertoire
put "$NomFichier.lsm"
put "$NomFichier.tar.gz"
bye
Fin-De-Session

exit 0

Un document en ligne peut donner une entrée à une fonction du même script.

Exemple 17-8. Documents en ligne et fonctions

#!/bin/bash
# here-function.sh

ObtientDonneesPersonnelles ()
{
  read prenom
  read nom
  read adresse
  read ville
  read etat
  read codepostal
} # Ceci ressemble vraiment à une fonction interactive, mais...


# Apporter l'entrée à la fonction ci-dessus. input to the above function.
ObtientDonneesPersonnelles <<ENREG001
Bozo
Bozeman
2726 Nondescript Dr.
Baltimore
MD
21226
RECORD001


echo
echo "$prenom $nom"
echo "$adresse"
echo "$ville, $etat $codepostal"
echo

exit 0

Il est possible d'utiliser : comme commande inactive acceptant une sortie d'un document en ligne. Cela crée un document en ligne << anonyme >>.

Exemple 17-9. Document en ligne << Anonyme >>

#!/bin/bash

: <<TESTVARIABLES
${HOSTNAME?}${USER?}${MAIL?}  # Affiche un message d'erreur si une des variables n'est pas configurée.
TESTVARIABLES

exit 0

Astuce

Une variante de la technique ci-dessus permet de << supprimer les commentaires >> de blocs de code.

Exemple 17-10. Décommenter un bloc de code

#!/bin/bash
# commentblock.sh

: << BLOC_COMMENTAIRE
echo "Cette ligne n'est pas un echo."
C'est une ligne de commentaire sans le préfixe "#".
Ceci est une autre ligne sans le préfixe "#".

&*@!!++=
La ligne ci-dessus ne causera aucun message d'erreur,
Parce que l'interpréteur Bash l'ignorera.
BLOC_COMMENTAIRE

echo "La valeur de sortie du \"BLOC_COMMENTAIRE\" ci-dessus est $?."   # 0
# Pas d'erreur.


#  La technique ici-dessus est aussi utile pour mettre en commentaire un bloc
#+ de code fonctionnel pour des raisons de déboguage.
#  Ceci permet d'éviter de placer un "#" au début de chaque ligne, et d'avoir
#+ ensuite à les supprimer.

: << DEBUGXXX
for fichier in *
do
  cat "$fichier"
done
DEBUGXXX

exit 0

Astuce

Encore une autre variante de cette symmpathique astuce rendant les scripts << auto-documentés >> possibles.

Exemple 17-11. Un script auto-documenté

#!/bin/bash
# self-document.sh: script auto-documenté
# Modification de "colm.sh".

DEMANDE_DOC=70

if [ "$1" = "-h"  -o "$1" = "--help" ]     # Demande de l'aide.
then
  echo; echo "Usage: $0 [nom-repertoire]"; echo
  cat "$0" | sed --silent -e '/DOCUMENTATIONXX$/,/^DOCUMENTATION/p' |
  sed -e '/DOCUMENTATIONXX/d'; exit $DEMANDE_DOC; fi

: << DOCUMENTATIONXX
Liste les statistiques d'un répertoire spécifié dans un format de tabulations.
------------------------------------------------------------------------------
Le paramètre en ligne de commande donne le répertoire à lister.
Si aucun répertoire n'est spécifié ou que le répertoire spécifié ne peut être
lu, alors listez le répertoire courant.

DOCUMENTATIONXX

if [ -z "$1" -o ! -r "$1" ]
then
  repertoire=.
else
  repertoire="$1"
fi  

echo "Liste de "$repertoire":"; echo
(printf "PERMISSIONS LIENS PROP GROUPE TAILLE MOIS  JOUR HH:MM NOM-PROG\n" \
; ls -l "$repertoire" | sed 1d) | column -t

exit 0

Note

Les documents en ligne créent des fichiers temporaires, mais ces fichiers sont supprimés après avoir été ouvert et ne sont plus accessibles par aucun autre processus.

bash$ bash -c 'lsof -a -p $$ -d0' << EOF
> EOF
lsof    1213 bozo    0r   REG    3,5    0 30386 /tmp/t1213-0-sh (deleted)
	      

Attention

Quelques utilitaires ne fonctionneront pas à l'intérieur d'un document en ligne.

Pour ces tâches trop complexes pour un << document en ligne >>, considérez l'utilisation du langage de scripts expect, qui est conçu spécifiquement pour alimenter l'entrée de programmes interactifs.

>-f fait que file tourne en mode batch, pour lire à partir d'un fichier désigné une liste de noms de fichiers à analyser. L'option -z, lorsqu'elle est utilisé sur un fichier compressé, essaie d'analyser le type du fichier décompressé.

bash$ file test.tar.gz
test.tar.gz: gzip compressed data, deflated, last modified: Sun Sep 16 13:34:51 2001, os: Unix

bash file -z test.tar.gz
test.tar.gz: GNU tar archive (gzip compressed data, deflated, last modified: Sun Sep 16 13:34:51 2001, os: Unix)
	      

Exemple 12-24. supprimer les commentaires de programmes C

#!/bin/bash
# strip-comment.sh: Supprime les commentaires (/* COMMENT */) d'un progamme C.

E_SANSARGS=65
E_ERREURARG=66
E_MAUVAIS_TYPE_FICHIER=67

if [ $# -eq "$E_SANSARGS" ]
then
  echo "Usage: `basename $0` fichier-C" >&2 # Message d'erreur vers stderr.
  exit $E_ERREURARG
fi  

# Test du type de fichier.
type=`eval file $1 | awk '{ print $2, $3, $4, $5 }'`
# "file $1" affiche le type du fichier...
# puis awk supprime le premier champ, le nom du fichier...
# enfin, le résultat remplit la variable "type".
type_correct="ASCII C program text"

if [ "$type" != "$type_correct" ]
then
  echo
  echo "Ce script fonctionne uniquement sur les fichiers C."
  echo
  exit $E_MAUVAIS_TYPE_FICHIER
fi  


# Script sed assez complexe:
#--------
sed '
/^\/\*/d
/.*\/\*/d
' $1
#--------
#  Facile à comprendre si vous prenez quelques heures pour apprendre les
#+ concepts de sed.


#  Il est possible d'ajouter une ligne supplémentaire au script sed pour gérer
#+ le cas où la ligne de code a un commentaire le suivant, sur la même ligne.
#  Ceci est laissé en exercice (difficile).

# De même, le code ci-dessus supprime les lignes avec un "*/" ou "/*",
# ce qui n'est pas un effet désirable.

exit 0


# ----------------------------------------------------------------
# Le code ci-dessous ne s'exécutera pas à cause du 'exit 0' ci-dessus.

# Stephane Chazelas suggère l'alternative suivante:

usage() {
  echo "Usage: `basename $0` fichier-C" >&2
  exit 1
}

BIZARRE=`echo -n -e '\377'`   # ou BIZARRE=$'\377'
[[ $# -eq 1 ]] || usage
case `file "$1"` in
  *"C program text"*) sed -e "s%/\*%${BIZARRE}%g;s%\*/%${BIZARRE}%g" "$1" \
     | tr '\377\n' '\n\377' \
     | sed -ne 'p;n' \
     | tr -d '\n' | tr '\377' '\n';;
  *) usage;;
esac

# Ceci ne fonctionne pas avec, par exemple:
# printf("/*");
# or
# /*  /* commentaire intégré bogué */
#
# Pour gérer tous les cas spécifiques (commentaires dans des chaînes,
# commentaires dans des chaînes où se trouve un \", \\" ...) la seule façon est
# d'écrire un analyseur C
# (lex ou yacc peut-être?).

exit 0
which

which commande-xxx donne le chemin complet vers << commande-xxx >>. C'est utile pour trouver si une commande ou un utilitaire particulier est installé sur le système.

$bash which rm
/usr/bin/rm

whereis

Similair à which, ci-dessus, whereis commande-xxx donne le chemin complet vers << commande-xxx >>, mais aussi sa page man.

$bash whereis rm
rm: /bin/rm /usr/share/man/man1/rm.1.bz2

whatis

whatis fichierxxx recherche << ficheirxxx >> dans la base de données whatis. C'est utile pour identifier les commandes système et les fichiers de configuration importants. Considérez-le en tant que commande man simplifiée.

$bash whatis whatis
whatis               (1)  - search the whatis database for complete words

Exemple 12-25. Explorer /usr/X11R6/bin

#!/bin/bash

# Que sont tous ces mystérieux binaires dans /usr/X11R6/bin?

REPERTOIRE="/usr/X11R6/bin"
# Essayez aussi "/bin", "/usr/bin", "/usr/local/bin", etc.

for fichier in $REPERTOIRE/*
do
  whatis `basename $fichier`   # affiche des informations sur le binaire.
done

exit 0
# Vous pouvez souhaiter rediriger la sortie de ce script, de cette façon:
# ./what.sh >>whatis.db
# ou la visualiser une page à la fois sur stdout,
# ./what.sh | less

Voir aussi Exemple 10-3.

vdir

Affiche une liste détaillée du contenu du répertoire. L'effet est similaire à ls -l.

Il fait partie de GNU fileutils.

bash$ vdir
total 10
 -rw-r--r--    1 bozo  bozo      4034 Jul 18 22:04 data1.xrolo
 -rw-r--r--    1 bozo  bozo      4602 May 25 13:58 data1.xrolo.bak
 -rw-r--r--    1 bozo  bozo       877 Dec 17  2000 employment.xrolo

bash ls -l
total 10
 -rw-r--r--    1 bozo  bozo      4034 Jul 18 22:04 data1.xrolo
 -rw-r--r--    1 bozo  bozo      4602 May 25 13:58 data1.xrolo.bak
 -rw-r--r--    1 bozo  bozo       877 Dec 17  2000 employment.xrolo
	      

locate, slocate

La commande locate cherche les fichiers en utilsant une base de données stockée pour ce seul but. La commande slocate est la version sécurisée de locate (qui pourrait être un alias de slocate).

$bash locate hickson
/usr/lib/xephem/catalogs/hickson.edb

readlink

Déréférence le fichier sur lequel pointe un lien symbolique.

bash$ readlink /usr/bin/awk
../../bin/gawk
	      

strings

Utiliser la commande strings pour trouver les chaînes de caractères affichables dans un fichier binaire ou de données. Elle listera les séquences de caractères affichables trouvées dans le fichier cible. C'est intéressant pour un examen rapide (et sale) d'un core dump ou pour regarder un fichier image inconnu (strings fichier-image | more pourrait afficher quelque chose comme JFIF, qui identifierait le fichier en tant que graphique jpeg). Dans un script, vous devriez probablement analyser la sortie de strings avec grep ou sed. Voir Exemple 10-7 et Exemple 10-9.

Exemple 12-26. Une commande strings << améliorée >>

#!/bin/bash
# wstrings.sh: "word-strings" (commande "strings" améliorée)
#
#  Ce script filtre la sortie de "strings" en la comparant avec une liste de
#+ mots communs.
#  Ceci élimine efficacement tout le bruit et n'affiche que les mots reconnus.

# =================================================================
#               Vérification standard de(s) argument(s) du script
ARGS=1
E_MAUVAISARGS=65
E_AUCUNFICHIER=66

if [ $# -ne $ARGS ]
then
  echo "Usage: `basename $0` nomfichier"
  exit $E_MAUVAISARGS
fi

if [ -f "$1" ]                        # Vérifie si le fichier existe.
then
    nom_fichier=$1
else
    echo "Le fichier \"$1\" n'existe pas."
    exit $E_AUCUNFICHIER
fi
# =================================================================


LONGUEUR_CHAINE_MINIMUM=3                 #  Longueur minimum d'une chaîne.
FICHIER_MOTS=/usr/share/dict/linux.words  #  Dictionnaire.
                                          #  Vous pouvez spécifier un autre
					  #+ fichier de mots, à condition que
					  #+ son format soit d'un mot par ligne.


listemots=`strings "$1" | tr A-Z a-z | tr '[:space:]' Z | \
tr -cs '[:alpha:]' Z | tr -s '\173-\377' Z | tr Z ' '`

# Traduit la sortie de la commande 'strings' avec de multiples passes de 'tr'.
#  "tr A-Z a-z" réalise une conversion en minuscule.
#  "tr '[:space:]'" change les espaces blancs par des Z.
#  "tr -cs '[:alpha:]' Z" change les caractères non alphabetiques en Z.
#+ et ne conserve qu'un seul Z pour les Z successifs.
#  "tr -s '\173-\377' Z" convertit tous les caractères après 'z' en Z
#+ et ne conserve qu'un seul Z pour les Z successifs
#+ ce qui supprime tous les caractères bizarres que la précédente passe aurait
#+ oublié de gérer.
#  Finalement, "tr Z ' '" convertit tous les Z en espaces blancs,
#+ ce qui sera vu comme des mots séparés dans la boucle ci-dessous.

#  Notez que la technique de remplissage de la sortie de 'tr' vers lui-même,
#+ mais avec different arguments et/ou options à chaque passe.


for mot in $listemots    # Important:
                         # $listemots ne doit pas être entre guillemets ici.
                         # "$listemots" ne fonctionne pas.
                         # Pourquoi?
do

  longueur_chaine=${#mot}                     # Longueur de la chaîne.
  if [ "$longueur_chaine" -lt "$LONGUEUR_CHAINE_MINIMUM" ]
  then   # Ne pas tenir compte des petites chaînes.
    continue
  fi

  grep -Fw $mot "$FICHIER_MOTS"       # Correspond seulement aux mots complets.

done  


exit 0

Comparaison

diff, patch

diff: utilitaire de comparaison de fichiers flexible. Il compare les fichiers cibles ligne par ligne, séquentiellement. Dans certaines applications, telles que la comparaison de dictionnaires de mots, il peut être utile pour filtrer les fichiers avec sort et uniq avant de les envoyer via un tube à diff. diff fichier-1 fichier-2 affiche en sortie les lignes qui différent des deux fichiers, avec des symbôles indiquant à quel fichier appartient la ligne en question.

L'option --side-by-side de diff affiche en sortie chaque fichier comparé, ligne pat ligne, dans des colonnes séparées, et avec les lignes ne correspondant pas marquées. Les options -c et -u rendent la sortie de la commande plus facile à interpréter.

Il existe de nombreuses interfaces agréables pour diff, comme spiff, wdiff, xdiff et mgdiff.

Astuce

La commande diff renvoie un état de sortie de 0 si les fichiers comparés sont identiques et 1 si ils ne le sont pas. Cela permet d'utiliser diff dans une construction de test à l'intérieur d'un script shell (voir ci-dessous).

Une utilisation commune de diff est de générer des fichiers de différence à utiliser avec patch. L'option -e permet la génération de tels fichiers, à utiliser avec des scripts ed ou ex.

patch: utilitaire de gestion de versions. Suivant un fichier de différences généré par diff, patch peut mettre à jour une version précédente d'un paquetage en une nouvelle version. Il est bien plus convenable de distribuer un fichier << diff >> sensiblement plus petit que le corps entier du paquetage revu. Les correctifs (<< patches >>) du noyau sont devenus la méthode préférée pour distribuer les fréquentes mises à jour du noyau Linux.

patch -p1 <correctif
# Prend toutes les modifications indiquées dans 'correctif'
# et les applique aux fichiers référencés.
# Ceci met à jour le paquetage en une nouvelle version.

Applicquer un correctif au noyau:

cd /usr/src
gzip -cd patchXX.gz | patch -p0
# Mettre à jour le source du noyau en utilisant 'patch'.
# De la documentation du noyau Linux, "README",
# par un auteur anonyme (Alan Cox?).

Note

La commande diff peut aussi comparer récursivement les répertoires (et les fichiers qui s'y trouvent).

bash$ diff -r ~/notes1 ~/notes2
Only in /home/bozo/notes1: fichier02
 Only in /home/bozo/notes1: fichier03
 Only in /home/bozo/notes2: fichier04
	      

Astuce

Utiliser zdiff pour comparer des fichiers gzipped.

diff3

Une version étendue de diff qui compare trois fichiers en une fois. Cette commande renvoie un état de sortie de 0 si l'exécution est réussie, mais malheureusement, cela ne donne aucune information sur le résultat de la comparaison.

bash$ diff3 fichier-1 fichier-2 fichier-3
====
 1:1c
   Ceci est la ligne 1 de "fichier-1"
 2:1c
   Ceci est la ligne 1 de "fichier-2"
 3:1c
   Ceci est la ligne 1 de "fichier-3"
	      

sdiff

Compare et/ou édite les deux fichiers pour les assembler dans un fichier de sortie. Dû à sa nature interactive, cette commande trouvera peut d'utilité dans un script.

cmp

La commande cmp est une version simplifiée de diff, ci-dessus. Alors que diff reporte les différences entre deux fichiers, cmp montre simplement à quel point ils diffèrent.

Note

Comme diff, cmp renvoie un état de sortie de 0 si les fichiers comparés sont identiques et de 1 si ils diffèrent. Ceci permet une utilisation dans une construction de test à l'intérieur d'un script shell.

Exemple 12-27. Utiliser cmp pour comparer deux fichiers à l'intérieur d'un script.

#!/bin/bash

ARGS=2  # Deux arguments attendus par le script.
E_MAUVAISARGS=65
E_ILLISIBLE=66

if [ $# -ne "$ARGS" ]
then
  echo "Usage: `basename $0` fichier1 fichier2"
  exit $E_MAUVAISARGS
fi

if [[ ! -r "$1" || ! -r "$2" ]]
then
  echo "Les deux fichiers à comparer doivent exister et être lisibles."
  exit $E_ILLISIBLE
fi

cmp $1 $2 &> /dev/null  # /dev/null enterre la sortie de la commande "cmp".
#   cmp -s $1 $2  a le même résultat ("-s" option de silence pour "cmp")
#   Merci à Anders Gustavsson pour nous l'avoir indiqué.
#
# Fonctionne aussi avec 'diff', c'est-à-dire   diff $1 $2 &> /dev/null

if [ $? -eq 0 ]         # Teste du code de sortie de la commande "cmp".
then
  echo "Le fichier \"$1\" est identique au fichier \"$2\"."
else  
  echo "Le fichier \"$1\" diffère du fichier \"$2\"."
fi

exit 0

Astuce

Utiliser zcmp sur des fichiers gzip.

comm

Utilitaire de comparaison de fichiers versatile. Les fichiers doivent être triés pour qu'il soit utile.

comm -options premier-fichier second-fichier

comm fichier-1 fichier-2 affiche trois colonnes:

  • colonne 1 = lignes uniques à fichier-1

  • colonne 2 = lignes uniques à fichier-2

  • colonne 3 = lignes communes aux deux.

Les options permettent la sortie d'une ou plusieurs colonnes.

  • -1 supprime la colonne 1

  • -2 supprime la colonne 2

  • -3 supprime la colonne 3

  • -12 supprime les deux colonnes 1 et 2, etc.

Utilitaires

basename

Supprime le chemin d'un nom de fichier, en affichant seulement le nom. La construction basename $0 permet au script de connaître son nom, c'est-à-dire le nom par lequel il a été invoqué. Ceci peut être utilisé pour les messages d'<< usage >> si, par exemple, un script est appelé sans ses arguments:
echo "Usage: `basename $0` arg1 arg2 ... argn"

dirname

Supprime le basename d'un nom de fichier, en affichant que le chemin.

Note

basename et dirname peuvent s'exécuter sur des chaînes de caractères arbitraires. L'argument n'a pas besoin de référer un fichier existant, voire même un fichier (voir Exemple A-8).

Exemple 12-28. basename et dirname

#!/bin/bash

a=/home/bozo/daily-journal.txt

echo "Nom de base       de /home/bozo/daily-journal.txt = `basename $a`"
echo "Nom du répertoire de /home/bozo/daily-journal.txt = `dirname $a`"
echo
echo "Mon répertoire personnel est `basename ~/`."   # Fonctionne aussi avec ~.
echo "Le chemin de mon répertoire personnel est `dirname ~/`."  # Fonctionne aussi avec ~.

exit 0
split

Utilitaire pour diviser un fichier en plusieurs petites parties. Habituellement utilisé pour diviser un gros fichier en fichiers tenant sur une disquette ou pour préparer un courrier électronique ou pour les télécharger.

sum, cksum, md5sum

Ces utilitaires ont pour but de vérifier une somme de contrôle. Une somme de dontrôle est un nombre calculé à partir du contenu d'un fichier, dans le but de vérifier son intégrité. Un script peut se référer à une liste de sommes de contrôle pour des raisons de sécurité, comme pour s'assurer que des fichiers clés du système n'ont pas été modifié ou corrompu. Pour les applications de sécurité, utilisez la commande md5sum en 128 bits (message digest checksum).

bash$ cksum /boot/vmlinuz
1670054224 804083 /boot/vmlinuz


bash$ md5sum /boot/vmlinuz
0f43eccea8f09e0a0b2b5cf1dcf333ba  /boot/vmlinuz
	      

Notez que cksum affiche aussi la taille, en octet, du fichier cible.

Exemple 12-29. Vérifier l'intégrité d'un fichier

#!/bin/bash
# file-integrity.sh: Vérifie si les fichiers d'un répertoire donné ont été
#                    modifié.

E_REP_INEXISTANT=70
E_MAUVAIS_FICHIER_BD=71

fichierdb=File_record.md5
# Fichier pour stocker les enregistrements.


init_base_donnees ()
{
  echo ""$repertoire"" > "$fichierdb"
  # Ecrit le nom du répertoire sur la première ligne du fichier.
  md5sum "$repertoire"/* >> "$fichierdb"
  # Ajoute les sommes de contrôle md5 et les noms de fichiers.
}

verifie_base_donnees ()
{
  local n=0
  local nomfichier
  local somme_controle

  # ------------------------------------------------- #
  #  Cette vérification du fichier ne devrait être
  #+ inutile mais c'est mieux d'être sain que désolé.

  if [ ! -r "$fichierdb" ]
  then
	  echo "Incapable de lire les somme de contrôle du fichier de base de données!"
    exit $E_MAUVAIS_FICHIER_BD
  fi
  # ------------------------------------------------- #

  while read enregistrement[n]
  do

    repertoire_verifie="${enregistrement[0]}"
    if [ "$repertoire_verifie" != "$repertoire" ]
    then
      echo "Les répertoires ne correspondent pas!"
      # Essaie d'utiliser un fichier d'un autre répertoire.
      exit $E_REP_INEXISTANT
    fi

    if [ "$n" -gt 0 ]   # Pas de nom de répertoire.
    then
      nomfichier[n]=$( echo ${enregistrement[$n]} | awk '{ print $2 }' )
      #  md5sum écrit les enregistrements après,
      #+ effectue un contrôle des sommes, puis du fichier.
      somme_controle[n]=$( md5sum "${nomfichier[n]}" )

      if [ "${enregistrement[n]}" = "${somme_controle[n]}" ]
      then
        echo "${nomfichier[n]} non modifié."
      else
        echo "${nomfichier[n]} : ERREUR DE SOMME DE CONTROLE!"
	# Le fichier a été changé depuis la dernière vérification.
      fi

    fi  


    let "n+=1"
  done <"$fichierdb"       #  Lit les sommes de contrôle à partir du fichier de
                           #+ base de données.

}  

# =================================================== #
# main ()

if [ -z  "$1" ]
then
  repertoire="$PWD"     #  Si non spécifi,;
else                    #+ utilise le répertoire courant.
  repertoire="$1"
fi  

clear                   # Efface l'écran.

# ------------------------------------------------------------------ #
if [ ! -r "$fichierdb" ] # Besoin de créer un fichier de base de données.
  then
	  echo "Configuration de la base de données, \""$repertoire"/"$fichierdb"\"."; echo
    init_base_donnees
  fi  
# ------------------------------------------------------------------ #

verifie_base_donnees          # Fait le vrai travail.

echo 

#  Vous pouvez souhaiter rediriger stdout vers un fichier spécialement si le
#+ répertoire vérifié a de nombreux fichiers.

#  Pour une explication sur la vérificaton d'intégrité.
#+ considérez le paquetage #+ http://sourceforge.net/projects/tripwire/.

exit 0
shred

Efface de façon sécurisé un fichier en l'écrasant (en écrivant dessus) plusieurs fois avec des octets aléatoires avant de le supprimer. Cette commande a le même effet que Exemple 12-42, mais le fait de façon plus élégante et plus approfondie.

Il fait partie des utilitaires GNU fileutils.

Attention

Des technologies avancées peuvent toujours retrouvées le contenu d'un fichier, même après l'utilisation de shred.

Coder et crypter

uuencode

Cet utilitaire code des fichiers binaires en caractères ASCII, leur permettant d'être transmis dans le corps de message email ou d'être envoyé dans un groupe de nouvelles.

uudecode

Ceci inverse le codage, décode des fichiers passés par uuencode et récupère les binaires originaux.

Exemple 12-30. Décoder des fichier codés avec uudecode

#!/bin/bash

lignes=35        # Permet 35 lignes pour l'entête (très généreux generous).

for Fichier in *   # Teste tous les fichiers du répertoire courant...
do
  recherche1=`head -$lignes $Fichier | grep begin | wc -w`
  recherche2=`tail -$lignes $Fichier | grep end | wc -w`
  #  Les fichiers uuencodés ont un "begin" près du début et un "end" près de
  #+ la fin.
  if [ "$recherche1" -gt 0 ]
  then
    if [ "$recherche2" -gt 0 ]
    then
      echo "uudecoding - $Fichier -"
      uudecode $Fichier
    fi  
  fi
done  

#  Notez que lancer ce script sur lui-même le trompe et croie qu'il est un
#+ fichier uuencodé, parce qu'il contient les mots "begin" et "end".

# Exercice:
# Modifier ce script pour vérifier un entête de newsgroup.

exit 0

Astuce

La commande fold -s est utile (parfois dans un tube) pour décoder de long messages téléchargés à partir des groupes de nouvelles Usenet.

mimencode, mmencode

Les commandes mimencode et mmencode s'occupent du codage des pièces-jointes des courriers éléctroniques. Bien que les clients mail (MUA tels que pine ou kmail) gèrent normalement ceci automatiquement, ces utilitaires particuliers permettent de manipuler de telles pièces-jointes manuellement à partir de la ligne de commande ou dans un script shell.

crypt

A un moment, il était l'utilitaire de cryptage standard sous UNIX. [2] Des régulations gouvernementales, basées sur la politique, ont interdit l'export de logiciels de cryptage, ce qui a résulté en la disparition de la commande crypt de la majeure partie du monde UNIX, et il est toujours manquant sur la plupart des distributions Linux. Heureusement, des programmeurs ont réalisé un certain nombre d'alternatives, dont celle de l'auteur cruft (voir Exemple A-5).

Divers

mktemp

Crée un fichier temporaire avec un nom << unique >>.

PREFIX=nom_fichier
fichier_temporaire=`mktemp $PREFIX.XXXXXX`
#                          ^^^^^^ A besoin d'au moins 6 emplacements
#+                                dans le modèle de nom de fichier.
echo "nom de fichier_temporaire = $fichier_temporaire"
# nom fichier_temporaire = nom_fichier.QA2ZpY
#                 ou quelque chose de similaire...

make

Utilitaire pour construire et compiler des paquetages binaires. Il peut aussi être utilisé pour tout type d'opérations qui seraient déclenchées par une modification des fichiers source.

La commande make vérifie le Makefile, une liste de dépendances de fichiers et les opérations à réaliser.

install

Commande de copie de fichier à but spécifique, similaire à cp, mais est capable de modifier les droits et attributs des fichiers copiés. Cette commande semble fait uniquement pour l'installation de paquetages, et en tant que tel, il fait souvent son apparition dans les Makefiles (dans la section make install :). Il pourrait aussi trouver une utilité dans les scripts d'installation.

ptx

La commande ptx [fichier_cible] affiche en sortie un index permutté (liste référencée) du fichier cible. Elle peut être encore filtrée et formattée dans untube, si nécessaire.

more, less

Programmes envoyant un fichier texte ou un flux sur stdout, un écran à la fois. Ils peuvent être utilisé pour filtrer la sortie d'un script.

Notes

[1]

Un tar czvf archive_name.tar.gz * incluera les fichiers commençant par un point pour les répertoires compris dans le répertoire courant. C'est une << fonctionnalité >> non documentée de GNU tar.

[2]

C'est un système de chiffrement symétrique de bloc, employé pour crypter des fichiers sur un seul système ou sur un réseau local, par opposition à la classe de chiffrement publique, dont pgp est un exemple bien connu.