---
jupytext:
  text_representation:
    extension: .md
    format_name: myst
    format_version: 0.13
    jupytext_version: 1.16.0
kernelspec:
  name: python3
  display_name: Python 3
---

# Transformer et filtrer

```{code-cell} python
:tags: [hide-input]

import matplotlib.pyplot as plt
import matplotlib.patches as patches
import numpy as np
import seaborn as sns

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
```

Le traitement de données textuelles est l'un des domaines où le shell brille le plus. Linux dispose d'un ensemble d'outils de filtrage et de transformation qui, bien que conçus chacun pour une tâche précise, peuvent être enchaînés en pipelines d'une puissance remarquable. Ces outils — `cut`, `sort`, `uniq`, `tr`, `sed` et `awk` — constituent la boîte à outils du data engineering en ligne de commande. Ils traitent les données ligne par ligne, de manière efficace même sur des fichiers de plusieurs gigaoctets, sans charger tout le contenu en mémoire. Ce chapitre présente chacun de ces outils en détail, en insistant sur leurs options les plus utiles et sur la façon dont ils s'articulent les uns avec les autres.

## La commande `cut` — extraire des colonnes

La commande `cut` extrait des portions de chaque ligne d'un fichier ou d'un flux. Elle est particulièrement adaptée aux fichiers structurés en colonnes séparées par un délimiteur fixe, comme les fichiers CSV ou les fichiers de configuration à tabulations.

```{prf:definition} Syntaxe de `cut`
:label: definition-06-01
La commande `cut` accepte trois modes d'extraction exclusifs :

- **`-c` (characters)** : extrait les caractères aux positions indiquées.
- **`-b` (bytes)** : extrait les octets aux positions indiquées (utile pour les données binaires ou l'encodage multi-octets).
- **`-f` (fields)** : extrait les champs (colonnes) délimités par un séparateur.

L'option **`-d`** (delimiter) spécifie le caractère séparateur utilisé avec `-f`. Par défaut, le séparateur est la tabulation.
```

### Extraction par champs (`-f` et `-d`)

L'usage le plus courant de `cut` est l'extraction de colonnes dans un fichier délimité. Supposons un fichier `utilisateurs.csv` contenant des informations sur des utilisateurs :

```bash
# Contenu de utilisateurs.csv
# nom,prenom,age,ville,email
alice,Dupont,32,Paris,alice@example.com
bob,Martin,25,Lyon,bob@example.com
carol,Bernard,41,Marseille,carol@example.com
```

Pour extraire uniquement le premier et le troisième champ (nom et âge) :

```bash
cut -d',' -f1,3 utilisateurs.csv
# alice,32
# bob,25
# carol,41
```

Pour extraire une plage de champs continus, on utilise la notation `début-fin` :

```bash
cut -d',' -f1-3 utilisateurs.csv
# alice,Dupont,32
# bob,Martin,25
# carol,Bernard,41
```

Pour extraire tous les champs à partir du deuxième :

```bash
cut -d',' -f2- utilisateurs.csv
# Dupont,32,Paris,alice@example.com
# Martin,25,Lyon,bob@example.com
# Bernard,41,Marseille,carol@example.com
```

### Extraction par caractères (`-c`)

L'option `-c` traite chaque ligne comme une séquence de caractères et extrait ceux aux positions indiquées. C'est utile pour les fichiers à largeur fixe, comme certains exports de bases de données héritées :

```bash
# Extraire les caractères 1 à 5 de chaque ligne
cut -c1-5 fichier.txt

# Extraire les caractères 1, 3 et 5
cut -c1,3,5 fichier.txt

# Extraire à partir du dixième caractère
cut -c10- fichier.txt
```

Un usage pratique : extraire les huit premières lettres d'un hash Git pour l'affichage :

```bash
git log --format="%H %s" | cut -c1-8,41-
# Affiche les 8 premiers caractères du hash, puis le message de commit
```

```{prf:remark}
:label: remark-06-01
La commande `cut` ne peut pas réordonner les champs : elle les extrait toujours dans l'ordre où ils apparaissent dans la ligne. Si vous avez besoin de réordonner les colonnes, il faut utiliser `awk`. De même, `cut` ne gère pas les délimiteurs à plusieurs caractères ni les champs entre guillemets (comme dans certains fichiers CSV complexes). Pour ces cas, on préférera `awk` ou des outils spécialisés comme `csvkit`.
```

### Cas pratiques avec `/etc/passwd`

Le fichier `/etc/passwd` est un excellent terrain d'entraînement pour `cut` car il est délimité par des deux-points et contient sept champs bien définis :

