Gentoo Logo

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>
^
(« ^ » est le séparateur de champ)

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 !



Imprimer

Dernière mise à jour le 14 février 2008

Résumé : Dans cette conclusion d'une série d'articles sur Sed, Daniel Robbins vous donne un réel aperçu de la puissance de Sed. Après une introduction sur des scripts Sed pratiques et essentiels, il explique la réalisation d'un script Sed qui convertit un fichier Quicken au format « .QIF » en un fichier texte lisible. Ce script de conversion est non seulement opérationnel, mais il sert aussi d'excellent exemple pour illustrer la puissance des scripts en Sed.

Daniel Robbins
Auteur

Christophe Lefebvre
Traducteur

Donate to support our development efforts.

Copyright 2001-2014 Gentoo Foundation, Inc. Questions, Comments? Contact us.