morceau d'image...

déc 03 2008

SED – Stream EDitor

sed SED (ou Stream EDitor) est un éditeur non-interactif, un outil en mode commande très puissant, qui fait partie des « Unix tools » depuis la nuit des temps (1973). On peut également trouver une version windows.

Pourquoi en ai-je eu besoin ? Voulant personnaliser un site web généré automatiquement par Gramps (un logiciel de généalogie), il me fallait pouvoir remplacer un mot par un autre, et ceci dans plusieurs fichiers situés dans une multitude de sous-répertoires.
Je reviendrai probablement sur Gramps plus en détail dans un autre article, car c’est vraiment un bon logiciel.

Le site web de Gramps

Le site web en question liste en plusieurs onglets les noms de famille, les individus, les lieux, les sources, une galerie photo (si vous en avez ajouté). Et une fois un individu sélectionné, ses données sont listées, et un mini arbre de ses ascendants affiché. Plutôt pas mal.

Mais voilà, je voulais y ajouter l’arbre généalogique complet, un document au format PDF (Acrobat) également réalisé à l’aide de Gramps.
Après quelques recherches, je vois que Gramps permet affectivement d’ajouter un onglet à la génération du site et d’y mettre le texte que l’on souhaite, soit exactement ce qu’il me fallait ! Hélas, il nomme (très arbitrairement) ce nouveau premier onglet « Souche« . Et moi je voulais qu’il s’appelle « Arbre« .

J’ai commencé par modifier manuellement les quelques fichiers .html à la racine du site, puis je me suis rendu compte que le mot en question existait sur chaque page html dans une multitude de sous-répertoires générés par gramps lors de la création du site. Impossible à faire à la main.

Le script

C’est là qu’intervient sed, l’éditeur de flux ! un éditeur non interactif donc, et qui va éditer les fichiers indiqués, les lire ligne par ligne, et y apporter les modifications demandées. Puissant, non ? Vous lui dites ce que vous voulez modifier dans les fichiers, et il le fait. Il gère les expressions régulières, peut supprimer, ajouter, substituer, trier, bref à peu près tout ce que l’on peut avoir besoin de faire sur un ficher texte.

J’ai donc commencé par lire un article de Linux magazine sur cette commande, pratiqué un peu, et suis parvenu très facilement à faire ce que je voulais… mais dans un seul répertoire ! Et pas un mot dans l’article pour faire la même chose sur une arborescence de fichiers.
Après quelques recherches sur le net, il faut finalement utiliser conjointement la commande find, et créer un petit script (info trouvé sur la page FAQ de sed, vraiment très complète).

Voilà donc ce petit script :

#! /bin/sh
# nom du fichier : replaceall.sh
# pas de SAUVEGARDE des fichiers faite par ce script.
find . -type f -name '*.html' -print | while read i
do
sed "s|$1|$2|g" $i > $i.tmp && mv $i.tmp $i
done

Enregistrer ce fichier, l’appeler par exemple replaceall, et enfin lui donner les droits d’exécution. Il suffit ensuite de le lancer à partir de la racine du site généré par Gramps :

$ ./replaceall Souche Arbre

Et voilà, en quelques secondes, chaque occurrence du mot Souche a été remplacé par le mot Arbre.

En savoir plus sur sed

On trouve pas mal de sites expliquant ou détaillant l’utilisation de sed, comme par exemple :
http://www.commentcamarche.net/faq/478-sed [fr]
http://sed.sourceforge.net/sedfaq.html [us]

Pour les plus motivés

Lisez la suite, ce sont les notes prises lors de mon « auto-formation ». Cela donne aussi un aperçu de la puissance de la ligne de commande.

UTILISATION DE SED

Recherche d’un mot dans un fichier

$ sed -n -e '/Souche/p' < index.html
Mon arbre généalogique - Souche

(sans l’option -n, tout le fichier est listé, et la ligne contenant le mot Souche apparait en double)

Explication de la commande :
-n n’affiche que le résultat du traitement sur la chaîne
-e permet de spécificer l’exécution d’une commande
Souche est le mot à rechercher, entre barre de fraction
‘p’ est la commande affichant le résultat du traitement
< opérateur de redirection
index.html le nom du fichier que l’on veut parcourir

Afficher le numéro de la ligne dans le fichier

$ sed -n -e '/Souche/=' < index.html
5

Explication de la commande :
Le caractère = affiche le numéro de ligne

Supprimer la ou les lignes contenant le mot