```bash
# Afficher uniquement les noms d'utilisateurs (champ 1)
cut -d':' -f1 /etc/passwd

# Afficher le nom d'utilisateur et son répertoire personnel (champs 1 et 6)
cut -d':' -f1,6 /etc/passwd

# Afficher le nom d'utilisateur et son shell par défaut (champs 1 et 7)
cut -d':' -f1,7 /etc/passwd | sort
```

## La commande `sort` — trier les données

La commande `sort` trie les lignes d'un fichier ou d'un flux. Sans options, elle effectue un tri lexicographique (alphabétique) ascendant. Mais ses nombreuses options permettent des tris numériques, inversés, par colonnes spécifiques ou avec déduplication.

```{prf:definition} Options principales de `sort`
:label: definition-06-02
Les options les plus utilisées de `sort` sont :

- **`-n`** (numeric) : tri numérique plutôt que lexicographique. Sans cette option, `10` serait trié avant `2` car `'1' < '2'` en ASCII.
- **`-r`** (reverse) : ordre décroissant.
- **`-k`** (key) : définit la ou les colonnes de tri. La syntaxe est `-k début[,fin]`.
- **`-t`** (tab/separator) : définit le séparateur de champs (analogue à `-d` de `cut`).
- **`-u`** (unique) : supprime les doublons (équivalent à `sort | uniq`).
- **`-f`** (fold) : insensible à la casse.
- **`-h`** (human) : tri de tailles humaines (`1K`, `2M`, `3G`).
- **`-V`** (version) : tri de numéros de version (`1.9 < 1.10`).
```

### Tri numérique vs lexicographique

La distinction entre tri numérique et lexicographique est fondamentale et source de nombreuses erreurs :

```bash
# Fichier contenant des tailles de fichiers
printf "10\n2\n20\n3\n100\n1\n" > tailles.txt

# Tri lexicographique (défaut) — incorrect pour des nombres
sort tailles.txt
# 1
# 10
# 100
# 2
# 20
# 3

# Tri numérique — correct
sort -n tailles.txt
# 1
# 2
# 3
# 10
# 20
# 100

# Tri numérique inversé (plus grand en premier)
sort -rn tailles.txt
# 100
# 20
# 10
# 3
# 2
# 1
```

### Tri par colonne (`-k` et `-t`)

L'option `-k` permet de trier selon une colonne précise. La syntaxe complète est `-k pos_debut[,pos_fin][options]`, où `pos_debut` et `pos_fin` sont des numéros de champs (1-indexed) et `options` peut inclure `n` (numérique), `r` (inverse), `f` (insensible à la casse), etc.

```bash
# Trier le fichier passwd par UID (champ 3), numériquement
sort -t':' -k3,3n /etc/passwd

# Trier un CSV par âge (champ 3), numériquement, ordre décroissant
sort -t',' -k3,3rn utilisateurs.csv

# Tri multi-critères : d'abord par ville (champ 4), puis par nom (champ 1)
sort -t',' -k4,4 -k1,1 utilisateurs.csv
```

```{prf:example} Trouver les dix processus les plus gourmands en mémoire
:label: example-06-01
La combinaison de `ps` et `sort` permet d'identifier rapidement les processus qui consomment le plus de ressources :

```bash
# Lister les processus triés par consommation mémoire (RSS), décroissant
ps aux --no-headers | sort -k6,6rn | head -10

# Lister les processus triés par CPU, décroissant
ps aux --no-headers | sort -k3,3rn | head -10

# Afficher uniquement le nom et la consommation mémoire
ps aux --no-headers | sort -k6,6rn | head -10 | awk '{print $11, $6"K"}'
```
```

### Tri de tailles humaines (`-h`) et de versions (`-V`)

```bash
# Trier des tailles de fichiers en format humain
du -sh /usr/lib/* 2>/dev/null | sort -h | tail -10
# Affiche les 10 plus grands répertoires de /usr/lib

# Trier des numéros de version correctement
printf "1.10\n1.9\n2.0\n1.2\n" | sort -V
# 1.2
# 1.9
# 1.10
# 2.0
```

## La commande `uniq` — gérer les doublons

La commande `uniq` élimine ou identifie les lignes consécutives identiques dans un flux. Elle fonctionne **sur des lignes consécutives** : il est donc presque toujours nécessaire de trier les données avec `sort` avant d'appliquer `uniq`.

```{prf:definition} Options de `uniq`
:label: definition-06-03
Les options essentielles de `uniq` :

- **`-c`** (count) : préfixe chaque ligne par le nombre d'occurrences consécutives.
- **`-d`** (duplicates) : n'affiche que les lignes qui apparaissent **plus d'une fois**.
- **`-u`** (unique) : n'affiche que les lignes qui apparaissent **exactement une fois**.
- **`-i`** (ignore case) : comparaison insensible à la casse.
- **`-f N`** : ignore les N premiers champs lors de la comparaison.
- **`-s N`** : ignore les N premiers caractères lors de la comparaison.
```

