jeudi 18 mars 2010

sed partie 2

Dans le billet précédent de nombreuses commandes pouvant être utilisées dans un script sed ont été présentées, à l'exception toutefois de la commande principale, la commande de substitution s.

En voici la syntaxe:

s/regexp/remplacement/[flags]

s remplace quand elle est vérifiée, l'expression régulière regexp par la chaîne remplacement.

Par défaut, seule la première occurrence de regexp est remplacée. Ce comportement peut-être modifié à l'aide des flags qui peuvent être ajoutés à la commande tels que:

g changer toutes les occurrences

2 changer l'occurrence 2

p impression s'il y a un remplacement.

Exemple d'instruction utilisant la commande s:

$ sed  '/oui/s/Tata/Titine/' 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!

Et oui Titine aime Toto et Toto aime Tata

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

Ils se voient tous les jours!

La substitution s'effectue pour la première occurrence de « Tata » et pour les lignes où l'expression régulière constante « oui » est rencontrée (NB: le contenu du fichier toto sur lequel s'applique la commande est rappelé dans le billet précédent).

Choisissons de remplacer la deuxième occurrence:

$ sed -n '/oui/s/Tata/Titine/2p' toto

Et oui Tata aime Toto et Toto aime Titine

Cette fois, nous imprimons seulement la ligne où la substitution a eu lieu. Dans ce script où /oui/ est la condition restrictive et s la commande, p n'est pas à proprement parler une commande, mais un des flags de la commande s.

Et si on demande le remplacement de la 3ième occurrence?

$ sed -n  '/oui/s/Tata/Titine/3p' toto

Nous n'avons aucun output car il n'y a pas de substitution.

Comme ceci:

$ sed -n  '/oui/{s/Tata/Titine/3;p}' toto

Et oui Tata aime Toto et Toto aime Tata

les lignes où « oui » est rencontré sont imprimées qu'il y ait substitution ou pas. p n'est plus un flag de s, mais une commande à part entière. A la condition restrictive /oui/ correspondent deux commandes séparées par un point-virgule et entourées d'accolades.

Passons maintenant en revue quelques manipulations qui peuvent être effectuées en utilisant sed et la commande s.

Ajout à la fin d'une ligne

Il suffit de prendre $ pour l'expression régulière intervenant dans s, puisque $ désigne la fin de la ligne.

$ sed -n '/belle/s/$/ C\o47est super!/p' toto

La vie est belle car Tata habite près de Toto. C'est super!

Dans la chaîne de remplacement, \o47 est mis pour l'apostrophe (47 en octal)

Considérons maintenant un fichier grub.conf dans lequel on voudrait introduire l'option vga afin d'adapter la résolution des terminaux virtuels.

La formule à utiliser est celle-ci:

$ sed -i.old '/^kernel/s/$/ vga=795/' grub.conf

^ désigne le début de la ligne, donc toutes les lignes qui commencent par kernel se voient ajouter l'option adéquate.

Il n'y aura pas d'output suite à l'utilisation de l'option i. Le fichier initial est sauvegardé sous le nom grub.conf.old, et grub.conf est directement modifié.

Conversion des fins de ligne Unix au format DOS

$ sed -n '1p' toto | od -An -tx1z

4a 65 20 6d 27 61 70 70 65 6c 6c 65 20 54 6f 74  >Je m'appelle Tot<

6f 2e 0a                                         >o..<

Nous pouvons constater en affichant en hexadécimal via od la ligne 1 du fichier toto, que les lignes de notre fichier se termine par le caractère de contrôle nouvelle ligne (0A en hexadécimal)

$ sed -n '1s/$/\r/p' toto | od -An -tx1z

4a 65 20 6d 27 61 70 70 65 6c 6c 65 20 54 6f 74  >Je m'appelle Tot<

6f 2e 0d 0a                                      >o...<

Suite à la substitution, on a maintenant une fin de ligne au format DOS avec les caractères de contrôle retour chariot (0D en hexadécimal) et nouvelle ligne.

Rappelons que dans l'espace de travail, lorsque la substitution s'effectue la ligne traitée est dépourvue de son caractère nouvelle ligne. La substitution ajoute un retour chariot auquel s'ajoute lors de l'impression un caractère nouvelle ligne.

Pour convertir tout le fichier, il suffit d'enlever la condition restrictive:

$ sed -n 's/$/\r/p' toto > toto.dos

Nous avons dirigé l'output vers le fichier toto.dos (qui sera créé) de sorte qu'il n'y a pas d'output à l'écran.

Vérifions:

$ od -An -tx1z -N 64 toto.dos

4a 65 20 6d 27 61 70 70 65 6c 6c 65 20 54 6f 74  >Je m'appelle Tot<

6f 2e 0d 0a 4a 65 20 76 61 69 73 20 c3 a0 20 6c  >o...Je vais .. l<

27 c3 a9 63 6f 6c 65 20 61 76 65 63 20 54 61 74  >'..cole avec Tat<

61 0d 0a 4e 6f 75 73 20 61 6c 6c 6f 6e 73 20 c3  >a..Nous allons .<

Conversion des fin de ligne DOS au format Unix

$ sed -n '1s/\r$//p' toto.dos | od -An -tx1z

4a 65 20 6d 27 61 70 70 65 6c 6c 65 20 54 6f 74  >Je m'appelle Tot<

6f 2e 0a         

Nous remplaçons par rien (nous supprimons) le retour chariot \r qui se trouve en fin de ligne après importation de celle-ci par sed. Ensuite un caractère nouvelle ligne est ajouté via la fonction d'impression déclenchée par le flag p.

Concaténation de deux lignes

$ sed -n '/appelle/{N;p}' toto

Je m'appelle Toto.

Je vais à l'école avec Tata

Lorsque « appelle » est rencontré, N ajoute à l'espace de travail la ligne suivante. Les deux lignes sont séparées par un caractère nouvelle ligne \n (0A en hexadécimal). Ensuite p imprime le contenu de l'espace de travail.

$ sed -n '/appelle/{N;s/\n/ /;p}' toto

Je m'appelle Toto. Je vais à l'école avec Tata

Cette fois avant l'impression, nous remplaçons le \n inclus dans l'espace de travail par un espace, avec comme résultat une concaténation des deux lignes.

Si une ligne se termine par un « \ », la concaténer avec la suivante

Notre fichier de travail sera le fichier toto.bis:

$ cat toto.bis

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!

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!

Procédons:

$ sed ':a;/\\$/{N;s/\\\n/ /};ta' toto.bis

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!

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!

Un « \ » est un caractère d'échappement, mais il perd cette qualité, il devient un caractère ordinaire, s'il est lui-même précédé d'un caractère d'échappement. Dans l'expression précédente, les « \ » sont des caractères d'échappement, tandis que les « \ » sont des caractères ordinaires. La commande ta effectue un branchement vers le label a seulement en cas de substitution. Cette boucle via le label a est nécessaire pour gérer le cas où plusieurs lignes successives se terminent par un « \ » (backslash).

Suppression des espaces et des tabulations en fin de ligne

Soit le fichier hello contenant « Hello! »:

$ cat hello

Hello!

L'utilisation de od permet de visualiser l'existence d'espaces (x20) et de tabulations (x09):

$ od -An -tx1z hello
48 65 6c 6c 6f 21 09 09 20 20 0a                 >Hello!..  .<

La présence de tels caractères de contrôles dans des fichiers de configuration système, n'est pas toujours recommandées. Il convient donc de les enlever, ce qui se fait aisément avec sed:

sed 's/[ \t]*$//' hello | od -An -tx1z
48 65 6c 6c 6f 21 0a                             >Hello!.<

Dans l'expression régulière, le meta-caractère * signifie de 0 à n occurrences du caractère précédent qui ici peut être un espace ou une tabulation, le tout étant situé en fin de ligne. Donc lorsque l'expression régulière est satisfaite, lorsque de 0 à n espaces ou tabulations sont rencontrés en fin de ligne, ceux-ci sont remplacés par rien, c'est-à-dire supprimés.

Numérotation des lignes

La commande « = » envoie directement vers le flux de sortie, sans transiter par l'espace de travail le numéro de la dernière ligne lue:

$ sed -n '3,5{=;p}' toto

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

Pour mettre le numéro sur la même ligne que le texte qui suit, il faut diriger le flux de sortie du premier sed vers un deuxième sed. On peut alors à l'aide de la commande N, concaténer les lignes deux à deux, comme ceci:

$ sed -n '3,5{=;p}' toto | sed -n 'N;s/\n/\t/;p'

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

On a remplacé dans l'espace de travail le caractère \n qui sépare les deux lignes à concaténer, par un caractère de tabulation \t. Cependant, dès l'instant où le nombre de chiffre du numéro de ligne augmente comme ci-après:

$ sed -n '9,11{=;p}' toto toto | sed -n 'N;s/\n/\t/;p'

9       Je vais à l'école avec Tata

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

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

il convient, pour un résultat plus esthétique, d'aligner les chiffres sur la droite.

Après importation des deux lignes à concaténer, insérons chaque fois 5 espaces au début de l'espace de travail avant de remplacer le caractère \n par un espace:

$ sed -n '9,11{=;p}' toto toto | sed -n 'N;s/^/     /;s/\n/ /;p'

     9 Je vais à l'école avec Tata

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

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

Le résultat n'est pas encore satisfaisant. Voici comment procéder:

$ sed -n '9,11{=;p}' toto toto | sed -nr 'N;s/^/     /;s/ *(.{6})\n/\1  /;p'

     9  Je vais à l'école avec Tata

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

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

L'option -r indique que l'expression régulière qui intervient dans la deuxième substitution est une expression régulière étendue, ce qui implique que les parenthèses et les accolades qui y figurent sont des meta-caractères.

* correspond à 0 à n occurrences du caractère précédent (ici un espace)

{6} correspond à exactement 6 occurrences du caractère précédent (ici un caractère quelconque représenté par le meta-caractère « . », c'est-à-dire en fait un espace ou un chiffre)

Les parenthèses dans l'expression régulière sont des parenthèses de mémorisation: leur contenu est représenté dans la chaîne de remplacement par \1.

La façon dont l'expression régulière est décodée dépend du nombre de chiffres du numéro de ligne:

0 espace  + (5 espaces + 1 chiffres) + \n

1 espace  + (4 espaces + 2 chiffres) + \n

2 espaces + (3 espaces + 3 chiffres) + \n

….

Le contenu des parenthèses est repris dans la chaîne de remplacement suivi de deux espaces.

Centrer un texte

$ sed -r ':a;s/^.{1,78}$/ & /;ta' 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!

                    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!

Dans la chaîne de remplacement, & est mis pour l'entièreté de l'expression régulière rencontrée.

La commande ta effectue un branchement vers le label a seulement si l'espace de travail a été modifié par la commande s.

{1,78} signifie de 1 à 78 occurrences du caractère précédent, le meta-caractère « . », qui désigne n'importe quel caractère.

En clair, pour chaque ligne lue, une boucle est exécutée dans le script qui ajoute un espace avant et après cette ligne tant que la ligne obtenue ne dépasse pas 78 caractères.

Mettre des séparateurs de milliers dans un nombre.

$ echo '1234567 123456 12345 1234 123' | sed -r ':a;s/\B[0-9]{3}\b/.&/;ta'

1.234.567 123.456 12.345 1.234 123

[0-9] est mis pour n'importe quel chiffre (de0 à 9).

Chaque fois qu'un groupe de 3 chiffres est trouvé qui se trouve à la fin d'un mot (condition \b) et qui ne commence pas au début d'un mot (condition \B), ce groupe est remplacé par lui-même (&) précédé d'un point.

Le processus recommence après chaque remplacement effectué.

Regardons de plus près comment évolue la chaîne traitée, au cours des différentes boucles.

1234567 123456 12345 1234 123

1234.567 123456 12345 1234 123

1.234.567 123456 12345 1234 123

1.234.567 123.456 12345 1234 123

1.234.567 123.456 12.345 1234 123

1.234.567 123.456 12.345 1.234 123

Le groupe de 3 chiffres trouvé (et qui sera précédé d'un point à l'étape suivante) est marqué en rouge. Le groupe 567 qui est trouvé lors du premier passage ne l'est plus ensuite. En effet, le point étant un séparateur de mot, 567 est devenu lui-même un mot, 567 commence un mot, ce qui est une condition d'exclusion.

Normalisation d'une liste avec des prix

Nous disposons d'un fichier nommé prix et comprenant des prix, tel que celui-ci:

item 1  1.25,95

  item 2   12.50

voiture 18,4v55*,3 xxx

jeudi:

billets d'avion          1544,2

item 3      8,
carburant 42,653
item 4 ,95

et nous aimerions le normaliser: mettre les séparateurs de milliers à bon escient, introduire les centimes, aligner les prix ….

Le nombre d'instructions du script devenant conséquent, il devient intéressant de mettre ces instructions dans un fichier. Passons en revue les différentes instructions qui vont figurer dans ce fichier.

Commençons par supprimer toutes les tabulations, les espaces inutiles en début de ligne:

s/^[ \t]*//

Supprimons tout ce qui n'est pas chiffre à la fin:

s/[^0-9]*$//

Le symbole ^ n'est pas ici l'indicateur de début de ligne mais plutôt un symbole de négation:[^0-9] est la classe complémentaire de [0-9], donc une classe qui contient tout ce qui n'est pas chiffre (sauf \n). Tout ensemble de 0 à n non-chiffre ancré en fin de ligne, sera supprimé.

Si une ligne ne comprend aucun chiffre, elle sera vidée par l'instruction précédente. Supprimons les lignes vides:

/^$/d

(Une ligne vide est telle que l'indicateur de fin de ligne est collé à l'indicateur de début de ligne).

Remplaçons les chaînes d'au moins un espace ou une tabulation par un seul espace:

s/[ \t]+/ /g

Le meta-caractère + a un sens proche du meta-caractère *: il signifie de 1 à n occurrences du caractère précédent.

On supprime tout ce qui dans le prix n'est pas chiffre ou virgule:

:a;s/[^0-9, ]([0-9,]*)$/\1/;ta

La chaîne recherchée pour être remplacée ne commence ni par un chiffre ni une virgule, ni par un espace, ensuite elle comprend uniquement des chiffres et des virgules. La chaîne de remplacement est la chaîne trouvée amputée de son premier caractère. Analysons en détail ce qui se passe pour le le prix 18,4v55*,3 en marquant en rouge la chaîne trouvée à chaque passage pour être remplacée:

Passage 1              18,4v55*,3

Passage 2              18,4v55,3

Passage 3               18,455,3

Il n'y a plus de suppression possible et le branchement ta devient inopérant.

On ne conserve qu'une seule virgule(la dernière):

:b;s/,([0-9]*,)/\1/;tb

Explication: le motif recherché est constitué d'une virgule suivie de 0 à n chiffres et encore d'une virgule. Le motif de remplacement est la partie du motif recherché qui est entre parenthèses, ce qui revient à supprimer la première des 2 virgules. L'opération est éventuellement recommencée plusieurs fois.

Pour les lignes se terminant par une virgule suivie d'un seul chiffre, on ajoute 0 à la fin:

s/,[0-9]{1}$/&0/

(Rappelons que & représente la chaîne trouvée et destinée à être remplacée)

S'il y a plus de deux chiffres après la virgule, on les supprime:

s/(,[0-9]{2}).+$/\1/

Explication: le motif recherché est constitué d'une virgule suivie de 2 chiffres et encore d'au moins un caractère, le tout ancré en fin de ligne. On ne conserve que la virgule et les deux chiffres qui suivent.

Si la ligne ne se termine pas par une virgule suivie de 2 chiffres, on ajoute ,00 à la fin:

/,[0-9]{2}$/!s/$/,00/

Explication: ! Indique la négation. Donc la substitution s'effectue seulement si une virgule suivie de deux chiffres ne sont pas ancrés en fin de ligne.

Insérons un zéro avant la virgule si elle n'est pas précédée par un chiffre:

S/([^0-9])(,[0-9]{2}$)/\10\2/

Explication: le motif à remplacer est un non-chiffre suivi d'une virgule et de deux chiffres terminant la ligne. Il est accommodé de 2 paires de parenthèses mémorisantes et la chaîne remplaçante est construite en insérant un 0 entre les deux parties mémorisées de la chaîne à remplacer (\1 correspond au contenu de la première paire de parenthèses mémorisantes et \2 au contenu de la deuxième paire).

Il reste à ajouter  les séparateurs de milliers, aligner les prix à droite:

:c;s/\B[0-9]{3}\b/.&/;tc

Explication: voir plus haut

:f;/^.{1,39}$/s/[0-9\.\,]*$/ &/;tf

Explication: le fonctionnement est le même que pour centrer un texte sauf que & est précédé d'un espace au lieu d'être suivi et précédé par un espace.

Ajoutons encore le symbole de l'unité monétaire:

s/$/ €/

Plaçons toutes ces instructions dans un fichier appelé norme-prix . En voici le contenu:

#!/bin/sed -rf

 

# supprime les tabulations et espaces au début

s/^[ \t]*//

# supprime tous ce qui n'est pas chiffres à la fin

s/[^0-9]*$//

# supprime les lignes vides

/^$/d

# remplace les chaînes de au moins un espace ou une tabulation par un seul espace

s/[ \t:]+/ /g

# supprime les caractères autres que chiffres et virgule à l'intérieur des prix

:a;s/[^0-9, ]([0-9,]*)$/\1/;ta

# ne conserve qu'une virgule

:b;s/,([0-9]*,)/\1/;tb

# un seul chiffre après la virgule: ajoute 0

s/,[0-9]{1}$/&0/

# ne garde que les deux chiffres après la virgule

s/(,[0-9]{2}).+$/\1/

# si le prix ne se termine pas par une virgule et 2 chiffres: ajoute,00

/,[0-9]{2}$/!s/$/,00/

# si pas de chiffres devant la virgule met un 0

s/([^0-9])(,[0-9]{2}$)/\10\2/

# insère les séparateurs de milliers

:c;s/\B[0-9]{3}\b/.&/;tc

# aligne les prix à droite

:f;/^.{1,39}$/s/[0-9\.\,]*$/ &/;tf

# ajoute le symbole monétaire

s/$/ €/

Nous pouvons maintenant exécuter sed avec l'option -f qui indique que les instructions se trouvent dans le fichier dont le nom suit:

$ sed -rf norme-prix prix

item 1                            125,95 €

item 2                          1.250,00 €

voiture                        18.455,30 €

billets d'avion                 1.544,20 €

item 3                              8,00 €

carburant                          42,65 €

item 4                              0,95 €

La ligne shebang, pour autant que norme-prix ait été rendu exécutable, permet aussi de procéder comme suit:

$ ./norme-prix prix

item 1                            125,95 €

item 2                          1.250,00 €

voiture                        18.455,30 €

billets d'avion                 1.544,20 €

item 3                              8,00 €

carburant                          42,65 €

item 4                              0,95 €

 

 

 

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.