$ sed -n -e '/Souche/!p' < index.html > myindex1.html

Explication de la commande :

!p est la négation (représentée par le signe !) de p.

On va donc ne traiter que les lignes ne contenant pas ce mot. Le fichier ainsi créé (myindex1.html) ne comporte donc plus la ligne « Mon arbre généalogique – Souche ».

ou encore :

$ sed -e '/Souche/d' < index.html > myindex2.html

‘d’ efface la ligne ou la condition est remplie

Note : Avec la commande ‘d’, il ne faut pas utiliser l’option -n, car la commande d supprime des lignes du flux de sortie. Il faut donc s’abstenir d’utiliser -n pour empêcher l’echo.

Au passage, pour vérifier que les deux nouveaux fichiers sont bien identiques, on peut utiliser par exemple les deux commandes suivantes :

$ diff -s myindex1.html myindex2.html
Les fichiers myindex1.html et myindex2.html sont identiques.
$ md5sum myindex1.html myindex2.html
49a26282bf6937123d57bbcba2860af9  myindex1.html
49a26282bf6937123d57bbcba2860af9  myindex2.html

Les substitutions

Pour substituer une chaîne de caractère par un autre, le principe de sed est donc le suivant :

s / expression reguliere (motif) / remplacement / option(s)

Avant de commencer, voyons un peu ce que sont ces « expressions régulières » (regular expression en anglais, souvent appelées regex).

Les Regex

Les expressions régulières permettent donc de définir des règles pour identifier ce que l’on veut modifier. En voilà quelques exemples de base :

Regex Définition
/^ftp/ ligne commençant par ftp
/^ftp.*tcp/ ligne commençant par ftp et comprenant la chaîne tcp n’importe où ensuite (.* signifie n’imorte quel nombre de n’importe quel caractère)
/effect$/ ligne se terminant par le mot effect
/^…p/ ligne avec p en 4e position en partant du début
/aman[de]/ ligne contenant amand ou amane
/aman[de][a-t] ligne contenant amand ou amane suivid’une lettre entre a et t

Exemples simples

$ echo coucou | sed -n -e "s/c/d/p"
doucou

On voit que la règle a été appliquée, mais une seule fois. C’est le comportement par défaut de la commande p.

$ echo coucou | sed -n -e "s/c/d/gp"
doudou

ou encore

$ echo coucou | sed -e "s/c/d/g"
doudou

Explication de la commande :

g pour global : à chaque occurence, la substitution sera appliquée

Modifier tous les fichiers html du répertoire

Afin d’éviter les mauvaises surprises, on crée d’abord un répertoire que l’on nomme nouveau. Les fichiers modifiés y seront copiés :

$ mkdir nouveau
$ for i in `/bin/ls *.html`; do
> sed -e 's/Souche/Arbre/g' < $i > nouveau/$i
> done

Explication de la commande :

for i in … ; do création d’une boucle qui s’exécutera tant que la condition existe
> est ici le prompt système après la commande do
`/bin/ls *.html` lister tous les fichiers .html du répertoire courant
$i variable prenant la valeur de i initialisée dans la boucle

La première ligne va donc lister tous les fichiers .html du répertoire. Un à un, ils seront « passés » à la deuxième ligne, $i prendra donc successivement la valeur de chaque nom de fichier du répertoire courant. Une fois la substitution effectuée par sed, on enregistre le ficher avec le même nom, mais dans le répertoire que l’on a précédemment créé.

La même chose, mais en une seule ligne :

$ for i in `/bin/ls *.html`; do sed -e 's/Souche/Arbre/g'< $i > nouveau/$i; done

Remarque : la combinaison de l’option p de la commande s et de l’option -n pour sed est utile pour mettre au point la ligne de commande. Ensuite on ne les utilise plus.

Tous les fichiers html de l’arborescence

Il s’agit donc su script déjà indiqué plus haut.

#! /bin/sh
# nom du fichier : replaceall.sh
.sh.sh# pas de SAUVEGARDE des fichiers faite par ce script.
find . -type f -name '*.html' -print | while read i
do
sed "s|$1|$2|g" $i > $i.tmp && mv $i.tmp $i
done

La commande find va donc trouver tous les fichiers .html, puis une boucle est créée, et la commande sed exécutée. On remarque que le séparateur utilisé par sed est maintenant | et non plus /. En fait, sed accepte n’importe quel caractère. Pratique en cas de conflit avec ce que l’on cherche…