### Comptage de fréquences

Le pipeline `sort | uniq -c | sort -rn` est un classique pour compter les fréquences d'apparition :

```bash
# Compter les erreurs dans un fichier de log
grep "ERROR" /var/log/syslog | awk '{print $5}' | sort | uniq -c | sort -rn | head -10

# Compter les adresses IP les plus fréquentes dans un log Apache
awk '{print $1}' /var/log/apache2/access.log | sort | uniq -c | sort -rn | head -20

# Compter les extensions de fichiers dans un répertoire
find /home -type f | sed 's/.*\.//' | sort | uniq -c | sort -rn
```

### Identifier les doublons et les uniques

```bash
# Fichier avec des prénoms, certains en double
printf "alice\nbob\nalice\ncarol\nbob\nbob\ndave\n" > prenoms.txt

# Lignes qui apparaissent plus d'une fois
sort prenoms.txt | uniq -d
# alice
# bob

# Lignes qui n'apparaissent qu'une seule fois
sort prenoms.txt | uniq -u
# carol
# dave

# Toutes les lignes avec leur nombre d'occurrences
sort prenoms.txt | uniq -c | sort -rn
#       3 bob
#       2 alice
#       1 dave
#       1 carol
```

```{prf:remark}
:label: remark-06-02
Une erreur courante est d'oublier le `sort` avant `uniq`. Par exemple, avec le fichier `alice\nbob\nalice`, un `uniq` sans tri ne supprimera pas les deux occurrences d'`alice` car elles ne sont pas consécutives. La règle à retenir : **`uniq` ne supprime que les lignes immédiatement consécutives et identiques**.
```

## La commande `tr` — translitération et transformation

La commande `tr` (translate) effectue des opérations de substitution, suppression et compression caractère par caractère. Contrairement aux autres commandes de cette liste, `tr` ne lit pas de fichiers : elle opère uniquement sur l'entrée standard.

```{prf:definition} Syntaxe de `tr`
:label: definition-06-04
La syntaxe de `tr` est : `tr [OPTIONS] ensemble1 [ensemble2]`

- **Sans option** : remplace chaque caractère de `ensemble1` par le caractère correspondant dans `ensemble2`.
- **`-d`** (delete) : supprime les caractères présents dans `ensemble1`.
- **`-s`** (squeeze) : compresse les séquences répétées de caractères de `ensemble1` en une seule occurrence.
- **`-c`** (complement) : utilise le complément de `ensemble1` (tous les caractères qui ne sont pas dans `ensemble1`).

Les ensembles peuvent utiliser des **plages** (`a-z`, `A-Z`, `0-9`) et des **classes POSIX** (`[:alpha:]`, `[:digit:]`, `[:upper:]`, `[:lower:]`, `[:space:]`, `[:punct:]`).
```

### Conversion de casse

```bash
# Convertir en majuscules
echo "bonjour le monde" | tr 'a-z' 'A-Z'
# BONJOUR LE MONDE

# Convertir en minuscules
echo "BONJOUR LE MONDE" | tr 'A-Z' 'a-z'
# bonjour le monde

# Utilisation des classes POSIX (portable, gère l'accentuation différemment)
echo "Bonjour" | tr '[:upper:]' '[:lower:]'
# bonjour
```

### Suppression de caractères (`-d`)

```bash
# Supprimer tous les chiffres
echo "a1b2c3d4" | tr -d '0-9'
# abcd

# Supprimer les retours à la ligne (concaténer toutes les lignes)
cat fichier.txt | tr -d '\n'

# Supprimer les espaces
echo "b o n j o u r" | tr -d ' '
# bonjour

# Supprimer tous les caractères non alphanumériques
echo "Bonjour, monde ! (2024)" | tr -dc '[:alnum:] \n'
# Bonjour monde  2024
```

### Compression de caractères répétés (`-s`)

```bash
# Compresser les espaces multiples en un seul
echo "bonjour    le     monde" | tr -s ' '
# bonjour le monde

# Compresser les retours à la ligne multiples
cat fichier.txt | tr -s '\n'

# Normaliser un fichier CSV mal formaté (espaces autour des virgules)
echo "alice ,  32 , Paris" | tr -s ' '
# alice ,  32 , Paris   (tr -s ne supprime pas, il compresse)
```

### Translitération avancée

```bash
# Chiffrement ROT13 (substitution alphabétique)
echo "message secret" | tr 'a-zA-Z' 'n-za-mN-ZA-M'
# zrffntr frperg

# Convertir les tabulations en espaces
cat fichier.tsv | tr '\t' ' '

# Convertir les retours à la ligne Windows (CR+LF) en Unix (LF)
tr -d '\r' < fichier_windows.txt > fichier_unix.txt

# Remplacer les virgules par des tabulations (conversion CSV -> TSV)
tr ',' '\t' < fichier.csv > fichier.tsv
```

