15.4. Commandes d'analyse de texte

Commandes affectant le texte et les fichiers textes

sort

Outil de tri de fichier, souvent utilisée dans un tube pour trier. Cette commande trie un flux de texte ou un fichier, ascendant ou descendant, ou selon diverses clés ou positions de caractère. Avec l'option -m, elle combine des fichiers pré-triés. La page info recense ses multiples possibilités et options. Voir l'Exemple 10.9, « Rechercher les auteurs de tous les binaires d'un répertoire », l'Exemple 10.10, « Afficher les liens symboliques dans un répertoire » et l'Exemple A.8, « makedict : Créer un dictionnaire ».

tsort

Tri topologique, lisant chaque paire de mots séparés par un espace et triant en fonction des motifs donnés. Le but original de tsort était de trier une liste des dépendances pour une version obsolète de l'éditeur de liens ld dans une « ancienne » version d'UNIX.

Le résultat d'un tsort diffère habituellement du résultat de la commande sort, décrite ci-dessus.

uniq

Ce filtre élimine les lignes dupliquées depuis un fichier trié. On le voit souvent dans un tube combiné avec un sort.

cat liste-1 liste-2 liste-3 | sort | uniq > liste.finale
# Concatène les fichiers liste,
# les trie,
# efface les lignes doubles,
# et enfin écrit le résultat dans un fichier de sortie.

L'option très utile -c préfixe chaque ligne du fichier d'entrée avec son nombre d'occurence.

bash$ cat fichiertest
Cette ligne apparaît une seule fois.
Cette ligne apparaît deux fois.
Cette ligne apparaît deux fois.
Cette ligne apparaît trois fois.
Cette ligne apparaît trois fois.
Cette ligne apparaît trois fois.


bash$ uniq -c fichiertest
      1 Cette ligne apparaît une seule fois.
      2 Cette ligne apparaît deux fois.
      3 Cette ligne apparaît trois fois.


bash$ sort fichiertest | uniq -c | sort -nr
      3 Cette ligne apparaît trois fois.
      2 Cette ligne apparaît deux fois.
      1 Cette ligne apparaît trois fois.
              

La commande sort FICHIER_ENTREE | uniq -c | sort -nr renvoie la liste contenant le nombre d'occurence des lignes du fichier FICHIER_ENTREE (l'option -nr de sort produit un tri numérique inversé). Ce type de recherche trouve son utilité dans l'analyse de fichiers de traces et de dictionnaires, ainsi que là où la structure lexicale d'un document doit être examinée.

Exemple 15.12. Analyse de fréquence d'apparition des mots

#!/bin/bash
# wf.sh : Compte la fréquence de répétition des mots d'un fichier texte.
# Ceci est une version plus efficace du script "wf2.sh".

# Vérifie si un fichier a été fourni en ligne de commande.
ARGS=1
E_MAUVAISARGS=65
E_FICHIERINEXISTANT=66

