La commande sed


Dans les chapitres précédents, on a appris à rechercher des expressions plus ou moins complexes dans un texte, à compter leur nombre, à les localiser dans le texte ... Mais aucune des commandes rencontrées ne permettait de modifier le texte lui-même.

La commande sed est un programme qui analyse un fichier texte ligne par ligne et permet de le modifier (en remplaçant des expressions régulières par exemple).


Rechercher et remplacer


Les bases.

L'usage le plus courant de sed consiste à rechercher des instances d'une chaîne de caractères ou d'une expression régulière et à les remplacer par une autre chaîne ou par une expression dépendant du motif initial.

Par exemple :

bash$ cat exemple_sed1
Paris Paris
New-York New-York
tokyo Tokyo
londre londre
Le Caire Le Caire
bash$ sed -e 's/Paris/Pekin/' < exemple_sed1
Pekin Paris
New-York New-York
tokyo Tokyo
londre londre
Le Caire Le Caire

Dans le fichier exemple_sed1, on a recherché la chaîne 'Paris' que l'on a remplacée (substitute, d'où le 's') par 'Pekin'. L'option -e indique à sed que ce qui suit est un script (ou commande à exécuter).

Un script de substitution est de la forme :

's/expr1/expr2/opt'

où 'expr1' et 'expr2' sont respectivement l'expression à rechercher et l'expression à substituer et 'opt' est une éventuelle option.

La première de ces options est l'option 'g' (pour global). Dans l'exemple précédent, on peut constater que sans options, la commande sed recherche une expression, la remplace par une autre, puis s'arrête. L'option 'g' oblige sed à rechercher toutes les occurrences d'une expression donnée et à les remplacer :

bash$ sed -e 's/Paris/Pekin/g' < exemple_sed1
Pekin Pekin
New-York New-York
tokyo Tokyo
londre londre
Le Caire Le Caire

Une question importante est alors de bien comprendre où recommence la recherche une fois qu'une substitution a été faite. Si l'on considère, par exemple :

bash$ echo 'baa' | sed 's/ba/ab/'
aba

On peut se demander si, avec l'option 'g' et une fois la substitution effectuée, la nouvelle occurrence de 'ba' sera ou non detectée ... et la réponse est ... NON :

bash$ echo 'baa' | sed 's/ba/ab/g'
aba

En fait, la recherche redémarre juste après le motif qui a été trouvé.

En revanche, il est possible d'effectuer plusieurs recherches successives avec sed, pour cela, on pourrait bien sûr employer la technique suivante :

bash$ echo baa | sed -e 's/ba/ab/' | sed -e 's/ba/ab/'
aab

Mais, il est possible de préciser à la commande sed elle-même qu'elle doit effectuer plusieurs subsitutions, tout simplement en mettant autant d'options -e que nécessaire :

bash$ echo baa | sed -e 's/ba/ab/' -e 's/ba/ab/'
aab

Sed applique alors les règles une par une à chaque ligne de son entrée.

Enfin, précisons qu'il est possible de fournir des scripts à sed, c'est-à-dire, des fichiers dans lesquels ont décrit des commandes sed :

bash$ ls -la sed_script1
-rw-r--r-- 1 bac bac 78 mar 4 08:38 sed_script1

bash$ cat sed_script1
# Voici notre premier exemple de script sed
#
s/Paris/Pekin/g
s/tokyo/Tokyo/

bash$ sed -f sed_script1 < exemple_sed1
Pekin Pekin
New-York New-York
Tokyo Tokyo
londre londre
Le Caire Le Caire

L'option -f indique à sed que ce qui suit est le nom d'un fichier script qu'il doit utiliser.

Utiliser des expressions régulières avec sed.

Le motif que l'on recherche avec sed peut, comme dans les exemples précédents, être une chaîne de caractères, mais bien souvent, l'utilisateur souhaitera une plus grande expressivité (et voudra rechercher des expressions plus complexes). Comme c'était le cas pour grep, on utilisera alors des expressions régulières dont la syntaxe est exactement la même que celle utilisées avec grep. Cependant, comme nous allons le voir, il va falloir être un peu plus rigoureux sur leur utilisation : ici on ne se contente pas de rechercher des occurrences mais on va remplacer certains motifs par d'autres.

Revenons à notre fichier d'exemple. Si l'on souhaite remplacer tous les noms de villes commençant par une majuscule par X, on pourrait être tentés d'écrire :

bash$ cat exemple_sed1
Paris Paris
New-York New-York
tokyo Tokyo
londre londre
Le Caire Le Caire
BANGKOK BANGKOK

bash$ sed -e 's/\<[A-Z]/X/g' exemple_sed1
Xaris Xaris
Xew-Xork Xew-Xork
tokyo Xokyo
londre londre
Xe Xaire Xe Xaire
XXXXXXX XXXXXXX

Comme on peut le constater, seul le motif qui a été reconnu est remplacé par conséquent, il va falloir être très précis sur la description du motif recherché.

Dans l'exemple précédent, la bonne solution sera :

bash$ sed -e 's/\<[A-Z][a-zA-Z]*/X/g' exemple_sed1
X X
X-X X-X
tokyo X
londre londre
X X X X
X X

On pourra utiliser toutes les constructions d'expressions régulières décrites dans rechercher des expressions régulières, et en particulier, la sauvegarde et la restitution de motifs (les registres utilisés pourront en particulier être rappelés lors de la substitution).