## La commande `sed` — éditeur de flux

`sed` (stream editor) est un éditeur non interactif qui traite les données ligne par ligne en appliquant des commandes d'édition. C'est l'un des outils les plus puissants du shell, capable de remplacer, supprimer, insérer et transformer du texte de manière très fine.

```{prf:definition} Modèle de fonctionnement de `sed`
:label: definition-06-05
`sed` maintient un **espace de motif** (*pattern space*) et un **espace de maintien** (*hold space*). Pour chaque ligne du flux d'entrée :

1. La ligne est chargée dans l'espace de motif.
2. Les commandes `sed` sont appliquées séquentiellement.
3. Le contenu de l'espace de motif est affiché (sauf si supprimé par `d` ou si `-n` est actif).

La syntaxe générale est : `sed [OPTIONS] 'adresse commande' fichier`

L'**adresse** est optionnelle et permet de cibler certaines lignes. La **commande** spécifie l'action à effectuer.
```

### Substitution : la commande `s`

La commande de substitution est de loin la plus utilisée. Sa syntaxe est `s/motif/remplacement/drapeaux` :

```bash
# Remplacement simple (première occurrence par ligne)
echo "le chat mange le chat" | sed 's/chat/chien/'
# le chien mange le chat

# Remplacement global (toutes les occurrences par ligne) avec le drapeau g
echo "le chat mange le chat" | sed 's/chat/chien/g'
# le chien mange le chien

# Insensible à la casse avec le drapeau i
echo "Chat CHAT chat" | sed 's/chat/chien/gi'
# chien chien chien

# Remplacement uniquement de la Nième occurrence
echo "a a a a" | sed 's/a/b/2'
# a b a a
```

### Adresses et sélection de lignes

Les adresses permettent de cibler une ou plusieurs lignes spécifiques :

```bash
# Agir uniquement sur la ligne 3
sed '3s/foo/bar/' fichier.txt

# Agir sur les lignes 2 à 5
sed '2,5s/foo/bar/' fichier.txt

# Agir sur la dernière ligne ($)
sed '$s/foo/bar/' fichier.txt

# Agir sur les lignes contenant un motif
sed '/motif/s/foo/bar/' fichier.txt

# Agir sur toutes les lignes SAUF celles contenant un motif (!)
sed '/motif/!s/foo/bar/' fichier.txt

# Agir sur les lignes paires (toutes les 2 lignes à partir de la 2e)
sed '0~2s/foo/bar/' fichier.txt
```

### Suppression de lignes : la commande `d`

```bash
# Supprimer les lignes vides
sed '/^$/d' fichier.txt

# Supprimer les lignes commençant par '#' (commentaires)
sed '/^#/d' fichier.txt

# Supprimer les lignes 3 à 7
sed '3,7d' fichier.txt

# Supprimer les espaces en début et fin de ligne (trim)
sed 's/^[[:space:]]*//; s/[[:space:]]*$//' fichier.txt
```

### Modification en place (`-i`)

L'option `-i` (in-place) permet de modifier le fichier directement, sans passer par une redirection :

```bash
# Modifier le fichier en place
sed -i 's/ancien/nouveau/g' fichier.txt

# Modifier en place avec sauvegarde du fichier original
# (le suffixe .bak sera ajouté au nom de l'original)
sed -i.bak 's/ancien/nouveau/g' fichier.txt

# Modifier plusieurs fichiers en une commande
sed -i 's/http:/https:/g' *.html
```

```{prf:remark}
:label: remark-06-03
Sur macOS, la syntaxe de `-i` est légèrement différente : il faut obligatoirement spécifier un suffixe (même vide) : `sed -i '' 's/ancien/nouveau/g' fichier.txt`. Pour écrire des scripts portables, on peut soit utiliser `perl -pi -e` à la place, soit détecter le système et adapter la commande.
```

### Insertion, ajout et affichage sélectif

```bash
# Insérer une ligne AVANT la ligne 3 (i pour insert)
sed '3i\Ligne insérée avant la ligne 3' fichier.txt

# Ajouter une ligne APRÈS la ligne 3 (a pour append)
sed '3a\Ligne ajoutée après la ligne 3' fichier.txt

# Afficher uniquement les lignes 5 à 10 (-n supprime l'affichage automatique, p affiche)
sed -n '5,10p' fichier.txt

# Afficher uniquement les lignes contenant "erreur"
sed -n '/erreur/p' fichier.txt

# Afficher avec les numéros de ligne
sed -n '=' fichier.txt | paste - <(cat fichier.txt)
```

