vendredi 5 mars 2010

sed partie 1

La commande sed est un puissant éditeur de flux permettant de nombreuses manipulations intéressantes.

La syntaxe pour appeler sed est la suivante:

sed OPTIONS... [SCRIPT] [FICHIER]

sed commence par importer la première ligne du fichier en entrée dans son espace de travail (en la débarrassant de son caractère nouvelle ligne final). Ensuite les manipulations figurant dans le script sont effectuées sur cet espace de travail. A la sortie du script, le contenu de l'espace de travail est imprimé (par défaut) après qu'un caractère nouvelle ligne lui ai été ajouté. L'espace de travail est vidé et si la fin du fichier n'est pas atteinte, le cycle recommence avec la lecture de la ligne suivante.

Chaque instruction dans le script est constituée d'une commande éventuellement précédée d'une condition restrictive, limitant le champ d'action de cette commande. Cette condition restrictive peut-être une expression régulière, un numéro de ligne etc...

Une condition restrictive doit toujours être immédiatement suivie d'une commande: il n'y a pas de commande par défaut.

Exemple (utilisant le même fichier toto que dans le billet précédent):

$ sed '3q' toto

Je m'appelle Toto.

Je vais à l'école avec Tata

Nous allons à pied car ce n'est pas loin.

La condition restrictive est ici le numéro de ligne 3. Pour les lignes 1 et 2, l'instruction contenue dans le script est inopérante et chaque sortie du script provoque l'impression de la ligne qui vient d'être lue. Pour la ligne 3, la condition est vérifiée, il y a exécution de la commande q (quit) qui provoque la sortie du script et l'arrêt du programme.

Nous avons ci-dessus émulé head -3.

Montrons maintenant d'autres opérations simples pouvant être réalisées avec sed, mais auparavant rappelons le contenu du fichier toto:

1       Je m'appelle Toto.

2       Je vais à l'école avec Tata

3       Nous allons à pied car ce n'est pas loin.

4       Elle m'aime et ça c'est cool!

5       Et oui Tata aime Toto et Toto aime Tata

6       La vie est belle car Tata habite près de Toto.

7       Ils se voient tous les jours!

(Les numéros de ligne ne font pas partie du fichier)

Impression des lignes ne contenant pas une expression régulière

$ sed '/aime/d' toto

Je m'appelle Toto.

Je vais à l'école avec Tata

Nous allons à pied car ce n'est pas loin.

La vie est belle car Tata habite près de Toto.

Ils se voient tous les jours!

La condition restrictive est la présence de l'expression régulière constante /aime/. Celle-ci est suivie de la commande d (delete) qui vide l'espace de travail, ce qui induit le fait qu'à la sortie du script, il n'y a rien à imprimer: toutes les lignes contenant le mot « aime » sont donc supprimées du flux sortant (émulation de grep -v).

Impression des lignes contenant une expression régulière

Il suffit d'inverser la condition, de détruire les lignes qui ne contiennent pas l'expression régulière, donc de remplacer la condition /aime/ par la condition /aime/!:

$ sed '/aime/!d' toto

Elle m'aime et ça c'est cool!

Et oui Tata aime Toto et Toto aime Tata

Nous avons émulé grep.

Essayons de procéder en conservant la condition /aime/ mais en remplaçant la commande d par la commande p (print):

$ sed '/aime/p' toto

Je m'appelle Toto.

Je vais à l'école avec Tata

Nous allons à pied car ce n'est pas loin.

Elle m'aime et ça c'est cool!

Elle m'aime et ça c'est cool!

Et oui Tata aime Toto et Toto aime Tata

Et oui Tata aime Toto et Toto aime Tata

La vie est belle car Tata habite près de Toto.

Ils se voient tous les jours!

Le résultat obtenu n'est pas celui espéré: à l'impression demandée par la commande p, s'ajoute l'impression automatique à chaque sortie du script. Les lignes satisfaisant au critère sont donc imprimées deux fois. Heureusement, sed peut être lancé avec l'option -n qui désactive cette impression automatique:

$ sed -n '/aime/p' toto

Elle m'aime et ça c'est cool!

Et oui Tata aime Toto et Toto aime Tata

Pour le fun, essayons maintenant de procéder sans utiliser l'option -n et sans inverser la condition, comme ceci:

$ sed  '/aime/b;d' toto

Elle m'aime et ça c'est cool!

Et oui Tata aime Toto et Toto aime Tata

Le script contient maintenant deux instructions (séparées par un point-virgule)

La commande b est une commande de branchement. Comme elle n'est suivie par aucun nom de label, le branchement s'effectue à la fin du script et ce pour les lignes satisfaisant au critère. Les autres lignes n'échappent pas à la commande inconditionnelle d.

Plutôt que d'utiliser le point-virgule pour séparer les instructions, on peut procéder en faisant précéder chacune d'elle par l'option -e (qui signifie: ajouter ce qui suit au script):

