Transformer et filtrer#

Hide code cell source

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) :

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 :

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

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

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 :

# 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 :

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

Remarque 14

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 :

# 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.

# 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

Exemple 17 (Trouver les dix processus les plus gourmands en mémoire)

La combinaison de ps et sort permet d’identifier rapidement les processus qui consomment le plus de ressources :

# 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#

# 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

Remarque 15

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)#

# 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)#

# 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#

# 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 :

# 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#

# 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 :

# 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

Remarque 16

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#

# 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#

# 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 :

# 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 : Aho, Weinberger et Kernighan.

```{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 :

# 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#

# 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.) :

# 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 :

# 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#

# 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 :

# 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()

Hide code cell source

# 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()
_images/057cce5bca097ee49a013e08e270cd4c58cb3246aaf331bbf6d86d5e0cbe5563.png

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#

# 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#

# 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#

# 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

Remarque 17

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.