### Commandes multiples et scripts `sed`

```bash
# Plusieurs commandes séparées par ;
sed 's/foo/bar/; s/baz/qux/' fichier.txt

# Plusieurs commandes avec -e
sed -e 's/foo/bar/' -e 's/baz/qux/' fichier.txt

# Script sed dans un fichier
# Contenu de script.sed :
# s/foo/bar/g
# /^#/d
# s/  */ /g
sed -f script.sed fichier.txt
```

### Traitement multi-lignes avec `sed`

`sed` peut accumuler des lignes dans l'espace de maintien pour des traitements multi-lignes :

```bash
# Joindre chaque paire de lignes
sed 'N; s/\n/ /' fichier.txt

# Supprimer les lignes vides consécutives (ne garder qu'une seule ligne vide)
sed '/./,/^$/!d' fichier.txt
# Ou plus simplement :
cat -s fichier.txt
```

## La commande `awk` — traitement structuré de données

`awk` est un véritable langage de programmation dédié au traitement de données structurées. Contrairement à `sed` et `tr`, `awk` comprend les concepts de champs, d'enregistrements, de variables et de structures de contrôle. Son nom est l'acronyme de ses créateurs : **A**ho, **W**einberger et **K**ernighan.

```{prf:definition} Structure d'un programme `awk`
:label: definition-06-06
Un programme `awk` est une séquence de règles de la forme :

```
motif { action }
```

Pour chaque ligne (enregistrement) de l'entrée, si le `motif` est vérifié, l'`action` est exécutée. Les blocs `BEGIN` et `END` sont des motifs spéciaux exécutés respectivement avant et après le traitement de toutes les lignes.

**Variables automatiques de `awk` :**
- `$0` : la ligne entière.
- `$1`, `$2`, ..., `$NF` : le 1er, 2e, ..., dernier champ.
- `NF` : nombre de champs dans la ligne courante.
- `NR` : numéro de l'enregistrement (ligne) courant.
- `FNR` : numéro de la ligne dans le fichier courant (utile avec plusieurs fichiers).
- `FS` : séparateur de champs en entrée (défaut : espace/tabulation).
- `OFS` : séparateur de champs en sortie (défaut : espace).
- `RS` : séparateur d'enregistrements en entrée (défaut : `\n`).
- `ORS` : séparateur d'enregistrements en sortie (défaut : `\n`).
- `FILENAME` : nom du fichier en cours de traitement.
```

### Les bases : sélection et affichage de champs

```bash
# Afficher le premier et troisième champ de chaque ligne
awk '{print $1, $3}' fichier.txt

# Afficher le dernier champ ($NF)
awk '{print $NF}' fichier.txt

# Afficher l'avant-dernier champ ($(NF-1))
awk '{print $(NF-1)}' fichier.txt

# Afficher les champs dans l'ordre inverse
awk '{print $3, $2, $1}' fichier.txt

# Définir le séparateur de champs (-F ou FS)
awk -F':' '{print $1, $3}' /etc/passwd
awk -F',' '{print $1, $4}' utilisateurs.csv
```

### Filtres avec `awk`

`awk` peut filtrer les lignes en fonction de conditions portant sur n'importe quelle variable ou champ :

```bash
# Afficher les lignes où le troisième champ est supérieur à 30
awk -F',' '$3 > 30 {print $0}' utilisateurs.csv

# Afficher les lignes contenant le mot "erreur" (équivalent de grep)
awk '/erreur/' fichier.txt

# Afficher les lignes qui ne contiennent PAS "erreur"
awk '!/erreur/' fichier.txt

# Afficher les lignes de longueur supérieure à 80 caractères
awk 'length($0) > 80' fichier.txt

# Afficher les lignes paires
awk 'NR % 2 == 0' fichier.txt

# Afficher les 10 premières lignes (équivalent de head)
awk 'NR <= 10' fichier.txt
```

### Les blocs `BEGIN` et `END`

```bash
# Afficher un en-tête avant les données et un pied de page après
awk -F',' 'BEGIN {print "=== Rapport utilisateurs ==="}
           {print $1, $2, $3}
           END {print "=== Total :", NR, "utilisateurs ==="}' utilisateurs.csv

# Calculer la somme et la moyenne d'une colonne numérique
awk -F',' 'BEGIN {somme=0; n=0}
           NR>1 {somme += $3; n++}
           END {print "Somme:", somme; print "Moyenne:", somme/n}' utilisateurs.csv
```

### Calculs et opérations arithmétiques

`awk` dispose d'opérateurs arithmétiques complets (`+`, `-`, `*`, `/`, `%`, `^`) et de fonctions mathématiques (`sin`, `cos`, `sqrt`, `exp`, `log`, `int`, `rand`, etc.) :

