# Sed

C’est l’outil absolu pour modifier du texte en le passant par un pipe ! Ou pour effectuer des changements en masses sur un fichier sans l’ouvrir. Bref, comme le dit l’adage : « Sed, c’est bien » 😁

Il est possible de faire des trucs de tarés avec (hey, c’est pas juste un truc pour faire des substitutions à coup d’expressions rationnelles, c’est un vrai éditeur de texte, on peut se balader dans le texte, faire des copier/coller, etc).

## Syntaxe de base

Avec un fichier :
```bash
sed <commande> fichier.txt [fichier2.txt] [fichier3.txt]
```

En utilisant la sortie d’une autre commande :

```bash
find . -name \*.txt | sed <commande>
```

**NB** : `sed`, par défaut, ne modifie pas le fichier utilisé.
Il affichera sur la sortie standard le fichier modifié par la commande passée à `sed`.
Si on souhaite que le fichier soit modifié, on utilise l’option `--in-place` ou son abbréviation `-i` (on peut indifféremment placer l’option avant ou après la commande) :

```bash
sed <commande> --in-place fichier.txt
sed -i <commande> fichier.txt
```

## Expression régulières

Si `sed` est un éditeur de texte (son nom veut dire Stream EDitor) et qu’il est utilisable en lui donnant des commandes équivalentes à « Va à ligne 3, supprime 4 caractères, descend à la ligne suivante… », on l’utilise souvent avec des expressions régulières.

### Rappel sur les expressions régulières

Les expressions régulières permettent de rechercher des correspondances avec un motif, écrit avec une syntaxe spéciale.

Les éléments de syntaxe suivants appartiennent aux *Perl Compatible Regular Expression* (PCRE), qui sont le standard de la très grande majorité des langages de programmation.
`sed` utilisant une syntaxe légèrement différente, certains caractères devront être échappés (voir plus bas).

**NB** : j’ai placé les expressions régulières de cette section entre des `/` pour les distinguer des chaînes de caractères simples.

- `.` : correspond à un caractère, n’importe lequel. `/./` correspondra à tout sauf à une chaîne vide
- `?` : quantificateur, modifie la correspondance du caractère qui le précède : celui ci peut être présent zéro ou une fois. `/a?/` correspondra à une chaîne vide ou à `a`
- `+` : quantificateur : le caractère précédent sera présent une ou plusieurs fois. `/a+/` correspondra à `a`, `aa`, `aaa`…
- `*` : quantificateur : le caractère précédent sera présent zéro ou plusieurs fois. `/a*/` correspondra à une chaîne vide, à `a`, `aa`, `aaa`…
- `{n}` : quantificateur : le caractère précédent sera présent `n` fois. `/a{3}/` correspondra à `aaa`
- `{n,m}` : quantificateur : le caractère précédent sera présent de `n` à `m` fois. `/a{3,5}/` correspondra à `aaa`, `aaaa` ou `aaaaa`
- `{n,}` : quantificateur : le caractère précédent sera présent au moins `n` fois. `/a{3,}/` correspondra à `aaa`, `aaaa`, `aaaaa`, `aaaaaa`…
- `|` : séparateur d’expression. `/bonjour|hello/` correspondra à `bonjour` ou à `hello`
- `[^liste]` : correspond aux caractères n’étant pas entre crochets. `/[^ae]/` correspondra à n’importe quel caractère sauf à `a` et à `e`
- `[liste]` : correspond à un des caractères entre crochets. `/[ae]/` correspondra à `a` ou à `e`. Pour que le caractère `^` soit un choix possible, il faut le placer à une autre place que la première place : `[liste^]`, `[lis^te]`… On peut aussi spécifier des plages de caractères : `[0-9a-zA-Z]`
- `^` : ancre, correspond au début de la ligne. `/^a/` correspondra à `a` si celui-ci est le premier caractère de la ligne
- `$` : ancre, correspond à la fin de la ligne. `/a$/` correspondra à `a` si celui-ci est le dernier caractère de la ligne
- `(…)` : groupe l’expression. On peut s’en servir, par exemple, pour capturer des éléments (ce qui permet de les réutiliser plus tard) ou faire des sous-expressions. `/Bonjour (foo|bar), ça va \?/` correspondra à `Bonjour foo, ça va ?` et à `Bonjour bar, ça va ?`