Les sous -chaînes

Parfois, l’identification de ce que l’on veut modifier n’est pas facile, et semblerait nécessiter plusieurs « passages » de sed. Dans l’exemple ci-dessous, on souhaite remplacer le c par un ç pour le mot français, mais également Français, Française, etc.. Mais pas pour d’autres mots ou la même suite de lettre apparaît. On construit alors un motif commençant par f ou par F, suivi de rancais ou rançais. Et seuls les mots que l’on souhaite modifier le seront. Démonstration :

$ echo "francais Francais rancaise Francaise franc" | sed -n -e 's/\([fF]\)\(rancais\)/\1rançais/gp''
français Français rancaise Française franc

Explication de la commande :

| ce caractère permet de passer à la seconde commandes le résultat de la première (appelé pipe)
\1 cette valeur numérique échappée est la sélection faite par le premier motif [fF]. La sélection est alors conservée et réutilisée
\ ce caractère sert à « échapper » le caractère qui le suit

Echapper : certains caractères seraient interprétés différemment par la commande. La présence du caractère \ permet de spécifier à la commande de traiter le caractère suivant comme un caractère normal. Cela complique la lecture de la ligne de commande pour un humain, mais bon…

Autres exemples

Prenons le fichier /etc/services, et ajoutons –ici–> quand la ligne commence par supfile suivi de s ou d, puis d’une lettre comprise entre b et r.
Je vous laisse deviner la différence entre la première et la deuxième commande. La troisième ajoute en plus ##insert## à un endroit bien précis.

$ sed -n -e 's/\(^supfile[sd][b-r].*\)/--ici--> \1/gp' < /etc/services
--ici--> supfilesrv    871/tcp                # SUP server
--ici--> supfiledbg    1127/tcp            # SUP debugging

$ sed -n -e 's/\(^supfile[sd][b-r]\).*/--ici--> \1/gp' < /etc/services
--ici--> supfilesr
--ici--> supfiledb

$ sed -n -e 's/\(^supfile[sd][b-r]\)\(.*\)/ici--> \1##insert##\2/gp' < /etc/services
ici--> supfilesr##insert##v    871/tcp                # SUP server
ici--> supfiledb##insert##g    1127/tcp            # SUP debugging

Imaginons un nouveau protocole ITCP qui permettrait de déverouiller l’accès réseau des iPhone. Pour l’heure, seul le protocole FTP fonctionne avec ITCP.
On va rechercher les lignes commençant par ftp, comprenant tcp, et remplacer ce dernier par iftp :

$ sed -n -e 's/\(^ftp.*\)\(tcp\)/\1i\2/gp' < /etc/services
ftp-data    20/itcp
ftp        21/itcp
ftps-data    989/itcp                # FTP over SSL (data)
ftps        990/itcp

Stockage des commandes

Reprenons la commande

$ for i in `/bin/ls *.html`; do sed -e 's/Souche/Arbre/g' < $i > nouveau/$i; done

On peut stocker les commandes de sed dans un fichier :

$ echo 's/Souche/Arbre/g' > essai.sed

La commande devient alors :

$ for i in `/bin/ls *.html`; do sed -f essai.sed < $i > nouveau/$i; done

Mieux, éditer le fichier essai.sed et ajouter au début :

#/usr/bin/sed -f

Puis donner les droits d’exécution au fichier :

$ chmod +x essai.sed

La commande devient alors :

$ for i in `/bin/ls *.html`; do essai.sed < $i > nouveau/$i; done

Pattern space

Le fonctionnement de Sed fait qu’il charge une ligne de texte en mémoire tampon, puis opère les modifications avant de l’afficher. Cette mémoire tampon est appelée Pattern space dans la terminologie de Sed.
La commande N permet ainsi d’ajouter au pattern space un saut de ligne (\n), puis la ligne de données suivante.

Exemple

Soit un fichier appelé test, et contenant 10 lignes. Chacune d’elles contient un nombre de 1 à 10 placé chronologiquement. Nous voulons différencier les lignes paires des impaires.

$ sed 'N;s/\(^.*\)\n\(^.*\)/_\1_\n*\2*/' < test
_1_
*2*
_3_
*4*
_5_
*6*
_7_
*8*
_9_
*10*

Conclusion

Avec sed, on peut donc faire beaucoup de choses, y compris se prendre la tête. Mais quel outil !

Publié par Pascal à 23:12

Aucun commentaire à cet article ↓

Laisser un commentaire