```bash
# Convertir des degrés Celsius en Fahrenheit
awk 'BEGIN {for (c=0; c<=100; c+=10) printf "%3d°C = %6.2f°F\n", c, c*9/5+32}'

# Calculer la taille totale des fichiers (en Ko)
ls -la | awk 'NR>1 && $1 !~ /^d/ {total += $5} END {printf "Total: %.1f Ko\n", total/1024}'

# Calculer la facture avec TVA
awk -F',' '{ttc = $2 * 1.20; printf "%-20s HT: %8.2f€  TTC: %8.2f€\n", $1, $2, ttc}' facture.csv
```

### Variables et tableaux associatifs

L'une des caractéristiques les plus puissantes d'`awk` est son support des tableaux associatifs :

```bash
# Compter le nombre d'occurrences de chaque valeur du premier champ
awk '{compteur[$1]++} END {for (k in compteur) print k, compteur[k]}' fichier.txt

# Calculer la somme par groupe (équivalent d'un GROUP BY SQL)
awk -F',' '{somme[$4] += $3; count[$4]++}
           END {for (ville in somme)
                    printf "%-15s: total=%d, moy=%.1f\n",
                           ville, somme[ville], somme[ville]/count[ville]}' utilisateurs.csv

# Détecter les doublons sans trier
awk 'seen[$0]++ > 0' fichier.txt
```

### Fonctions de chaînes dans `awk`

```bash
# Convertir en majuscules (GNU awk)
awk '{print toupper($0)}' fichier.txt

# Convertir en minuscules
awk '{print tolower($1)}' fichier.txt

# Extraire une sous-chaîne : substr(str, début, longueur)
awk '{print substr($1, 1, 3)}' fichier.txt

# Rechercher et remplacer : gsub(motif, remplacement, chaîne)
awk '{gsub(/foo/, "bar"); print}' fichier.txt

# Longueur d'une chaîne
awk '{print length($1), $1}' fichier.txt | sort -rn | head -5

# Séparer une chaîne avec split
awk '{n = split($1, parts, "."); for (i=1; i<=n; i++) print parts[i]}' fichier.txt
```

```{prf:example} Analyser un log Apache avec `awk`
:label: example-06-02
Les fichiers de log Apache suivent le format *Combined Log Format*. Voici comment les exploiter avec `awk` :

```bash
# Format : IP - - [date] "MÉTHODE URL protocole" CODE taille "référent" "user-agent"

# Compter les codes HTTP et les trier
awk '{print $9}' /var/log/apache2/access.log | sort | uniq -c | sort -rn

# Calculer le trafic total en octets
awk '{sum += $10} END {printf "Trafic total: %.2f Mo\n", sum/1048576}' /var/log/apache2/access.log

# Lister les pages les plus consultées (code 200 uniquement)
awk '$9 == 200 {print $7}' /var/log/apache2/access.log | sort | uniq -c | sort -rn | head -20

# Identifier les adresses IP ayant généré le plus d'erreurs 404
awk '$9 == 404 {ip[$1]++} END {for (i in ip) print ip[i], i}' \
    /var/log/apache2/access.log | sort -rn | head -10
```
```

## Visualisation : le pipeline de transformation