$ sed -e '/aime/b' -e 'd' toto

Elle m'aime et ça c'est cool!

Et oui Tata aime Toto et Toto aime Tata

On peut considérer que cette fois chaque ligne du script contient une seule instruction, alors qu'auparavant les deux instructions se trouvaient sur la même ligne.

Impression de la ligne 3

$ sed '3!d' toto

Nous allons à pied car ce n'est pas loin.

ou

$ sed -n '3p' toto

Nous allons à pied car ce n'est pas loin.

ou

$ sed '3b;d' toto

Nous allons à pied car ce n'est pas loin.

Impression des lignes 2 → 4

$ sed '2,4!d' toto

Je vais à l'école avec Tata

Nous allons à pied car ce n'est pas loin.

Elle m'aime et ça c'est cool!

ou

$ sed -n '2,4p' toto

Je vais à l'école avec Tata

Nous allons à pied car ce n'est pas loin.

Elle m'aime et ça c'est cool!

ou

$ sed  '2,4b;d' toto

Je vais à l'école avec Tata

Nous allons à pied car ce n'est pas loin.

Elle m'aime et ça c'est cool!

Impression des lignes 2 et 4

$ sed -n '2p;4p' toto

Je vais à l'école avec Tata

Elle m'aime et ça c'est cool!

ou

$ sed  '2b;4b;d' toto

Je vais à l'école avec Tata

Elle m'aime et ça c'est cool!

Impression de la dernière ligne

$ sed '$!d' toto

Ils se voient tous les jours!

ou

$ sed -n '$p' toto

Ils se voient tous les jours!

(la condition $ est vérifiée pour la dernière ligne du fichier)

Imprimer des lignes sur base d'une condition de départ et d'une condition d'arrêt

$ sed -n '/vais/,/aime/p' toto

Je vais à l'école avec Tata

Nous allons à pied car ce n'est pas loin.

Elle m'aime et ça c'est cool!

ou

$ sed  '/vais/,/aime/b;d' toto

Je vais à l'école avec Tata

Nous allons à pied car ce n'est pas loin.

Elle m'aime et ça c'est cool!

Imprimer les lignes sur base d'une condition de départ et d'une condition d'arrêt, mais en excluant les lignes vérifiant ces conditions

$ sed  '/vais/,/aime/ba;d;:a;/vais/d;/aime/d' toto

Nous allons à pied car ce n'est pas loin.

La commande ba branche vers le label a défini via l'instruction spéciale :a. La différence avec la formule précédente est que d'autres instructions viennent après le branchement qui détruisent ce qu'on veut éliminer.

Imprimer la ligne qui suit celle contenant une expression régulière

Ceci

$ sed -n '/vais/{n;p}' toto

Nous allons à pied car ce n'est pas loin.

semble simple et parfait. L'utilisation des accolades permet de faire suivre la condition restrictive de deux commandes:

n (next): imprime le contenu de l'espace de travail (sauf si l'option -n a été spécifiée, ce qui est le cas) et le remplace par celui de la ligne suivante

p (print): imprime cette ligne

Cependant dans le cas suivant nous constatons un problème:

$ sed -n '/Tata/{n;p}' toto

Nous allons à pied car ce n'est pas loin.

La vie est belle car Tata habite près de Toto.

La ligne « La vie est belle... » (6) suit une ligne contenant Tata: il est donc légitime qu'elle figure dans l'output. Mais la ligne suivante (la ligne 7) devrait aussi y figurer puisque « La vie est belle... » contient aussi Tata. Essayons de comprendre pourquoi elle est absente:

sed lit la ligne 5 « Et oui Tata... » et exécute le script. A l'intérieur du script, la ligne suivante (6) est lue via la commande n, puis imprimée.  Ensuite le script se termine et sed lit la ligne 7 avant d'exécuter à nouveau le script, mais l'instruction qui s'y trouve est inopérante puisque la condition restrictive n'est pas vérifiée par cette ligne 7.

La formulation correcte est la suivante:

$ sed -n ':a;/Tata/{n;p;ba}' toto

Nous allons à pied car ce n'est pas loin.

La vie est belle car Tata habite près de Toto.

Ils se voient tous les jours!

Cette fois, après impression de la ligne 6, le programme ne sort pas du script, mais le ré-exécute depuis le début. Comme la ligne 6 contient « Tata », la ligne 7 est lue via n puis imprimée.

Imprimer la ligne qui précède celle contenant une expression régulière

Le plus simple:

$ sed -n '/vais/{g;1!p};h' toto

Je m'appelle Toto.

La commande h (hold) effectue de manière inconditionnelle, une copie de l'espace de travail dans un espace de sauvegarde.