Ainsi, si l'on souhaite recherche toutes les lignes qui contiennent deux fois la même ville et les remplacer par X, on peut essayer :

bash$ sed -e 's/\([a-zA-Z]*\) \1$/X/g' exemple_sed1
X
New-York New-York
tokyo Tokyo
X
Le Caire Le Caire
X

ou mieux :

bash$ sed -e 's/\(.*\) \1$/X/g' exemple_sed1
X
X
tokyo Tokyo
X
X
X

Par ailleurs, il est possible d'utiliser le motif sauvegardé dans la substitution :

bash$ sed -e 's/\(.*\) \1$/Matching : \1/g' exemple_sed1
Matching : Paris
Matching : New-York
tokyo Tokyo
Matching : londre
Matching : Le Caire
Matching : BANGKOK


Autres commandes sed


Dans cette partie, nous allons décrire brièvement les autres commandes sed, à savoir :

On notera que comme toutes ces commandes prendront leurs arguments sur la ligne suivante, il sera bien plus simple d'utiliser des fichiers de script (sinon, il faudrait taper d'odieuses lignes de commandes pleines de '\n' ...)


Ajouter des lignes.

La syntaxe du script permettant d'ajouter les lignes 'Ligne1', 'Ligne2' ... après la ligne contenant motif est la suivante (on utilise la commande 'a' pour append) :

/motif/a\
Ligne1\
Ligne2 ...

Par exemple :

bash$ ls -la ajout1
-rwxr-xr-x 1 bac bac 63 Mar 4 11:07 ajout1*

bash$ cat ajout1
#! /bin/sed -f

/londre/a\
Ligne1 a ajouter\
Ligne2 a ajouter

bash$ ./ajout1 exemple_sed1
Paris Paris
New-York New-York
tokyo Tokyo
londre londre
Ligne1 a ajouter
Ligne2 a ajouter
Le Caire Le Caire
BANGKOK BANGKOK

On notera que, bien sûr, cet ajout se fait après chaque occurrence de 'motif ' :

bash$ cat ajout2
#! /bin/sed -f

/\<[lP]/a\
Ligne1 a ajouter\
Ligne2 a ajouter

bash$ ./ajout2 exemple_sed1
Paris Paris
Ligne1 a ajouter
Ligne2 a ajouter
New-York New-York
tokyo Tokyo
londre londre
Ligne1 a ajouter
Ligne2 a ajouter
Le Caire Le Caire
BANGKOK BANGKOK

Insérer des lignes.

L'insertion et l'ajout diffèrent en cela que l'insertion se fait avant la ligne contenant le motif. Pour cela, on utilise la commande 'i' (pour insert) :

/motif/i\
Ligne1\
Ligne2 ...

Ainsi :

bash$ cat inser
#! /bin/sed -f

/londre/i\
Ligne1 a ajouter\
Ligne2 a ajouter

bash$ ./inser exemple_sed1
Paris Paris
New-York New-York
tokyo Tokyo
Ligne1 a ajouter
Ligne2 a ajouter
londre londre
Le Caire Le Caire
BANGKOK BANGKOK

Changer des lignes.

On peut changer entièrement une ligne contenant un motif en utilisant la commande 'c' (pour change) :

/motif/c\
Ligne1\
Ligne2 ...

Par exemple :

bash$ cat change
#! /bin/sed -f

/londre/c\
Ligne1 a ajouter\
Ligne2 a ajouter

bash$ ./change exemple_sed1
Paris Paris
New-York New-York
tokyo Tokyo
Ligne1 a ajouter
Ligne2 a ajouter
Le Caire Le Caire
BANGKOK BANGKOK

Supprimer des lignes.

De même, on peut supprimer une ligne contenant un motif en utilisant la commande 'd' (pour delete) :

/motif/d

Par exemple :

bash$ sed -e '/londre/d' exemple_sed1
Paris Paris
New-York New-York
tokyo Tokyo
Le Caire Le Caire
BANGKOK BANGKOK

ou :

bash$ cat delete
#! /bin/sed -f

/londre/d

bash$ ./delete exemple_sed1
Paris Paris
New-York New-York
tokyo Tokyo
Le Caire Le Caire
BANGKOK BANGKOK

Substituer des caractères.

Il est également possible de remplacer des caractères dans un texte entier en utilisant la commande 'y' :

bash$ sed -e 'y/aiueo/AIUEO/' exemple_sed1
PArIs PArIs
NEw-YOrk NEw-YOrk
tOkyO TOkyO
lOndrE lOndrE
LE CAIrE LE CAIrE
BANGKOK BANGKOK

Les caractères 'a', 'i', 'u', 'e' et 'o' sont respectivement remplacés par 'A', 'I', 'U', 'E' et 'O'.

L'entrée et la sortie de la commande 'y' doivent être constituées de listes de caractères (et non d'expressions régulières) de même longueur.

Accéder à une ligne particulière.

Accéder à une ligne particulière en sed est très simple : il suffit pour cela d'en préciser le numéro !

Par exemple :

bash$ cat sed_script2
#! /bin/sed -f
#
3a\
LIGNE 1\
LIGNE 2

bash$ ./sed_script2 exemple_sed1
Paris Paris
New-York New-York
tokyo Tokyo
LIGNE 1
LIGNE 2
londre londre
Le Caire Le Caire
BANGKOK BANGKOK

ou :

bash$ sed -e '3d' exemple_sed1
Paris Paris
New-York New-York
londre londre
Le Caire Le Caire
BANGKOK BANGKOK

Exercice 7