```{code-cell} python
:tags: [hide-input]

fig, ax = plt.subplots(figsize=(16, 8))
ax.set_xlim(-0.5, 15.5)
ax.set_ylim(-1.5, 7.5)
ax.axis('off')
ax.set_title('Pipeline de transformation de texte en ligne de commande',
             fontsize=15, fontweight='bold', pad=20)

palette = sns.color_palette("muted", 7)

# Données des étapes du pipeline
etapes = [
    {'nom': 'Données\nbrutes', 'exemple': '/var/log/\naccess.log', 'x': 0.5, 'couleur': palette[0]},
    {'nom': 'cut', 'exemple': 'Extraire\nles colonnes', 'x': 2.5, 'couleur': palette[1]},
    {'nom': 'tr', 'exemple': 'Normaliser\nles caractères', 'x': 4.5, 'couleur': palette[2]},
    {'nom': 'grep / awk\n(filtre)', 'exemple': 'Sélectionner\nles lignes', 'x': 6.5, 'couleur': palette[3]},
    {'nom': 'sort', 'exemple': 'Trier\nles données', 'x': 8.5, 'couleur': palette[4]},
    {'nom': 'uniq -c', 'exemple': 'Compter\nles occurrences', 'x': 10.5, 'couleur': palette[5]},
    {'nom': 'Résultat', 'exemple': 'Rapport\nfinal', 'x': 12.5, 'couleur': palette[6]},
]

box_w = 1.7
box_h = 3.0
box_y = 2.0

for i, etape in enumerate(etapes):
    x = etape['x']
    c = etape['couleur']

    # Boîte principale
    boite = patches.FancyBboxPatch(
        (x - box_w / 2, box_y), box_w, box_h,
        boxstyle="round,pad=0.1", linewidth=2,
        edgecolor=c, facecolor=(*c[:3], 0.2)
    )
    ax.add_patch(boite)

    # Nom de l'outil
    ax.text(x, box_y + box_h - 0.5, etape['nom'],
            ha='center', va='center', fontsize=10, fontweight='bold',
            color=c, fontfamily='monospace')

    # Description
    ax.text(x, box_y + 0.9, etape['exemple'],
            ha='center', va='center', fontsize=8, color='#444444',
            style='italic')

    # Flèche vers l'étape suivante
    if i < len(etapes) - 1:
        next_x = etapes[i + 1]['x']
        ax.annotate('',
                    xy=(next_x - box_w / 2 - 0.05, box_y + box_h / 2),
                    xytext=(x + box_w / 2 + 0.05, box_y + box_h / 2),
                    arrowprops=dict(arrowstyle='->', color='#555555', lw=2.0))
        # Symbole pipe
        mx = (x + box_w / 2 + next_x - box_w / 2) / 2
        ax.text(mx, box_y + box_h / 2 + 0.25, '|', ha='center', va='center',
                fontsize=14, color='#888888', fontweight='bold')

# Légende en bas
ax.text(7.5, 0.5,
        'Chaque étape lit sur stdin et écrit sur stdout — '
        'les outils sont composables à l\'infini',
        ha='center', va='center', fontsize=10, color='#555555',
        style='italic',
        bbox=dict(boxstyle='round,pad=0.4', facecolor='#f8f8f8',
                  edgecolor='#cccccc', alpha=0.9))

plt.tight_layout()
plt.show()
```

```{code-cell} python
:tags: [hide-input]

# Visualisation comparative des outils : domaines d'application
fig, axes = plt.subplots(1, 2, figsize=(16, 6))

# --- Graphique 1 : Complexité vs Puissance ---
ax1 = axes[0]
outils = ['cut', 'tr', 'uniq', 'sort', 'sed', 'awk']
complexite = [1, 1.5, 2, 2.5, 4, 5]
puissance  = [2, 2.5, 3, 3.5, 4.5, 5]
tailles    = [200, 200, 250, 300, 350, 450]

colors = sns.color_palette("muted", len(outils))

scatter = ax1.scatter(complexite, puissance, s=tailles, c=colors,
                      alpha=0.8, edgecolors='white', linewidth=2, zorder=5)

for i, outil in enumerate(outils):
    ax1.annotate(outil, (complexite[i], puissance[i]),
                 textcoords="offset points", xytext=(10, 5),
                 fontsize=11, fontweight='bold', fontfamily='monospace',
                 color=colors[i])

ax1.set_xlabel('Complexité de la syntaxe', fontsize=12)
ax1.set_ylabel('Puissance / expressivité', fontsize=12)
ax1.set_title('Complexité vs Puissance des outils de filtrage', fontsize=13, fontweight='bold')
ax1.set_xlim(0, 6.5)
ax1.set_ylim(0, 6.5)

# --- Graphique 2 : Domaines d'application (radar simplifié en barres) ---
ax2 = axes[1]

categories = ['Extraction\nde colonnes', 'Tri', 'Déduplication',
              'Transformation\nde caractères', 'Substitution\nde texte',
              'Calculs\nnumériques']
outils_sel = ['cut', 'sort', 'uniq', 'tr', 'sed', 'awk']
scores_outils = {
    'cut':  [5, 0, 0, 0, 0, 0],
    'sort': [0, 5, 3, 0, 0, 0],
    'uniq': [0, 2, 5, 0, 0, 1],
    'tr':   [0, 0, 1, 5, 2, 0],
    'sed':  [1, 0, 1, 2, 5, 1],
    'awk':  [5, 2, 3, 2, 4, 5],
}

x_pos = np.arange(len(categories))
largeur = 0.13
colors2 = sns.color_palette("muted", len(outils_sel))

for i, (outil, c) in enumerate(zip(outils_sel, colors2)):
    offset = (i - len(outils_sel) / 2 + 0.5) * largeur
    bars = ax2.bar(x_pos + offset, scores_outils[outil], largeur,
                   label=outil, color=c, alpha=0.85, edgecolor='white')

ax2.set_xticks(x_pos)
ax2.set_xticklabels(categories, fontsize=9)
ax2.set_ylabel('Score de pertinence (0–5)', fontsize=11)
ax2.set_title('Domaines d\'application des outils de filtrage', fontsize=13, fontweight='bold')
ax2.legend(title='Outil', fontsize=9, title_fontsize=10)
ax2.set_ylim(0, 6)
ax2.yaxis.grid(True, alpha=0.5)

plt.tight_layout()
plt.show()
```

