Avertissement :
La version originale de cet article a été publiée sur IBM developerWorks
et est la propriété de Westtech Information Services. Ce document est une
traduction de la mise à jour de la version originale de l'article
réalisée par l'équipe de documentation Gentoo et contient quelques
améliorations proposées par l'équipe de documentation de Gentoo Linux.
Ce document n'est pas activement maintenu.
|
Sed par l'exemple, 3e partie
1.
Passons au niveau suivant : Manipulations de données en Sed
Musclé ce Sed !
Dans le deuxième article sur Sed, j'ai présenté
des exemples montrant le fonctionnement de Sed mais vraiment peu d'entre eux
ont réellement fait quelque chose de particulièrement utile. Dans ce dernier
article sur Sed, il est temps de voir une bonne utilisation de Sed et d'en
changer votre point de vue. Je vais vous montrer plusieurs exemples excellents
qui n'illustrent pas seulement la puissance de Sed mais qui font aussi des
choses ordonnées (et pratiques). Par exemple, dans la deuxième partie de cet
article, je vous montre comment j'ai réalisé un script Sed qui convertit un
fichier « .QIF » du programme financier Intuit Quicken en un fichier
texte proprement formaté. Avant de faire ça, nous allons nous intéresser à des
scripts Sed utiles encore un peu plus complexes.
Ré-encodage de texte
Notre premier script convertit un texte du format UNIX vers le format
DOS/Windows. Comme vous le savez sans doute, les fichiers texte au format
DOS/Windows ont un CR (Carriage Return, un « retour chariot » ) et un
LF (Line Feed, un « saut de ligne ») à la fin de chaque ligne, alors
qu'un texte UNIX a seulement un saut de ligne.
Il peut arriver que vous ayez besoin de transférer du texte UNIX vers un système
Windows et le script suivant va réaliser pour vous la conversion nécessaire.
Exemple de code 1.1 : Conversion de format UNIX vers le format Windows |
$ sed -e 's/$/\r/' mon_fichier_unix.txt > mon_fichier_dos.txt
|
Dans ce script, l'expression régulière « $ » correspond à la fin de la
ligne et le « \r » indique à Sed d'insérer juste avant un retour
chariot : insère un retour chariot avant un saut de ligne donc chaque
ligne se terminera par un « CR/LF ». Veuillez noter que le
« \r » est remplacé seulement par un CR « retour chariot »
depuis les versions 3.02.80 et ultérieures de GNU Sed. Si vous n'avez pas encore
installé GNU Sed, les instructions pour le faire sont dans
mon premier article sur Sed.
Je ne peux pas vous dire le nombre de fois où j'ai téléchargé des exemples de
script ou du code en langage C pour constater qu'ils étaient au format
DOS/Windows. Alors que beaucoup de programmes ne sont pas préoccupés par les
fichiers textes DOS/Windows au format CR/LF, d'autres programmes le sont :
le plus notable reste Bash, perturbé dès qu'il rencontre un retour chariot.
L'invocation suivante de Sed convertit le texte au format DOS/Windows vers le
format UNIX :
Exemple de code 1.2 : Conversion de format Windows vers le format UNIX |
$ sed -e 's/.$//' mon_fichier_dos.txt > mon_fichier_unix.txt
|
La façon dont ce script fonctionne est simple : notre expression régulière
de substitution recherche le dernier caractère de la ligne qui est suivi d'un
retour chariot. Nous le remplaçons avec rien, ce qui induit sa suppression lors
de la substitution. Si vous utilisez ce script et que vous remarquez que le
dernier caractère de chaque ligne du fichier produit a été supprimé, c'est que
vous avez spécifié un fichier texte qui était déjà au format UNIX. Pas besoin de
ça !
Inverser des lignes
Voici un autre petit script pratique. Il inverse les lignes d'un fichier tout
comme le fait la commande « tac » qui est incluse dans la plupart des
distributions Linux. Le nom « tac » peut être un peu trompeur parce
que « tac » n'inverse pas la position des caractères sur la ligne
(gauche et droite) mais la position des lignes dans le fichier (en haut et en
bas). Appliquer la commande « tac » sur le fichier suivant :
Exemple de code 1.3 : Exemple de fichier |
foo
bar
oni
|
... produit le résultat suivant :
Exemple de code 1.4 : Résultat obtenu |
oni
bar
foo
|
Nous pouvons faire la même chose avec le script Sed suivant :
Exemple de code 1.5 : Faisons la même chose avec un script Sed |
$ sed -e '1!G;h;$!d' fichier_a_l_endroit.txt > fichier_inverse.txt
|
Vous trouverez ce script Sed utile si vous êtes sur un système FreeBSD qui n'a
pas la commande « tac ». Bien que ce soit pratique, c'est aussi une
bonne idée de savoir ce que ce script fait. Dissectons-le !
Explication de l'inversion
Tout d'abord, ce script contient trois commandes Sed séparées par des
points-virgules : « 1!G », « h » et « $!d ».
À présent, il est temps de bien comprendre les adresses utilisées pour la
première et la troisième commande. Si la première commande était
« 1G », la commande « G » devrait être uniquement appliquée
à la première ligne. Cependant, il y a un caractère supplémentaire
« ! ». Celui-ci nie l'adresse ce qui signifie que la commande
« G » s'appliquera à tout excepté à la première ligne. Pour la
commande « $!d », nous avons un cas similaire. Si la commande était
« $d », elle s'appliquerait seulement sur la dernière ligne du fichier
(l'adresse « $ » est moyen simple de spécifier la dernière ligne).
Cependant, avec le « ! », « $!d » applique la commande
« d » à tout excepté à la dernière ligne. Maintenant, tout ce dont
nous avons besoin est de comprendre comment interagissent ces commandes.
Sed applique les trois commandes sur chaque ligne du fichier.
Première ligne (« foo ») : La première commande
« 1!G » n'a aucun effet puisqu'elle applique la commande
« G » sur tout le fichier excepté sur la première ligne. La deuxième
commande « h » dit à Sed de copier le contenu de l'espace de travail
(ce qui est en cours de traitement soit « foo ») vers un espace de
stockage temporaire (buffer). La troisième commande « d » supprime
« foo » de l'espace de travail (car après chaque série de commandes,
le contenu de l'espace de travail est affiché vers l'écran ou redirigé vers un
fichier).
Deuxième ligne (« bar ») : La première commande « G »
ajoute le contenu de l'espace de stockage temporaire (« foo\n ») à
l'espace de travail (« bar\n »). Notre espace de travail contient à
présent « bar\nfoo\n ». La deuxième commande « h » copie le
contenu de notre espace de travail dans notre espace de stockage temporaire. La
troisième commande « d » supprime le contenu de l'espace de travail
pour que rien ne soit affiché à l'écran ou redirigé vers un fichier).
Troisième et dernière ligne (« oni ») : Les mêmes étapes sont
répétées excepté que le contenu de l'espace de travail n'est pas supprimé (car
« $! » avant le « d » signifie que la commande de
suppression de l'espace de travail n'est pas appliqué lors du traitement de la
dernière ligne). Dans le cas présent, le contenu de l'espace de travail
« oni\nbar\n\foo » est redirigé vers le fichier fichier_inverse.txt.
Maintenant, il est l'heure de réaliser de la conversion de données encore
plus puissante avec Sed !
Sed, digne d'un tour de magie sur un fichier au format QIF
Ces dernières semaines, j'ai pensé à acheter Quicken pour équilibrer mes
comptes bancaires. Quicken est un très bon programme financier et effectuerait
certainement le travail avec plus qu'il n'en faut. Mais en y repensant, j'ai
décidé qu'il serait facile de de développer un logiciel qui équilibrerait mon
livre de comptes. Après avoir raisonné, je me suis dit que j'étais un
développeur de logiciel !
J'ai développé un petit programme d'équilibrage de livre de comptes (en
utilisant Awk) qui calcule ma balance en traitant un fichier texte qui
contient toutes mes transactions. Après un peu d'optimisations, je l'ai
amélioré de sorte que je peux maintenir différentes rubriques de crédit et de
débit comme Quicken peut le faire. Mais, il y avait une fonctionnalité que je
souhaitais ajouter. J'ai transféré mes comptes vers une banque qui a une
interface de gestion des comptes en ligne sur Internet. Un jour, j'ai remarqué
que le site Internet de ma banque me permettait de télécharger les informations
de mon compte au format Quicken « .QIF ». En vraiment très peu de
temps, j'ai décidé que ce serait vraiment pratique si je pouvais convertir ces
informations au format texte.
La belle histoire de deux formats de fichiers
Avant de nous intéresser au format QIF, voici à quoi ressemble mon livre de
compte :
Exemple de code 1.6 : Exemple d'un fichier au format QIF |
28 Aug 2000 food - - Y Supermarché 30.94
25 Aug 2000 watr - 103 Y Chèque n°103 52.86
|
Dans mon fichier, tous les champs sont séparés par une ou plusieurs
tabulations avec une transaction par ligne. Après la date, le champ suivant
indique le type de dépense (ou « - » si c'est un revenu. Le troisième
champ indique le type de revenu (ou « - » si c'est une dépense). Puis,
il y a un champ pour le numéro de chèque (« - » s'il n'est pas
renseigné), un champ de vérification de la transaction (« Y » ou
« N »), un commentaire et un montant en dollar.
Maintenant, nous sommes prêt à nous intéresser au format QIF. Quand j'ai
regardé mon fichier QIF téléchargé avec un éditeur de texte, voici ce que j'ai
vu :
Exemple de code 1.7 : Affichage du fichier QIF, peu pratique à lire... |
!Type:Bank
D08/28/2000
T-8.15
N
PCHECKCARD SUPERMARKET
^
D08/28/2000
T-8.25
N
PCHECKCARD PUNJAB RESTAURANT
^
D08/28/2000
T-17.17
N
PCHECKCARD SUPERMARKET
|
Après avoir parcouru le fichier, il n'est pas très difficile d'en identifier le
format. Ignorons juste la première ligne, le format de ce fichier est le
suivant :
Exemple de code 1.8 : Format du fichier |
D<date>
T<montant de la transaction>
N<numéro de chèque>
P<description>
^
|
Débutons le traitement
Quand vous abordez un tel projet avec Sed, ne soyez pas découragé ! Sed
permet de manipuler pas à pas des données jusqu'à parvenir au résultat souhaité.
Au fur et à mesure que vous progressez, vous pouvez continuer d'améliorer votre
script Sed jusqu'à ce que le résultat ressemble exactement à ce que vous
attendiez. Vous n'avez pas besoin d'y arriver dès votre premier essai.
Tout d'abord, j'ai créé un fichier que j'ai appelé qiftrans.sed et
j'ai commencé à traiter les données :
Exemple de code 1.9 : qiftrans.sed |
1d
/^^/d
s/[[:cntrl:]]//g
|
La première commande « 1d » supprime la première ligne. La seconde
commande supprime les caractères gênants « ^ ». La dernière ligne
supprime tous les caractères de contrôle qui peuvent exister dans le fichier.
Comme je traite un fichier avec un format de fichier inconnu, je veux éliminer
le risque de rencontrer n'importe quel caractère de contrôle. Maintenant,
consacrons un peu d'énergie à notre script de base :
Exemple de code 1.10 : Script de base amélioré |
1d
/^^/d
s/[[:cntrl:]]//g
/^D/ {
s/^D\(.*\)/\1\tOUTY\tINNY\t/
s/^01/Jan/
s/^02/Feb/
s/^03/Mar/
s/^04/Apr/
s/^05/May/
s/^06/Jun/
s/^07/Jul/
s/^08/Aug/
s/^09/Sep/
s/^10/Oct/
s/^11/Nov/
s/^12/Dec/
s:^\(.*\)/\(.*\)/\(.*\):\2 \1 \3:
}
|
Tout d'abord, j'ajoute une adresse « /^D/ » pour que Sed commence
seulement à traiter lorsqu'il rencontre le premier caractère du champ de date
QIF, « D ». Toutes les commandes incluses dans les accolades
(« { (...) } ») seront exécutés dans l'ordre dès que Sed lira une
ligne dans son espace de travail.
La première ligne incluse dans les accolades transforme une ligne comme
celle-ci :
Exemple de code 1.11 : La première ligne avant modification |
D08/28/2000
|
en une ligne qui ressemble à ça :
Exemple de code 1.12 : La première ligne après modification |
08/28/2000 OUTY INNY
|
Bien sûr, ce format n'est pas encore parfait mais c'est déjà ça. Nous allons
progressivement améliorer le contenu de l'espace de lecture. Les 12 prochaines
lignes ont pour effet de transformer la date en un format de trois lettres avec
la dernière ligne qui supprime les trois barres obliques de la date. Nous
terminons avec cette ligne :
Exemple de code 1.13 : Résultat final de la ligne |
Aug 28 2000 OUTY INNY
|
Les champs « OUTY » et « INNY » servent de textes
intermédiaires et seront remplacés ultérieurement. Je ne peux pas encore les
renseigner car si le montant en dollars est positif, je veux les changer
respectivement en « - » et « inco ». Puisque le montant en
dollars n'a pas encore été lu, j'ai besoin d'utiliser des textes intermédiaires
pour l'instant.
Améliorations
Il est l'heure pour quelques améliorations supplémentaires :
Exemple de code 1.14 : Améliorations supplémentaires |
1d
/^^/d
s/[[:cntrl:]]//g
/^D/ {
s/^D\(.*\)/\1\tOUTY\tINNY\t/
s/^01/Jan/
s/^02/Feb/
s/^03/Mar/
s/^04/Apr/
s/^05/May/
s/^06/Jun/
s/^07/Jul/
s/^08/Aug/
s/^09/Sep/
s/^10/Oct/
s/^11/Nov/
s/^12/Dec/
s:^\(.*\)/\(.*\)/\(.*\):\2 \1 \3:
N
N
N
s/\nT\(.*\)\nN\(.*\)\nP\(.*\)/NUM\2NUM\t\tY\t\t\3\tAMT\1AMT/
s/NUMNUM/-/
s/NUM\([0-9]*\)NUM/\1/
s/\([0-9]\),/\1/
}
|
Les sept dernières lignes du listing ci-dessus sont un peu plus compliquées, je
vais donc les expliquer en détails. Tout d'abord, nous avons une série de trois
commandes « N ». La commande « N » dit à Sed de lire
la ligne suivante et de l'ajouter à l'espace de travail. Les trois commandes
« N » ajoutent les trois lignes suivantes à notre espace temporaire
de stockage. Notre ligne ressemble désormais à ça :
Exemple de code 1.15 : Aperçu du résultat obtenu |
28 Aug 2000 OUTY INNY \nT-8.15\nN\nPCHECKCARD SUPERMARKET
|
Le résultat affiché par Sed est devenu laid ! Nous avons besoin de
supprimer les caractères inutiles de nouvelle ligne « \n » et de
réaliser encore un peu de formatage supplémentaire. Pour cela, nous allons
utiliser la commande de substitution. Le motif que nous voulons rechercher est
le suivant :
Exemple de code 1.16 : Supprimer des caractères de nouvelle ligne et appliquer un peu de formattage supplémentaire |
'\nT.*\nN.*\nP.*'
|
Cette expression recherche une nouvelle ligne suivie par un « T »,
par zéro ou plusieurs caractères, par une nouvelle ligne, par un
« N », par n'importe quel nombre de caractères, par une nouvelle
ligne, par un « P » et enfin suivi par un nombre quelconque de
caractères. Ouf ! Cette expression régulière recherche sur le contenu
entier des trois lignes qui ont juste été ajoutées dans l'espace de travail.
Mais nous voulons reformater cette section et non pas la remplacer entièrement.
Le montant en dollars, éventuellement le numéro de chèque et la description
doivent réapparaître dans notre chaîne de caractères de remplacement. Pour cela,
nous entourons ces parties intéressantes avec des parenthèses backslashées ainsi
nous pourrons nous y référer facilement dans notre chaîne de caractères de
remplacement (en utilisant « \1 », « \2 » et
« \3 » pour dire à Sed où les insérer). Voici la commande
complète :
Exemple de code 1.17 : La commande complète |
s/\nT\(.*\)\nN\(.*\)\nP\(.*\)/NUM\2NUM\t\tY\t\t\3\tAMT\1AMT/
|
Cette commande transforme notre ligne en :
Exemple de code 1.18 : Résultat de la commande précédente |
28 Aug 2000 OUTY INNY NUMNUM Y CHECKCARD SUPERMARKET AMT-8.15AMT
|
Tandis que cette ligne se présente de mieux en mieux, il y a encore quelques
petites choses qui nous paraissent peu intéressantes... La première est cette
chaîne de caractères étrange « NUMNUM ». À quoi sert-elle ?
Vous le découvrirez quand vous inspecterez les deux lignes suivantes de notre
script Sed qui remplace « NUMNUM » par un « - » et
« NUM<nombre>NUM » par « <nombre> ». Comme
vous pouvez le voir, entourer le numéro de chèque avec une balise quelconque
nous permet d'insérer de façon pratique un « - » si le champ est
vide.
Finitions
La dernière ligne supprime une virgule qui suit un nombre. Cela convertit les
montants en dollars comme « 3,231.00 » en « 3231.00 » qui
est le format que j'utilise. Maintenant, regardons notre script finalisé :
Exemple de code 1.19 : Le script finalisé |
1d
/^^/d
s/[[:cntrl:]]//g
/^D/ {
s/^D\(.*\)/\1\tOUTY\tINNY\t/
s/^01/Jan/
s/^02/Feb/
s/^03/Mar/
s/^04/Apr/
s/^05/May/
s/^06/Jun/
s/^07/Jul/
s/^08/Aug/
s/^09/Sep/
s/^10/Oct/
s/^11/Nov/
s/^12/Dec/
s:^\(.*\)/\(.*\)/\(.*\):\2 \1 \3:
N
N
N
s/\nT\(.*\)\nN\(.*\)\nP\(.*\)/NUM\2NUM\t\tY\t\t\3\tAMT\1AMT/
s/NUMNUM/-/
s/NUM\([0-9]*\)NUM/\1/
s/\([0-9]\),/\1/
/AMT-[0-9]*.[0-9]*AMT/b fixnegs
s/AMT\(.*\)AMT/\1/
s/OUTY/-/
s/INNY/inco/
b done
:fixnegs
s/AMT-\(.*\)AMT/\1/
s/OUTY/misc/
s/INNY/-/
:done
}
|
Les onze lignes supplémentaires utilisent la substitution et quelques commandes
de renvoi pour améliorer le résultat obtenu. Nous allons tout d'abord nous
intéresser à cette ligne :
Exemple de code 1.20 : Première ligne intéressante à regarder |
/AMT-[0-9]*.[0-9]*AMT/b fixnegs
|
Cette ligne contient une commande de « renvoi » qui est au format
« /expression_régulière/b étiquette ». Si l'espace de lecture
correspond à l'expression régulière, Sed saute à l'étiquette
« fixnegs ». Vous devriez repérer facilement cette étiquette qui
apparaît en « :fixnegs » dans le code. Si l'expression régulière ne
correspond pas, le traitement continue normalement avec la prochaine commande.
Maintenant que vous avez compris le fonctionnement de la commande elle-même,
jetons un coup d'œil aux renvois. Si vous regardez l'expression régulière de
renvoi vous voyez qu'elle recherche la chaîne « AMT » suivie par un
« - » suivi par un nombre de chiffres, un « . », un nombre
de chiffres et « AMT ». Je suis sûr que vous avez compris que cette
expression régulière traite spécifiquement un montant négatif en dollars.
Précédemment, nous avons entouré notre montant en dollars avec des chaînes de
caractères « AMT » pour que nous puissions facilement les retrouver.
Parce que l'expression régulière recherche uniquement des montants en dollars
commençant par un « - », notre renvoi se produit uniquement si nous
sommes en train de traiter un débit. Si nous traitons un débit, OUTY doit être
placé à « misc », INNY doit être placé à « - » et le signe
négatif avant le montant du débit doit être supprimé. Si vous suivez le code,
vous verrez que c'est exactement ce qui se produit. Si le renvoi n'est pas
exécuté, OUTY est remplacé par « - » et INNY est remplacé par
« inco ». Nous avons terminé ! Notre résultat est maintenant
parfait :
Exemple de code 1.21 : La ligne de résultat parfaite |
28 Aug 2000 misc - - Y CHECKCARD SUPERMARCHÉ -8.15
|
Ne soyez pas perdu
Comme vous pouvez le voir, convertir des données en utilisant Sed n'est pas si
difficile que ça, d'autant plus si vous approchez le problème par étapes.
N'essayez pas de tout faire avec une seule commande Sed ou tout d'un coup. Au
lieu de ça, travaillez progressivement jusqu'au but et continuez à améliorer
votre script Sed jusqu'à ce que le résultat obtenu soit ce que vous souhaitiez.
Sed est plein d'énergie et j'espère que vous deviendrez bientôt très familier
avec son fonctionnement interne et que vous continuerez à vous développer dans
la maîtrise de Sed !
|