if [ $# -ne "$ARGS" ]   #  Le nombre d'arguments passés au script
                        #+ est-il correct ?
then
  echo "Usage: `basename $0` nomfichier"
  exit $E_MAUVAISARGS
fi

if [ ! -f "$1" ]       # Est-ce que le fichier existe ?
then
  echo "Le fichier \"$1\" n'existe pas."
  exit $E_FICHIERINEXISTANT
fi



################################################################################
# main ()
sed -e 's/\.//g'  -e 's/\,//g' -e 's/ /\
/g' "$1" | tr 'A-Z' 'a-z' | sort | uniq -c | sort -nr
#                           =========================
#                           Fréquence des occurrences

#  Enlève les points et les virgules, et
#+ change les espaces entre les mots en retours chariot,
#+ puis met les lettres en minuscule et
#+ enfin préfixe avec le nombre d'apparition et
#+ effectue un tri numérique.

#  Arun Giridhar suggère la modification de ce qui est ci-dessus par :
#  . . . | sort | uniq -c | sort +1 [-f] | sort +0 -nr
#  Ceci ajoute une clé de tri secondaire, donc les instances des mêmes
#+ occurences sont triées alphabétiquement.
#  Comme il l'explique :
#  "Ceci est effectivement un tri radical, le premier étant sur la colonne
#+ la moins significatrice
#+ (mot ou chaîne, et une option pour ne pas être sensible à la casse)
#+ et le dernier étant la colonne la plus significative (fréquence)."
#
#  Ainsi que l'explique Frank Wang, voici l'équivalent de ci-dessus
#+       . . . | sort | uniq -c | sort +0 -nr
#+ et le reste fonctionne aussi :
#+       . . . | sort | uniq -c | sort -k1nr -k
################################################################################

exit 0

# Exercices:
# ---------
#  1) Ajouter une commande 'sed' pour supprimer les autres ponctuations, comme
#+  les deux-points.
#  2) Modifier le script pour filtrer aussi les espaces multiples et autres espaces blancs.

bash$ cat fichiertest
Cette ligne apparaît une fois.
Cette ligne apparaît deux fois.
Cette ligne apparaît deux fois.
Cette ligne apparaît trois fois.
Cette ligne apparaît trois fois.
Cette ligne apparaît trois fois.


bash$ ./wf.sh fichiertest
      6 Cette
      6 apparaît
      6 ligne
      3 fois
      3 trois
      2 deux
      1 une
               
expand, unexpand

Souvent utilisé dans un tube, expand transforme les tabulations en espaces.

unexpand transforme les espaces en tabulations. Elle inverse les modifications d'expand.

cut

Un outil d'extraction de champs d'un fichier. Il est similaire à la commande print $N de awk mais en plus limité. Il peut être plus simple d'utiliser cut dans un script plutôt que awk. À noter les options -d (délimitation) et -f (spécification du champ).

Utiliser cut pour obtenir une liste des systèmes de fichiers montés :

cut -d ' ' -f1,2 /etc/mtab

Utiliser cut pour avoir l'OS et la version du noyau :

uname -a | cut -d" " -f1,3,11,12

Utiliser cut pour extraire les en-têtes des messages depuis un dossier de courriers électroniques :

bash$ grep '^Subject:' messages-lus | cut -c10-80
Re: Linux suitable for mission-critical apps?
MAKE MILLIONS WORKING AT HOME!!!
Spam complaint
Re: Spam complaint

Utiliser cut pour analyser un fichier :

# Montre tous les utilisateurs compris dans /etc/passwd.

FICHIER=/etc/passwd

for utilisateur in $(cut -d: -f1 $FICHIER)
do
  echo $utilisateur
done

# Merci à Oleg Philon pour cette suggestion.

cut -d ' ' -f2,3 fichier est équivalent à awk -F'[ ]' '{ print $2, $3 }' fichier

[Note]

Note

Il est même possible de spécifier un saut de ligne comme délimiteur. L'astuce revient à embarquer un retour chariot (RETURN) dans la séquence de la commande.

bash$ cut -d'
 ' -f3,7,19 testfile
Ceci est la ligne 3 du fichier de test.
Ceci est la ligne 7 du fichier de test.
Ceci est la ligne 19 du fichier de test.
              

Merci pour cette précision, Jaka Kranjc.

Voir aussi l'Exemple 15.47, « Conversion de base ».

paste

Outil pour fusionner différents fichiers dans un seul fichier multi-colonne. Combiné avec cut, c'est utile pour créer des fichiers de traces.

join

Considérez-le comme un cousin de paste mais à usage spécifique. Ce puissant outil permet de fusionner deux fichiers d'une façon significative, qui crée essentiellement une simple version de base de données relationelle.

join travaille sur deux fichiers mais récupère seulement les lignes qui possèdent un champ commun (un nombre par exemple) et écrit le résultat vers stdout. Les fichiers joints doivent être triés de la même façon sur le champ cible pour que la correspondance fonctionne correctement.

Fichier: 1.donnees

100 Chaussures
200 Bretelles
300 Cure-dents
Fichier: 2.donnees

100 $40.00
200 $1.00
300 $2.00
bash$ join 1.donnees 2.donnees
Fichier: 1.donnees 2.donnees

 100 Chaussures $40.00
 200 Bretelles $1.00
 300 Cure-dents $2.00
              
[Note]

Note

Les champs de sélection apparaîtront seulement une fois dans le résultat.

head

Affiche le début d'un fichier sur stdout. Par défaut 10 lignes, mais c'est modifiable. Elle possède de nombreuses options.

Exemple 15.13. Quels fichiers sont des scripts ?

#!/bin/bash
# script-detector.sh : Detecte les scripts qui sont dans un répertoire.

TESTCHARS=2    # Teste les 2 premiers caractères.
SHABANG='#!'   # Les scripts commencent toujours avec un "#!"

for fichier in *  # Parcours tous les fichiers du répertoire courant.
do
  if [[ `head -c$TESTCHARS "$fichier"` = "$SHABANG" ]]
  #      head -c2                      #!
  #  L'option '-c' de "head" n'affiche que le nombre spécifié de
  #+ caractères, plutôt que de lignes (par défaut).
  then
    echo "Le fichier \"$fichier\" est un script."
  else
    echo "Le fichier \"$fichier\" n'est *pas* un script."
  fi
done
  
exit 0

#  Exercices :
#  ----------
#  1) Modifiez ce script pour prendre comme argument optionnel
#+    le répertoire à parcourir pour les scripts
#+    (plutôt que seulement le répertoire en cours).
#
#  2) Actuellement, ce script donne des "faux positifs" pour les
#+    scripts des langages Perl, awk, etc.
#     Corrigez ceci.


Exemple 15.14. Générer des nombres aléatoires de dix chiffres

#!/bin/bash
# rnd.sh : Affiche un nombre aléatoire de dix chiffres

# Script de Stephane Chazelas.

head -c4 /dev/urandom | od -N4 -tu4 | sed -ne '1s/.* //p'


# =================================================================== #

# Analyse
# -------

# head:
# -c4 prend les quatre premiers octets.

# od:
# -N4 limite la sortie à quatre octets.
# -tu4 sélectionne le format de sortie décimal non-signé.

# sed: 
# -n , combiné avec le drapeau "p" de la commande "s",
# n'affiche que les lignes correspondantes.



# L'auteur explique ci-après le fonctionnement de 'sed'.

# head -c4 /dev/urandom | od -N4 -tu4 | sed -ne '1s/.* //p'
# ----------------------------------> |

# On dit que ce que l'on va --------> |
# envoyer à "sed" ------------------> |
# est 0000000 1198195154\n ---------> |

# sed commence par lire les caractères: 0000000 1198195154\n.
# Ici, il trouve un caractère de saut de ligne,
# donc il est prêt pour commencer à s'occuper de la première ligne (0000000 1198195154).
# Il regarde ses <intervalle><action>s. La première est

#   intervalle  action
#   1           s/.* //p

# Le numéro de ligne est dans l'échelle, donc il exécute l'action :
# essaie de substituer la chaîne la plus longue finissant avec une espace dans la ligne
# ("0000000 ") avec rien (//), et s'il réussit, affiche le résultat
# ("p" est une option à la commande "s", c'est différent de la commande "p").

# sed est maintenant prêt à continuer la lecture de son entrée.
# (Notez qu'avant de continuer, si l'option -n n'a pas été fournie,
# sed aurait affiché de nouveau la ligne).

# Maintenant, sed lit le reste des caractères et trouve la fin du fichier.
# Il est maintenant prêt à traiter la deuxième ligne (qui est aussi numérotée
# '$' comme la dernière).
# Il voit qu'elle ne correspond pas à <intervalle>, donc son travail est terminé.

# En quelques mots, cette commande sed signifie :
# "Sur la première uniquement, supprime tout caractère jusqu'à l'espace le plus à droite,
# puis affiche-le."

# Une meilleure façon d'y parvenir aurait été :
#           sed -e 's/.* //;q'

# Ici, deux <intervalle><action>s pourraient avoir été écrit
#           sed -e 's/.* //' -e q):

#   intervalle                     action
#   rien (correspond à la ligne)   s/.* //
#   rien (correspond à la ligne)   q (quit)

# Ici, sed lit seulement la première ligne en entrée.
# Il réalise les deux actions et affiche la ligne (substituée) avant de quitter
# (à cause de l'action "q") car l'option "-n" n'est pas passée.

# =================================================================== #

# Une alternative plus simple au script d'une ligne ci-dessus serait :
#           head -c4 /dev/urandom| od -An -tu4

exit 0


Voir aussi l'Exemple 15.39, « Décoder des fichier codés avec uudecode ».

tail

Affiche la fin d'un fichier vers stdout. Par défaut 10 lignes mais cela peut être changé. Habituellement utilisé pour voir les changements faits à un fichier de traces avec -f qui affiche les lignes ajoutées à un fichier au moment où cela arrive.

Exemple 15.15. Utiliser tail pour surveiller le journal des traces système

#!/bin/bash

fichier=sys.log

cat /dev/null > $fichier; echo "Crée / efface fichier."
#  Crée ce fichier s'il n'existait pas auparavant,
#+ et le réduit à une taille nulle s'il existait.
#  : > fichier   et   > fichier marchent aussi.

tail /var/log/messages > $fichier
#  /var/log/messages doit avoir le droit de lecture pour que ce programme
#+ fonctionne.

echo "$fichier contient la fin du journal système."

exit 0

[Astuce]

Astuce

Pour lister une ligne spécifique d'un fichier texte, envoyez la sortie d'un head via un tube à tail -n 1. Par exemple, head -n 8 database.txt | tail -n 1 liste la huitième ligne du fichier database.txt.

Pour configurer une variable avec un bloc donné d'un fichier texte :

var=$(head -n $m $nomfichier | tail -n $n)

# nomfichier = nom du fichier
# m = nombre de lignes du début du fichier jusqu'à la fin du bloc
# n = nombre de lignes à récupérer (depuis la fin du bloc)
[Note]

Note

Les nouvelles implémentations de tail rendent obsolètes l'utilisation de tail -$LIGNES fichier. Le tail -n $LIGNES fichier standard est correct.

Voir aussi l'Exemple 15.5, « Fichier de traces utilisant xargs pour surveiller les journaux système », l'Exemple 15.39, « Décoder des fichier codés avec uudecode » et l'Exemple 29.6, « Nettoyage après un Control-C ».

grep

Un outil de recherche qui utilise les expressions rationnelles. À la base, c'était un filtre du vénérable ed éditeur de ligne, G.Re.P : global - regular expression - print.

grep motif [fichier...]

Recherche dans le fichier cible un motif, où motif peut être un texte littéral ou une expression rationnelle.

bash$ grep '[rst]ystem.$' osinfo.txt
The GPL governs the distribution of the Linux operating system.
              

Si aucun fichier n'est spécifié, grep travaillera en tant que filtre sur stdout, comme dans un tube.

bash$ ps ax | grep clock
765 tty1     S      0:00 xclock
901 pts/1    S      0:00 grep clock
              

-i active la recherche insensible à la casse.

-w recherche seulement les mots entiers.

-l liste seulement les fichiers dans lesquels des concordances ont été trouvées, mais pas les lignes correspondantes.

-r (récursif) cherche dans le répertoire et les sous-répertoires.

-n montre les lignes concordantes avec le numéro de ligne.

bash$ grep -n Linux osinfo.txt
2:This is a file containing information about Linux.
6:The GPL governs the distribution of the Linux operating system.
              

-v (ou --invert-match) n'affiche pas les lignes où le motif concorde.

grep motif1 *.txt | grep -v motif2

# Recherche dans "*.txt" de "motif1",
# mais ***pas*** "modif2".

-c (--count) affiche le nombre de concordances trouvées, plutôt que de les afficher.

grep -c txt *.sgml   # (nombre d'occurences de "txt" dans les fichiers "*.sgml")


#   grep -cz .
#            ^ point
# signifie compter (-c) les objets séparés par des zéros (-z) correspondant à "."
# c'est à dire, ceux qui ne sont pas vides (contenant au moins 1 caractère).
#
printf 'a b\nc  d\n\n\n\n\n\000\n\000e\000\000\nf' | grep -cz .     # 3
printf 'a b\nc  d\n\n\n\n\n\000\n\000e\000\000\nf' | grep -cz '$'   # 5
printf 'a b\nc  d\n\n\n\n\n\000\n\000e\000\000\nf' | grep -cz '^'   # 5
#
printf 'a b\nc  d\n\n\n\n\n\000\n\000e\000\000\nf' | grep -c '$'    # 9
# Par défaut, les caractères de fin de ligne (\n) séparent les objets à rechercher.

# Notez que -z est spécifique à GNU "grep"


# Merci, S.C.

L'option --color (ou --colour) marque la chaîne correspondante en couleur (sur la console ou dans une fenêtre xterm). Comme grep affiche chaque ligne entière contenant le modèle de correspondance, cela vous permettra de voir exactement ce qui a déclenché la correspondance. Voir aussi l'option -o qui affiche seulement la partie correspondant au modèle dans la ligne.

Exemple 15.16. Afficher les lignes From des courriels stockés sous forme de fichiers

#!/bin/bash
# from.sh

#  Émule l'outil "from" de Solaris, BSD, etc.
#  Affiche l'en-tête "From" de tous les messages
#+ compris dans votre répertoire de mails.


REPMAIL=~/mail/*               #  Pas de mise entre guillemets de la variable. Pourquoi ?
OPTS_GREP="-H -A 5 --color"    #  Affiche le fichier, quelques lignes de contexte
                               #+ et affiche "From" en couleur.
CHAINECIBLE="^From"            # "From" au début de la ligne.

for file in $REPMAIL           #  Pas de mise entre guillemets de la variable.
do
  grep $OPTS_GREP "$CHAINECIBLE" "$file"
  #    ^^^^^^^^^^              #  De nouveau, pas de mise entre guillemets de la variable.
  echo
done

exit $?

#  La sortie peut être envoyé vers 'more' ou
#+ redirigé dans un fichier...

Lorsqu'il est invoqué avec plus d'un fichier cible donné, grep spécifie quel fichier contient les concordances.

bash$ grep Linux osinfo.txt misc.txt
osinfo.txt:This is a file containing information about Linux.
osinfo.txt:The GPL governs the distribution of the Linux operating system.
misc.txt:The Linux operating system is steadily gaining in popularity.
              
[Astuce]

Astuce

Pour forcer grep à montrer le nom du fichier pendant la recherche d'un fichier cible, donnez /dev/null comme deuxième fichier.

bash$ grep Linux osinfo.txt /dev/null
osinfo.txt:This is a file containing information about Linux.
osinfo.txt:The GPL governs the distribution of the Linux operating system.
              

S'il y a une concordance de motif, grep renvoie un code de sortie 0, ce qui le rend utile comme test conditionnel dans un script, en particulier en combinaison avec l'option -q pour supprimer la sortie.

SUCCES=0                      # si la recherche avec grep est fructueuse
mot=Linux
nomfichier=donnees.fichier

grep -q "$mot" "$nomfichier"    # -q supprime l'affichage vers stdout

if [ $? -eq $SUCCES ]
# if grep -q "$mot" "$nomfichier"   peut remplacer les lignes 5 à 7.
then
  echo "$mot trouvé dans $nomfichier"
else
  echo "$mot introuvable dans $nomfichier"
fi

L'Exemple 29.6, « Nettoyage après un Control-C » montre comment utiliser grep pour chercher un mot dans un journal de traces.

Exemple 15.17. Émuler grep dans un script

#!/bin/bash
# grp.sh : Une réimplémentation brute de 'grep'.

E_MAUVAISARGS=65

if [ -z "$1" ]    # Vérification standard des arguments en ligne de commande.
then
  echo "Usage: `basename $0` motif"
  exit $E_MAUVAISARGS
fi  

echo

for fichier in *          # Parcourt tous les fichiers dans $PWD.
do
  sortie=$(sed -n /"$1"/p $fichier)  # Substitution de commande.

  if [ ! -z "$sortie" ]   # Que se passe t-il si "$sortie" n'est pas
                          # entre guillemets ?
  then
    echo -n "$fichier: "
    echo $sortie
  fi     #  sed -ne "/$1/s|^|${fichier}: |p"  est l'équivalent de dessus.

  echo
done  

echo

exit 0

# Exercices :
# ----------
# 1) Ajoutez des sauts de lignes à la sortie,
#    s'il y a plus d'une correspondance dans n'importe quel fichier donné.
# 2) Ajoutez des nouvelles possibilités.

Comment grep peut-il chercher deux modèles (ou plus) ? Que faire si vous voulez que grep affiche toutes les lignes d'un ou plusieurs fichiers contenant à la fois « modele1 » et « modele2 » ?

Une méthode est d'envoyer le résultat du grep modele1 via un tube dans grep modèle2.

Par exemple, étant donné le fichier suivant :

# Nom du fichier : fichiertest

Ceci est un fichier d'exemple.
Ceci est un fichier texte ordinaire.
Ce fichier ne contient aucun texte inhabituel.
Ce fichier n'est pas inhabituel.
Voici un peu de texte.

Maintenant, cherchons dans ce fichier des lignes contenant à la fois « fichier » et « texte »...

bash$ grep fichier fichiertest
# Nom du fichier : fichiertest
Ceci est un fichier d'exemple.
Ceci est un fichier texte ordinaire.
Ce fichier ne contient aucun texte inhabituel.
Ce fichier n'est pas inhabituel.

bash$ grep fichier fichiertest | grep texte
Ceci est un fichier texte ordinaire.
Ce fichier ne contient aucun texte inhabituel.

Maintenant, pour une utilisation récréative et intéressante de grep...

Exemple 15.18. Solutionneur de mots croisés

#!/bin/bash
# cw-solver.sh

#  Crossword puzzle and anagramming word game solver.
#  You know *some* of the letters in the word you're looking for,
#+ so you need a list of all valid words
#+ with the known letters in given positions.
#  For example: w...i....n
#               1???5????10
# w in position 1, 3 unknowns, i in the 5th, 4 unknowns, n at the end.
# (See comments at end of script.)


E_NOPATT=71
DICT=/usr/share/dict/word.lst
#                    ^^^^^^^^   Looks for word list here.
#  ASCII word list, one word per line.
#  If you happen to need an appropriate list,
#+ download the author's "yawl" word list package.
#  http://ibiblio.org/pub/Linux/libs/yawl-0.3.2.tar.gz
#  or
#  http://personal.riverusers.com/~thegrendel/yawl-0.3.2.tar.gz


if [ -z "$1" ]   #  If no word pattern specified
then             #+ as a command-line argument . . .
  echo           #+ . . . then . . .
  echo "Usage:"  #+ Usage message.
  echo
  echo ""$0" \"pattern,\""
  echo "where \"pattern\" is in the form"
  echo "xxx..x.x..."
  echo
  echo "The x's represent known letters,"
  echo "and the periods are unknown letters (blanks)."
  echo "Letters and periods can be in any position."
  echo "For example, try:   cw-solver.sh w...i....n"
  echo
  exit $E_NOPATT
fi

echo
# ===============================================
# This is where all the work gets done.
grep ^"$1"$ "$DICT"   # Yes, only one line!
#    |    |
# ^ is start-of-word regex anchor.
# $ is end-of-word regex anchor.

#  From _Stupid Grep Tricks_, vol. 1,
#+ a book the ABS Guide author may yet get around
#+ to writing one of these days . . .
# ===============================================
echo


exit $?  # Script terminates here.
#  If there are too many words generated,
#+ redirect the output to a file.

cw-solver w...i....n

twichildren
wellington
workingman
workingmen

egrep -- grep étendu -- est comme grep -E. Elle utilise un jeu d'expressions rationnelles légèrement différent et étendu, ce qui peut rendre une recherche plus flexible. Il accepte aussi l'opérateur booléen | (or).

bash $ egrep 'correspond|Correspond' fichier.txt
La ligne 1 correspond.
La ligne 3 correspond.
La ligne 4 contient des correspondances mais aussi des Correspondances.
              

fgrep -- grep rapide -- comme grep -F; recherche une chaîne littérale (pas d'expressions rationnelles), ce qui accélère en principe le traitement.

[Note]

Note

Sur certaines distributions Linux, egrep et fgrep sont des liens symboliques vers, ou des alias de grep, mais appelés avec les options -E et -F, respectivement.

Exemple 15.19. Rechercher des définitions dans le dictionnaire Webster de 1913

#!/bin/bash
# dict-lookup.sh

#  Ce script recherche des définitions dans le dictionnaire Webster de 1913.
#  Ce dictionnaire du domaine public est disponible au téléchargement à partir de
#+ plusieurs sites, dont celui du projet Gutenberg (http://www.gutenberg.org/etext/247).
#
#  Convertisez-le du format DOS au format UNIX (seulement LF à la fin d'une ligne)
#+ avant de l'utiliser avec ce script.
#  Stockez le fichier en ASCII pur, non compressé.
#  Configurez la variable DICO_PARDEFAUT ci-dessous avec chemin/nom du fichier.


E_MAUVAISARGS=65
LIGNESCONTEXTEMAX=50                    # Nombre maximum de lignes à afficher.
DICO_PARDEFAUT="/usr/share/dict/webster1913-dict.txt"
               # Fichier dictionnaire par défaut (chemin et nom du fichier).
               # À modifier si nécessaire.
#  Note :
#  -----
#  Cette édition particulière de 1913 de Webster
#+ commence chaque entrée avec une lettre en majuscule
#+ (minuscule pour le reste des caractères).
#  Seule la *toute première ligne* d'une entrée commence de cette façon,
#+ et c'est pourquoi l'algorithme de recherche ci-dessous fonctionne.



if [[ -z $(echo "$1" | sed -n '/^[A-Z]/p') ]]
#  Doit au moins spécifier un mot à rechercher
#+ et celui-ci doit commencer avec une lettre majuscule.
then
  echo "Usage: `basename $0` Mot-à-définir [dictionnaire]"
  echo
  echo "Note : Le mot à rechercher doit commencer avec une majuscule,"
  echo "le reste du mot étant en minuscule."
  echo "---------------------------------------------"
  echo "Exemples : Abandon, Dictionary, Marking, etc."
  exit $E_MAUVAISARGS
fi


if [ -z "$2" ]                  # Pourrait spécifier un dictionnaire différent
                                # comme argument de ce script.
then
  dico=$DICO_PARDEFAUT
else
  dico="$2"
fi

# ----------------------------------------------------------------
Definition=$(fgrep -A $LIGNESCONTEXTEMAX "$1 \\" "$dico")
#                  Définitions de la forme "Mot \..."
#
#  Et, oui, "fgrep" est assez rapide pour rechercher même dans un très gros fichier.


# Maintenant, récupérons le bloc de définition.

echo "$Definition" |
sed -n '1,/^[A-Z]/p' |
#  Affiche la première ligne en sortie
#+ jusqu'à la première ligne de la prochaine entrée.
sed '$d' | sed '$d'
#  Supprime les deux dernières lignes en sortie
#+ (une ligne blanche et la première ligne de la prochaine entrée).
# -----------------------------------------------------------------

exit 0

# Exercices :
# ----------
# 1)  Modifiez le script pour accepter tout type de saisie alphabetique
#   + (majuscule, minuscule, mixe), et convertissez-la dans un format acceptable
#   + pour le traitement.
#
# 2)  Convertissez le script en application GUI,
#   + en utilisant quelque chose comme 'gdialog' ou 'zenity' . . .
#     Le script ne prendre plus d'argument(s) en ligne de commande.
# 3)  Modifiez le script pour analyser un des autres dictionnaires disponibles
#   + dans le domaine public, tel que le « U.S. Census Bureau Gazetter »

[Note]

Note

Voir aussi Exemple A.41, « Quacky : un jeu de mots de type Perquackey » pour un exemple de recherche fgrep rapide sur un gros fichier texte.

agrep (grep approximatif) étend les possibilités de grep à une concordance approximative. La chaîne trouvée peut différer d'un nombre spécifié de caractères du motif. Cette commande ne fait pas partie des distributions Linux.

[Astuce]

Astuce

Pour chercher dans des fichiers compressés, utilisez zgrep, zegrep ou zfgrep. Ces commandes marchent aussi avec des fichiers non compressés, bien que plus lentement qu'un simple grep, egrep, fgrep. C'est pratique pour chercher dans divers fichiers, compressés ou non.

Pour chercher dans des fichiers compressés avec bzip, utilisez bzgrep.

look

La commande look fonctionne comme grep mais fait une recherche basée sur un « dictionnaire », une liste de mots triés. Par défaut, look cherche une correspondance dans /usr/dict/words mais un autre dictionnaire peut être utilisé.

Exemple 15.20. Chercher les mots dans une liste pour tester leur validité

#!/bin/bash
#  lookup : Effectue une recherche basée sur un dictionnaire sur chaque mot d'un
#+ fichier de données.

fichier=mots.donnees  #  Le fichier de données à partir duquel on lit les mots à
                      #+ tester.

echo

while [ "$mot" != end ]  # Le dernier mot du fichier de données.
do              # ^^^
  read mot      #  Depuis le fichier de données, à cause de la redirection à la
                #+ fin de la boucle.
  look $mot > /dev/null  #  Nous ne voulons pas afficher les lignes dans le
                         #+ dictionnaire.
  lookup=$?      # Code de sortie de 'look'.

  if [ "$lookup" -eq 0 ]
  then
    echo "\"$mot\" est valide."
  else
    echo "\"$mot\" est invalide."
  fi  

done <"$fichier"    #  Redirige stdin vers $fichier, donc les "lectures"
                    #+ commencent à partir d'ici.

echo

exit 0

# -----------------------------------------------------------------------------
# Le code ci-dessous ne s'exécutera pas à cause de la commande exit ci-dessus


# Stephane Chazelas propose aussi ce qui suit, une alternative plus concise :

while read mot && [[ $mot != end ]]
do if look "$mot" > /dev/null
   then echo "\"$mot\" est valide."
   else echo "\"$mot\" est invalide."
   fi
done <"$fichier"

exit 0

sed, awk

Langages de script convenant bien à l'analyse de fichiers texte et des sorties de commandes. Peuvent être utilisés seuls ou conjointement avec des tubes et des scripts shell.

sed

« Éditeur de flux » non interactif, permettant d'utiliser plusieurs commandes ex dans un mode batch. C'est souvent utile dans des scripts shell.

awk

Extracteur et formateur programmable de fichiers, bon pour la manipulation ou l'extraction de champs (colonnes) dans des fichiers textes structurés. Sa syntaxe est similaire à C.

wc

wc (word count) donne le nombre de mots d'un fichier ou d'un flux :

bash $ wc /usr/share/sed-4.1.2/README
13     70     447 /usr/share/sed-4.1.2/README
[13 lignes  70 mots  447 caractères]

wc -w donne seulement le nombre de mots.

wc -l donne seulement le nombre de lignes.

wc -c donne le nombre d'octets.

wc -m donne le nombre de caractères.

wc -L donne la taille de la ligne la plus longue.

Utiliser wc pour connaître le nombre de fichiers .txt dans le répertoire courant :

$ ls *.txt | wc -l
#  Cela ne fonctionnera que si aucun fichier "*.txt" ne contient de saut de ligne dans
#+ son nom.

# D'autres moyens de faire ça :
#      find . -maxdepth 1 -name \*.txt -print0 | grep -cz .
#      (shopt -s nullglob; set -- *.txt; echo $#)

# Merci, S.C.

Utiliser wc pour sommer la taille de tous les fichiers dont le nom commence avec une lettre entre d et h

bash$ wc [d-h]* | grep total | awk '{print $3}'
71832
              

Utiliser wc pour compter le nombre de fois où « Linux » apparaît dans le source de ce document.

bash$ grep Linux abs-book.sgml | wc -l
50
              

Voir aussi l'Exemple 15.39, « Décoder des fichier codés avec uudecode » et l'Exemple 19.8, « Boucle for redirigée ».

Certaines commandes incluent quelques fonctionnalités de wc comme options.

... | grep foo | wc -l
# Cette construction fréquemment utilisée peut être plus concise.

... | grep -c foo
# Utiliser l'option "-c" (or "--count") de grep à la place.

# Merci, S.C.
tr

Filtre de transposition de caractères.

[Attention]

Attention

Utilisez les guillemets et/ou les parenthèses, si besoin est. Les guillemets empêchent le shell de réinterpréter les caractères spéciaux dans les séquences de commande de tr. Les parenthèses devraient être mises entre guillemets pour empêcher leur expansion par le shell.

tr "A-Z" "*" < fichier ou tr A-Z \* < fichier remplacent toutes les majuscules de fichier par des astérisques (le résultat est écrit dans stdout). Sur certains systèmes, ça peut ne pas fonctionner. Cependant tr A-Z '[**]' fonctionnera.

-d efface un intervalle de caractères.

echo "abcdef"                 # abcdef
echo "abcdef" | tr -d b-d     # aef


tr -d 0-9 < fichierbidon
# Efface tous les chiffres du fichier "fichierbidon".

--squeeze-repeats (ou -s) efface toute occurence sauf la première, d'une chaîne de caractères. Cette option est utile pour supprimer les espaces blancs superflus.

bash$ echo "XXXXX" | tr --squeeze-repeats 'X'
X

L'option « complément » -c inverse l'ensemble de caractères à détecter. Avec cette option, tr n'agit que sur les caractères ne faisant pas partis de l'ensemble spécifiés.

bash$ echo "acfdeb123" | tr -c b-d +
+c+d+b++++

Notez que tr reconnaît les ensembles de caractères POSIX. [57]

bash$ echo "abcd2ef1" | tr '[:alpha:]' -
----2--1
              

Exemple 15.21. toupper : Transforme un fichier en majuscule.

#!/bin/bash
# Met en majuscule un fichier

E_MAUVAISARGS=65

if [ -z "$1" ]  # Vérification standard des arguments en ligne de commande.
then
  echo "Usage: `basename $0` nomfichier"
  exit $E_MAUVAISARGS
fi  

tr a-z A-Z <"$1"

# Même effet que le programme ci-dessus, mais utilisant la notation POSIX :
#        tr '[:lower:]' '[:upper:]' <"$1"
# Merci, S.C.

exit 0

#  Exercice :
#  Réécrire ce script en donnant le choix de modifier un fichier
#+ soit en majuscule soit en minuscule

Exemple 15.22. lowercase : Change tous les noms de fichier du répertoire courant en minuscule.

#! /bin/bash
#
# Change chaque nom de fichier en minuscules dans le répertoire courant.
#
#  Inspiré par un script de John Dubois,
#+ qui fut traduit en Bash par Chet Ramey,
#+ et considérablement simplifié par l'auteur du guide ABS.


for nomfichier in *             # Parcourt tous les fichiers du répertoire.
do
   nomF=`basename $nomfichier`
   n=`echo $nomF | tr A-Z a-z`  # Change le nom en minuscule.
   if [ "$nomF" != "$n" ]       #  Renomme seulement les fichiers qui ne sont
                                #+ pas en minuscules.
   then
     mv $nomF $n
   fi  
done   

exit $?


# Le code en dessous ne s'exécutera pas à cause de la commande exit ci-dessus
#-----------------------------------------------------------------------------#
# Pour le lancer, effacez la ligne de script ci dessus.

#  Le script suivant ne fonctionnera pas sur les fichiers dont le nom a des
#+ espaces ou des caractères de saut de ligne.

# Stephane Chazelas suggère donc l'alternative suivante :


for nomfichier in *  # Pas nécessaire d'utiliser basename,
                     # car "*" ne retourne aucun fichier contenant "/".
do n=`echo "$nomfichier/" | tr '[:upper:]' '[:lower:]'`
#                             Jeu de notation POSIX.
#                    Le slash est ajouté, comme ça les saut de lignes ne sont
#                    pas supprimés par la substition de commande.
   # Substitution de variable :
   n=${n%/}          #  Supprime le slash de fin, rajouté au dessus, du nom de
                     #+ fichier.
   [[ $nomfichier == $n ]] || mv "$nomfichier" "$n"
                     # Vérifie si le nom de fichier est déjà en minuscules.
done

exit $?

Exemple 15.23. du : Convertit les fichiers texte DOS vers UNIX.

#!/bin/bash
# Du.sh: Convertisseur de fichier texte DOS vers UNIX.

E_MAUVAISARGS=65

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

NOUVEAUFICHIER=$1.unx

CR='\015'  # Retour chariot.
           # 015 est le code ASCII en octal pour CR.
           # Les lignes d'un fichier texte DOS finissent avec un CR-LF.
           #  Les lignes d'un fichier texte UNIX finissent uniquement avec
           #+ un LF.

tr -d $CR < $1 > $NOUVEAUFICHIER
# Efface les caractères CR et écrit le résultat dans un nouveau fichier.

echo "Le fichier texte DOS original est \"$1\"."
echo "Le fichier texte UNIX converti est \"$NOUVEAUFICHIER\"."

exit 0

# Exercice :
# ---------
# Modifiez le script ci-dessus pour convertir de UNIX vers DOS.

Exemple 15.24. rot13 : rot13, cryptage ultra-faible.

#!/bin/bash
# rot13.sh: L'algorithme classique rot13,
#           cryptage qui pourrait berner un gamin de 3 ans.

# Usage: ./rot13.sh nomfichier
# ou     ./rot13.sh <nomfichier
# ou     ./rot13.sh et fournissez une entrée clavier (stdin)

cat "$@" | tr 'a-zA-Z' 'n-za-mN-ZA-M'   # "a" devient "n", "b" devient "o", etc.
#  La commande 'cat "$@"'
#+ permet la saisie de données depuis soit stdin soit un fichier.

exit 0

Exemple 15.25. Générer des énigmes « Crypto-Citations »

#!/bin/bash
# crypto-quote.sh : Crypte les citations

# Cryptage de célèbres citations par une simple substitution de lettres.
#  Le résultat est similaire aux puzzles "Crypto Quote"
#+ vus sur les pages "Op Ed" du journal Sunday.


key=ETAOINSHRDLUBCFGJMQPVWZYXK
# "key" n'est qu'un alphabet mélangé.
# Changer "key" modifie le cryptage.

# L'instruction 'cat "$@"' prend son entrée soit de stdin, soit de fichiers.
# Si stdin est utilisé, il faut terminer la saisie par un Ctrl-D.
# Sinon, spécifier le nom du fichier en tant que paramètre de la ligne de commande.

cat "$@" | tr "a-z" "A-Z" | tr "A-Z" "$key"
#        |  en majuscule  |     crypte
#  Fonctionne avec n'importe quel type de casse : majuscule, minuscule ou les
#+ deux ensemble.
# Les caractères non-alphabétiques restent inchangés.


# Essayer ce script avec :
# "Nothing so needs reforming as other people's habits."
# --Mark Twain
#
# Il en résulte :
# "CFPHRCS QF CIIOQ MINFMBRCS EQ FPHIM GIFGUI'Q HETRPQ."
# --BEML PZERC

# Pour décrypter, utiliser :
# cat "$@" | tr "$key" "A-Z"


#  Ce programme de cryptage bidon peut être cassé par un gosse de 12 ans
#+ en utilisant simplement un papier et un crayon.

exit 0

#  Exercice :
#  ---------
#  Modifier le script de façon à ce qu'il utilise encrypt ou decrypt,
#+ suivant le(s) argument(s) en ligne de commande.

fold

Un filtre qui scinde les lignes entrées à partir d'une taille spécifiée. C'est spécialement utile avec l'option -s, qui coupe les lignes à chaque espace (voir l'Exemple 15.26, « Affichage d'un fichier formaté. » et l'Exemple A.1, « mailformat : Formater un courrier électronique »).

fmt

Un formateur de fichier tout bête, utilisé en tant que filtre dans un tube pour « scinder » les longues lignes d'un texte.

Exemple 15.26. Affichage d'un fichier formaté.

#!/bin/bash

LARGEUR=40                    # Colonnes de 40 caractères.

b=`ls /usr/local/bin`       # Récupère la liste des fichiers du répertoire

echo $b | fmt -w $LARGEUR

# Aurait pu aussi être fait avec
#  echo $b | fold - -s -w $LARGEUR
 
exit 0

Voir aussi l'Exemple 15.5, « Fichier de traces utilisant xargs pour surveiller les journaux système ».

[Astuce]

Astuce

Une puissante alternative à fmt est par de Kamil Toman disponible sur http://www.cs.berkeley.edu/~amc/Par/.

col

Cette commande dont le nom est trompeur supprime les sauts de ligne inversés d'un flux en entrée. Elle tente aussi de remplacer les espaces blancs par des tabulations équivalentes. Le rôle principal de col est de filtrer la sortie de certains utilitaires de manipulation de textes, tels que groff et tbl.

column

Formateur de colonnes. Ce filtre transforme le texte écrit façon "liste" en un « joli » tableau par l'insertion de tabulations aux endroits appropriés.

Exemple 15.27. Utiliser column pour formater l'affichage des répertoires

#!/bin/bash
#  Il s'agit d'une légère modification du fichier d'exemple dans la page de
#+ manuel de "column".


(printf "PERMISSIONS LIENS PROPRIETAIRE GROUPE TAILLE MOIS JOUR HH:MM NOM-PROG\n" \
; ls -l | sed 1d) | column -t

#  "sed 1d" efface la première ligne écrite,
#+ qui devrait être "total        N",
#+ où "N" est le nombre total de fichiers trouvés par "ls -l".

# L'option -t de "column" affiche un tableau bien formaté.

exit 0

colrm

Filtre de suppression de colonnes. Ce filtre enlève les colonnes (caractères) d'un fichier et envoie le résultat vers stdout. colrm 2 4 < fichier efface le deuxième par bloc de 4 caractères de chaque ligne du fichier fichier.

[Attention]

Attention

Si le fichier contient des tabulations ou des caractères non imprimables, cela peut causer des comportements imprévisibles. Dans de tel cas, pensez à utiliser expand et unexpand dans un tube précédant colrm.

nl

Filtre de numérotation de lignes. nl fichier envoie fichier sur stdout en insérant un nombre au début de chaque ligne non vide. Si fichier est omit, alors ce filtre travaillera sur stdin.

La sortie de nl est très similaire à cat -b car, par défaut, nl ne liste pas les lignes vides.

Exemple 15.28. nl : un script d'autonumérotation.

#!/bin/bash
# line-number.sh

# Ce script s'affiche deux fois sur stdout en numérotant les lignes.

# 'nl' voit ceci comme la ligne 4 car il ne compte pas les lignes blanches.
# 'cat -n' voit la ligne ci-dessus comme étant la ligne 6.

nl `basename $0`

echo; echo  # Maintenant, essayons avec 'cat -n'

cat -n `basename $0`
# La différence est que 'cat -n' numérote les lignes blanches.
# Notez que 'nl -ba' fera de même.

exit 0
# -----------------------------------------------------------------

pr

Filtre d'impression formaté. Ce filtre paginera des fichiers (ou stdout) en sections utilisables pour des impressions papier ou pour les voir à l'écran. Diverses options permettent la manipulation des rangées et des colonnes, le regroupement des lignes, la définition des marges, la numérotation des lignes, l'ajout d'en-têtes par page et la fusion de fichiers entre autres choses. La commande pr combine beaucoup des fonctionnalités de nl, paste, fold, column et expand.

pr -o 5 --width=65 fileZZZ | more renvoie un joli affichage paginé à l'écran de fileZZZ avec des marges définies à 5 et 65.

Une option particulèrement utile est -d, forçant le double-espacement (même effet que sed -G).

gettext

Le package GNU gettext est un ensemble d'utilitaires pour adapter et traduire la sortie de texte des programmes en des langages étrangers. Bien que à l'origine la cible était les programmes C, il supporte maintenant un certain nombre de langages de programmation et de scripts.

Le programme gettext fonctionne avec les scripts shell. Voir la page info.

msgfmt

Un programme pour générer des catalogues binaires de messages. Il est utilisé pour la normalisation.

iconv

Un utilitaire pour convertir des fichiers en un codage différent (jeu de caractère). Son rôle principal concerne la normalisation.

# Convertit une chaîne d'UTF-8 vers UTF-16 et l'ajoute dans LISTELIVRES
function ecrit_chaine_utf8 {
    CHAINE=$1
    LISTELIVRES=$2
    echo -n "$CHAINE" | iconv -f UTF8 -t UTF16 | \
    cut -b 3- | tr -d \\n >> "$LISTELIVRES"
}

#  Vient du script "booklistgen.sh" de Peter Knowles
#+ permettant de convertir les fichiers au format Librie/PRS-50X de Sony.
#  (http://booklistgensh.peterknowles.com)
recode

Considérez-le comme une version puissante d'iconv, ci-dessus. Ce très souple utilitaire de conversion d'un fichier dans un jeu de caractère différent. Notez que recode ne fait pas partie d'une installation Linux standard.

TeX, gs

TeX et Postscript sont des langages de balises utilisés pour préparer une impression ou un formatage pour l'affichage vidéo.

TeX est le système "typesetting" élaboré de Donald Knuth. C'est souvent pratique d'écrire un script qui va encapsuler toutes les options et arguments passés à l'un de ces langages.

Ghostscript (gs) est un interpréteur GPL de Postscript .

texexec

Outil pour traiter des fichiers TeX et PDF. Trouvé dans /usr/bin dans plusieurs distributions Linux, c'est réellement un emballage shell qui appelle Perl pour invoquer Tex.

texexec --pdfarrange --result=Concatené.pdf *pdf

#  Concatène tous les fichiers PDF du répertoire actuel
#+ dans un seul fichier, Concatené.pdf . . .
#  (L'option --pdfarrange repagine un fichier PDF. Voir aussi --pdfcombine.)
#  La commande ci-dessus pourrait être avec « paramétrisée »
#+ et placée dans un script shell.
enscript

Outil pour convertir un fichier texte en PostScript

Par exemple, enscript fichier.txt -p fichier.ps crée un fichier PostScript filename.ps.

groff, tbl, eqn

Un autre langage de balises est groff. C'est la version avancée GNU de la commande UNIX roff/troff. Les pages de manuel utilisent groff.

tbl, utilitaire de création de tableau est considéré comme faisant partie de groff, dans la mesure où sa fonction est de convertir une balise tableau en commandes groff.

Le processeur d'équations eqn fait aussi parti de groff et sa fonction est de convertir une balise d'équation en commandes groff.

Exemple 15.29. manview : Visualisation de pages man formatées

#!/bin/bash
# manview.sh : Formate la source d'une page man pour une visualisation.

#  Ceci est utile lors de l'écriture de la source d'une page man et que vous
#+ voulez voir les résultats intermédiaires lors de votre travail.

E_MAUVAISARGS=65

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

groff -Tascii -man $1 | less
# De la page man de groff.

# Si la page man inclut des tables et/ou des équations,
# alors le code ci-dessus échouera.
# La ligne suivante peut gérer de tels cas.
#
#   gtbl < "$1" | geqn -Tlatin1 | groff -Tlatin1 -mtty-char -man
#
#   Merci, S.C.

exit 0

lex, yacc

lex, analyseur lexical, produit des programmes pour la détection de motifs. Ca a été remplacé depuis par flex, non propriétaire, sur les systèmes Linux.

L'utilitaire yacc crée un analyseur basé sur un ensemble de spécifications. Elle est depuis remplacée par le bison, non propriétaire, sur les systèmes Linux.



[57] Ce n'est vrai que pour la version GNU de tr, pas pour les versions génériques se trouvant dans les systèmes UNIX commerciaux.