## Combiner les outils : exemples de pipelines réels

La véritable puissance de ces outils ne se révèle que lorsqu'ils sont combinés. Voici quelques pipelines représentatifs de cas d'usage réels.

### Analyser les logs système

```bash
# Compter les tentatives de connexion SSH échouées par IP
grep "Failed password" /var/log/auth.log \
    | awk '{print $(NF-3)}' \
    | sort \
    | uniq -c \
    | sort -rn \
    | head -20

# Afficher les 10 utilisateurs système les plus "lourds" (UID élevé)
cut -d':' -f1,3 /etc/passwd \
    | sort -t':' -k2,2rn \
    | head -10 \
    | tr ':' '\t'
```

### Traiter des fichiers CSV

```bash
# Statistiques sur un fichier CSV : compter les valeurs uniques par colonne
awk -F',' 'NR>1 {count[$2]++} END {for (k in count) print count[k], k}' data.csv \
    | sort -rn \
    | head -10

# Transformer un CSV en SQL INSERT
awk -F',' 'NR>1 {
    printf "INSERT INTO users (nom, prenom, age, ville) VALUES (\047%s\047, \047%s\047, %s, \047%s\047);\n",
           $1, $2, $3, $4
}' utilisateurs.csv
```

### Traitement de code source

```bash
# Compter les lignes de code par extension dans un projet
find . -type f | grep -v '.git' | sed 's/.*\.//' | sort | uniq -c | sort -rn

# Lister toutes les fonctions définies dans les fichiers Python
grep -r "^def \|^    def " *.py \
    | sed 's/.*def \([a-zA-Z_]*\)(.*/\1/' \
    | sort -u

# Compter le nombre d'imports par module dans un projet Python
grep -r "^import\|^from" *.py \
    | awk '{print $2}' \
    | cut -d'.' -f1 \
    | sort \
    | uniq -c \
    | sort -rn
```

```{prf:remark}
:label: remark-06-04
Ces six outils — `cut`, `sort`, `uniq`, `tr`, `sed` et `awk` — suivent tous la **philosophie Unix** : faire une seule chose et la faire bien. Chacun lit sur l'entrée standard et écrit sur la sortie standard, ce qui permet de les enchaîner librement avec des pipes (`|`). Cette composabilité est le fondement de la ligne de commande Unix : des briques simples assemblées en pipelines complexes, bien plus efficaces que des outils monolithiques qui tenteraient de tout faire seuls.

Une bonne pratique est de construire le pipeline **progressivement** : on commence par la première commande, on vérifie le résultat, on ajoute une étape, on vérifie à nouveau. Cette approche itérative évite les surprises et facilite le débogage.
```

## Résumé

Dans ce chapitre, nous avons exploré l'outillage fondamental de la manipulation de données textuelles en ligne de commande :

- **`cut`** extrait des colonnes de données structurées par `-f` (champs délimités) ou `-c` (positions de caractères). L'option `-d` définit le séparateur, la valeur par défaut étant la tabulation.
- **`sort`** trie les lignes avec un contrôle fin : `-n` pour le tri numérique, `-r` pour l'ordre inverse, `-k` pour choisir la colonne de tri, `-t` pour le séparateur, `-u` pour éliminer les doublons, `-h` pour les tailles humaines et `-V` pour les numéros de version.
- **`uniq`** opère sur des lignes consécutives : il doit donc presque toujours être précédé de `sort`. L'option `-c` compte les occurrences, `-d` isole les doublons, `-u` isole les uniques.
- **`tr`** transforme des caractères un à un : translitération sans option, suppression avec `-d`, compression des répétitions avec `-s`. Les classes POSIX (`[:alpha:]`, `[:upper:]`, etc.) garantissent la portabilité.
- **`sed`** est un éditeur de flux capable de substitutions (`s/motif/remplacement/g`), de suppressions de lignes (`d`), d'insertions (`i`, `a`), d'affichage sélectif (`-n`/`p`) et de modification en place (`-i`). Les adresses permettent de cibler précisément les lignes à traiter.
- **`awk`** est un langage complet : variables automatiques (`$1`…`$NF`, `NR`, `FS`, `OFS`), blocs `BEGIN`/`END`, conditions, boucles, tableaux associatifs et fonctions de chaînes. Il excelle pour les calculs agrégés, les jointures simples et les reformatages complexes.

Dans le chapitre suivant, nous étudierons les **redirections et les pipes** : les mécanismes qui permettent précisément de connecter ces outils entre eux et de maîtriser les trois flux fondamentaux — stdin, stdout et stderr.
