# sed : modifiez votre texte sans ouvrir vos fichiers

> **Note: Technique spécialisée**
>
> sed est un outil Unix en ligne de commande qui modifie de nombreux fichiers texte en une seule passe. Il reste pratique pour des modifications ponctuelles à l'échelle d'un dépôt de sources texte ; dans un dépôt Git, vérifiez le résultat avec `git diff` avant de le committer. Voir [Git : du fichier au contenu](https://docs.redaction-technique.org/fr/tech-writing-process/git-from-file-to-content/).

## Objectif

Les clones d'Unix sont peu utilisés pour gérer la documentation technique. Ceci est étrange si l'on songe à la pléthore d'outils disponibles sous ces plateformes pour manipuler du texte dans tous les sens.

Le didacticiel construit les commandes pas à pas sur une phrase de Molière. Pour les voir appliquées à tout un fichier d'un coup, rendez-vous à [Beaucoup d’efforts...](#beaucoup-defforts).

Prenons l'exemple du dialogue entre M. Jourdain et son maître de philosophie, dans le *Bourgeois gentilhomme* de Molière :

**MONSIEUR JOURDAIN :**

*[...] Je voudrais donc lui mettre dans un billet : « Belle marquise, vos beaux yeux me font mourir d'amour » ; mais je voudrais que cela fût mis d'une manière galante, que cela fût tourné gentiment.*

[...]

**MAÎTRE DE PHILOSOPHIE :**

*On les peut mettre premièrement comme vous avez dit : Belle marquise, vos beaux yeux me font mourir d'amour. Ou bien : D'amour mourir me font, belle marquise, vos beaux yeux. Ou bien : Vos yeux beaux d'amour me font, belle marquise, mourir. Ou bien : Mourir vos beaux yeux, belle marquise, d'amour me font. Ou bien : Me font vos yeux beaux mourir, belle marquise, d'amour.*

## Intervertir les mots avec awk

Commençons par afficher la phrase d'origine dans un terminal :

```bash
awk '1'
```

Il s'agit maintenant d'intervertir les mots de la phrase, pour en créer une nouvelle. Pour une simple transposition, on pourrait juger plus facile d'utiliser awk. awk ne gère en effet pas des lignes, mais des champs d'un enregistrement (d'une ligne), délimités par défaut par des espaces. Autrement dit, awk traite le texte comme une base de données. Il peut facilement afficher toute la ligne, ou seulement un ou plusieurs champs, dans l'ordre souhaité. Les champs sont indiqués sous la forme $n, où n indique la position du champ dans la ligne, à partir de la gauche. Ainsi $1 indique le premier champ, $2 le deuxième, etc. $0 correspond à toute la ligne.

Nous allons donc donner la déclaration d'amour de M. Jourdain en entrée d'un programme awk d'une ligne, grâce au symbole de redirection pipeline (|).

```bash
awk '{print $9" "$8" "$6" "$7" "$1" "$2" "$3" "$4" "$5}'
```

La sortie de la commande echo n'est pas affichée. Ce qui est affiché, c'est la sortie du programme awk, dont la sortie de la commande echo, soit la déclaration d'amour de M. Jourdain, était l'entrée.

La sortie finale ne correspond cependant pas à ce que l'on souhaitait. Les champs ne correspondent pas trait pour trait à des mots. Il faudrait donc raffiner la commande awk.

## Réordonner les mots avec sed

Il est plus simple de se tourner vers sed. sed sélectionne dans des lignes des ensembles de caractères cités littéralement, ou via des méta-caractères dans des expressions rationnelles (ou expressions régulières). Un méta-caractère bien connu des expressions rationnelles est `*`, qui signifie « zéro ou plusieurs occurrences de l'élément précédent » : `a*` correspond à zéro ou plusieurs `a`, et `.*` - n'importe quel caractère, répété - correspond à une suite quelconque de caractères.

> **Note: Glob shell et regex**
>
> Ne confondez pas le `*` des expressions régulières avec le `*` du glob shell. En ligne de commande, `ls *.rst` utilise `*` seul pour signifier « n'importe quelle suite de caractères ». Dans une expression régulière, le `*` seul ne fait que répéter ce qui le précède ; c'est `.*` qui se comporte comme le glob shell.

### Capturer les mots avec des références arrière

sed gère également des références arrières, qui affichent à l'endroit où on le souhaite la valeur correspondant à une expression littérale ou rationnelle trouvée auparavant. Heureusement pour nous, la déclaration d'amour de M. Jourdain contient exactement neuf mots, ce qui correspond au nombre maximal de références arrières possibles.

```bash
sed "s/PATTERN/\9 \8 \6 \7, \1 \2, \3 \4 \5/"
```

### Cibler des mots entiers

Nous butons sur le même problème : l'expression régulière .* ne correspond pas à un mot, mais à une suite de caractères, ponctuation comprise. Il faut alors utiliser la forme `\<.*\>`, qui correspond à un mot tel que ceux dont M. Jourdain se sert pour faire de la prose. Nous allons utiliser les caractères d'échappement (barre oblique inverse `\`) pour que les signes `<` et `>` ne soient pas interprétés littéralement sous certaines consoles, mais comme des méta-caractères ayant une fonction spéciale :

```bash
export p="\(\<.*\>\) \(\<.*\>\), \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(d'\<.*\>\)"
sed "s/$p/\u\9 \8 \6 \7, \l\1 \2, \3 \4 \5/"
```

Nous pourrions également utiliser la forme [[:alpha:]]* qui fait gagner en lisibilité, mais perdre en concision :

### Corriger la capitalisation

C'est mieux, mais nous avons un problème de capitalisation. Nous allons donc utiliser les opérateurs `\u` et `\l` placés judicieusement. Auparavant, nous allons exporter des variables pour rendre le script plus concis et plus lisible :

```bash
sed "s/\(\<.*\>\) \(\<.*\>\), \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(d'\<.*\>\)/\u\9 \8 \6 \7, \l\1 \2, \3 \4 \5/"
```

Nous pouvons maintenant facilement redistribuer les références arrières pour obtenir toutes les variations du maître de philosophie :

```bash
sed "s/\(\<.*\>\) \(\<.*\>\), \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(d'\<.*\>\)/\u\3 \5 \4 \9 \6 \7, \l\1 \2, \8/"
```

```bash
sed "s/\(\<.*\>\) \(\<.*\>\), \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(d'\<.*\>\)/\u\8 \3 \4 \5, \l\1 \2, \9 \6 \7/"
```

```bash
sed "s/\(\<.*\>\) \(\<.*\>\), \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(d'\<.*\>\)/\u\6 \7 \3 \5 \4 \8, \l\1 \2, \9/"
```

## Molière et GNU/Linux

Réécrivons le dialogue de M. Jourdain et de son maître de philosophie en style geek :

**MONSIEUR JOURDAIN :**

Je voudrais donc lui afficher sur la sortie standard :

```console
$ Belle marquise, vos beaux yeux me font mourir d'amour.
```

Mais je voudrais que cela fût mis d'une manière galante, que cela fût tourné gentiment.

**MAÎTRE DE PHILOSOPHIE :**

On les peut mettre premièrement comme vous avez dit :

```bash
echo "Belle marquise, vos beaux yeux me font mourir d'amour."
```

Ou bien :

```bash
export declaration="Belle marquise, vos beaux yeux me font mourir d'amour."
echo $declaration
```

Ou bien :

```bash
export p="\(\<.*\>\) \(\<.*\>\), \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(d'\<.*\>\)"
sed "s/$p/\u\9 \8 \6 \7, \l\1 \2, \3 \4 \5/"
```

Ou bien :

```bash
export p="\(\<.*\>\) \(\<.*\>\), \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(d'\<.*\>\)"
sed "s/$p/\u\9 \8 \6 \7, \l\1 \2, \3 \4 \5/"
```

Ou bien :

```bash
export p="\(\<.*\>\) \(\<.*\>\), \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(d'\<.*\>\)"
sed "s/$p/\u\9 \8 \6 \7, \l\1 \2, \3 \4 \5/"
```

Ou bien :

```bash
export p="\(\<.*\>\) \(\<.*\>\), \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(\<.*\>\) \(d'\<.*\>\)"
sed "s/$p/\u\9 \8 \6 \7, \l\1 \2, \3 \4 \5/"
```

Chaque commande sed ci-dessus exécutait GNU sed. Voici ce binaire même - compilé depuis son code source C en WebAssembly - qui exécute le vrai GNU sed, en direct dans cette page. Modifiez les phrases et reformulez-les vous-même :

<noscript>Les démonstrations interactives ci-dessous nécessitent JavaScript. Sans lui, les commandes restent lisibles plus haut dans la page et fonctionnent à l'identique dans un terminal muni de GNU sed et de Python.</noscript>

La même idée de permutation de mots n'a pas à rester dans le shell : on la retrouve avec les [expressions régulières en Python](https://docs.redaction-technique.org/fr/tutorials/python-regular-expressions/). La voici en vrai Python, exécuté en direct dans cette page :

## Beaucoup d’efforts...

Certes, beaucoup d'efforts pour pas grand-chose, me direz-vous. Mais imaginons un fichier qui contient 1000 phrases de la même structure.

Ceci est en l'occurrence peu probable, mais il est en revanche monnaie courante de trouver dans la documentation technique des phrases de même structure, pour des raisons d'homogénéité stylistique.

Pour effectuer nos tests sur un échantillon, plaçons les trois phrases précédentes dans un fichier :

Plaçons les différentes commandes sed dans un script différent chacune :

Exécutons maintenant en boucle tous les scripts sed sur toutes les lignes du fichier :

```bash
for (( i=1; i<5; i++ )); do
   while read s;
    do echo "$s" |
     sed -f moliere$i.sed ;
    done < variations.txt
   done
```

Et voilà. En quelques instants, sans jamais ouvrir un seul fichier, nous appliquons une suite d'opérations complexes sur un nombre indéfini de phrases de même structure. Ce qui n'est pas possible sous un traitement de texte ou autre outil muni d'une interface graphique, ou sur des fichiers binaires.

## Articles connexes

- [Créer des documents différents à partir des mêmes sources via Jinja](https://docs.redaction-technique.org/fr/tutorials/conditional-text-jinja/)
- [Insérer automatiquement des données dans un fichier reStructuredText](https://docs.redaction-technique.org/fr/tutorials/auto-insert-data-restructuredtext/)
- [Le Raspberry Pi 3 en tant que plateforme de documentation](https://docs.redaction-technique.org/fr/tutorials/raspberry-pi-documentation-platform/)

---

Source: https://docs.redaction-technique.org/fr/tutorials/sed-text-editing/