Pour utiliser les caractères de manière littérale (exemple : pour correspondre à un point), on les échappera avec un `\`. `/\./` correspondra au caractère point (`.`).

### Caractères à échapper dans sed

La version GNU de `sed` utilise les *Basic Regular Expression* (BRE), qui ont une syntaxe légèrement différentes des PCRE.

Certains caractères doivent donc être échappés dans les BRE, qui sont utilisables tels quels pour des PCRE :

- le quantificateur `+`. Exemple : `/a\+/`
- le quantificateur `?`. Exemple : `/a\?/`
- le quantificateur `{i}`. Exemple : `/a\{5\}/`
- les parenthèses `(…)`. Exemple : `/\(a\)/`
- le séparateur d’expressions régulières `|`. Exemple : `/a\|b/`

## Effectuer une substitution de texte

La commande à utiliser est `'s/expression régulière/substitution/'` :

```bash
sed 's/foo/bar/' foo.txt
```

Cette commande remplacera la **1ère occurrence** de `foo` de **chaque ligne** du fichier par `bar`.

Si on souhaite remplacer toutes les occurrences de chaque ligne, on emploie le modificateur `g` :

```bash
sed 's/foo/bar/g' foo.txt
```

Si on ne souhaite remplacer que l’occurrence n°X de chaque ligne :

```bash
sed 's/foo/bar/X' foo.txt
## Exemple avec la 2e occurrence :
sed 's/foo/bar/2' foo.txt
```

Si on ne souhaite remplacer l’occurrence n°X de chaque ligne, ainsi que les suivantes :

```bash
sed 's/foo/bar/gX' foo.txt
## Exemple avec la 2e occurrence et les suivantes :
sed 's/foo/bar/g2' foo.txt
```

Ajouter quelque chose au début de chaque ligne :

```bash
sed 's/^/FooBar /' foo.txt
```

Ajouter quelque chose à la fin de chaque ligne :

```bash
sed 's/$/ BazQux/' foo.txt
```

Si on souhaite que l’expression régulière soit insensible à la casse (ex : `s` qui correspond aussi à `S`), on emploie le modificateur `i` :

```bash
sed 's/foo/bar/i' foo.txt
```

On peut utiliser plusieurs modificateurs en même temps :

```bash
sed 's/foo/bar/gi' foo.txt
```

Pour réutiliser ce qui a correspondu à l’expression régulière dans la chaîne de substitution, on utilise le caractère `&` :

```bash
sed 's/foo/& et bar/' foo.txt
```

Pour réutiliser ce qui a correspondu à un groupe d’expression, on utilise `\1` pour le 1er groupe, `\2` pour le 2e groupe… :

```bash
sed 's/Bonjour (foo|bar). Il fait beau./Bonsoir \1. À demain./' foo.txt
```

**NB** : on peut utiliser d’autres séparateurs que le caractère `/`, ce qui rend l’écriture d’une commande plus simple si l’expression régulière ou la substitution comportent des `/` : plus besoin de les échapper.
Exemple :

```bash
sed 's@/home/foo@/var/bar@' foo.txt
```

## Supprimer des lignes

Supprimer la ligne `n` :

```bash
sed 'nd' foo.txt
## Exemple avec la 3e ligne :
sed '3d' foo.txt
```

Supprimer les lignes `n` à `m` :

```bash
sed 'n,md' foo.txt
## Exemple :
sed '3,5d' foo.txt
```

Supprimer toutes les lignes sauf la ligne `n` :

```bash
sed 'n!d' foo.txt
## Exemple :
sed '6!d' foo.txt
```

**NB** : attention, le caractère `!` est un caractère spécial pour `bash` (et les autres shells).
Si vous utilisez des apostrophes (`'`) pour votre commande `sed`, tout ira bien, mais si vous utilisez des guillemets doubles (`"`), il faut l’échapper avec un `\`.

Supprimer toutes les lignes sauf les lignes `n` à `m` :

```bash
sed 'n,m!d' foo.txt
## Exemple :
sed '6,8!d' foo.txt
```

Supprimer les lignes qui correspondent à une expression régulière :

```bash
sed '/regex/d' foo.txt
## Exemple :
sed '/foobar/d' foo.txt
```

Pour supprimer les lignes vides, d’un fichier, il suffit d’utiliser l’expression régulière qui signifie que la ligne est vide :

```bash
sed '/^$/d' foo.txt
```

Pour supprimer la première ligne correspondant à une expression régulière, ainsi que toutes les lignes suivantes :

```bash
sed '/regex/,$d' foo.txt
## Exemple :
sed '/foobar/,$d' foo.txt
```

**NB** : attention encore une fois aux guillemets doubles, il faudrait échapper `$` dans cette commande car le shell essayerait d’interpréter `$d` comme une variable.