La commande g (qui s'exécute pour la ligne 2 « je vais... ») écrase le contenu de l'espace de travail avec celui de l'espace de sauvegarde qui contient la ligne lue précédemment (la ligne 1). Comme la ligne « Je vais... », n'est pas la ligne 1, le contenu de l'espace de travail est imprimé.

Cependant, ceci ne donne pas toujours un résultat satisfaisant:

$ sed -n '/Tata/{g;1!p;};h' toto

Je m'appelle Toto.

Elle m'aime et ça c'est cool!

Elle m'aime et ça c'est cool!

Après lecture de la ligne 5, g met la ligne 4 dans l'espace de travail et c'est donc cette ligne 4 qui est par la suite sauvegardée. Et après lecture de la ligne 6, c'est de nouveau la ligne 4 qui est imprimée.

Voici comment procéder:

$ sed -n '/Tata/{x;1!p;x};h' toto

Je m'appelle Toto.

Elle m'aime et ça c'est cool!

Et oui Tata aime Toto et Toto aime Tata

La commande x échange le contenu de l'espace de travail et celui de l'espace de sauvegarde. Comme x est exécuté deux fois, c'est bien la ligne qui vient d'être lue qui est à chaque fois sauvegardée.

Impression des 3 dernières lignes

$ sed ':a;$q;N;4,$D;ba' toto

Et oui Tata aime Toto et Toto aime Tata

La vie est belle car Tata habite près de Toto.

Ils se voient tous les jours!

Sed lis la première ligne du fichier et puis exécute le script. Comme la dernière instruction du script est une instruction de branchement inconditionnelle vers le début du script, sed ne sort plus jamais du script (sauf suite à l'instruction $q) Toutes les lignes sont dorénavant lues par l'intermédiaire de la commande N (Next). Contrairement à la commande n, N ne vide pas l'espace de travail, mais y  ajoute à chaque fois la ligne qu'il vient de lire (précédée d'un caractère nouvelle ligne et débarrassée de son caractère nouvelle ligne final). Après lecture de la ligne 4, la commande D devient opérante (jusqu'à la fin du fichier: condition 4,$): elle détruit à chaque fois la première ligne de l'espace de travail dans lequel subsistent alors les lignes (2-3-4), puis (3-4-5), ensuite (4-5-6) et finalement (5-6-7). Ensuite l'instruction $q devient à son tour opérante car la dernière ligne du fichier a été lue (par N): la commande q provoque la sortie du script (et donc l'impression du contenu de l'espace de travail) et l'arrêt du programme.

On a émulé tail -3.

Impression des lignes avec Toto ou Tata

Essayons comme ceci:

sed -n '/Toto/p;/Tata/p' toto

Je m'appelle Toto.

Je vais à l'école avec Tata

Eh oui Tata aime Toto et Toto aime Tata.

Eh oui Tata aime Toto et Toto aime Tata.

La vie est belle car Tata habite près de Toto.

La vie est belle car Tata habite près de Toto.

Mais il y a un problème. Les lignes avec Toto et Tata sont imprimées en double.

Comme ça, c'est mieux:

sed  '/Toto/b;/Tata/b;d' toto

Je m'appelle Toto.

Je vais à l'école avec Tata

Eh oui Tata aime Toto et Toto aime Tata.

La vie est belle car Tata habite près de Toto.

Autre possibilité:

sed -n '/Toto\|Tata/p' toto

Je m'appelle Toto.

Je vais à l'école avec Tata

Eh oui Tata aime Toto et Toto aime Tata.

La vie est belle car Tata habite près de Toto.

L'expression régulière contient un « | » qui est échappé par le backslash « \ » et devient un meta-caractère signifiant « OR ».

Encore une autre possibilité:

sed -rn '/Toto|Tata/p' toto

Je m'appelle Toto.

Je vais à l'école avec Tata

Eh oui Tata aime Toto et Toto aime Tata.

La vie est belle car Tata habite près de Toto.

L'option -r indique à sed que les expressions régulières utilisées sont des expressions régulières étendues et de ce fait « | » ne doit plus être échappé pour être un meta-caractère

Impression des lignes avec Toto et Tata dans n'importe quel ordre

sed '/Toto/!d;/Tata/!d' toto

Eh oui Tata aime Toto et Toto aime Tata.

La vie est belle car Tata habite près de Toto.

Les lignes qui ne contiennent pas Toto et qui ne contiennent pas Tata passent à la trappe.

Résultat: seules survivent les lignes avec Toto et Tata.

Impression des lignes avec Toto et Tata dans cet ordre

sed -n '/Toto.*Tata/p' toto

Eh oui Tata aime Toto et Toto aime Tata.

« .* » est un système de deux meta-caractères signifiant un certain nombre (même 0) de n'importe quel caractère.

Impression des lignes avec Toto ou Tata (ou exclusif)

sed  -nr '/Toto.*Tata|Tata.*Toto/d;/Toto/p;/Tata/p' toto

Je m'appelle Toto.

Je vais à l'école avec Tata

 

Dans le billet suivant, nous introduirons la commande reine d'un script sed, à savoir la commande de substitution s.