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

# Lire et chercher

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

## Lire des fichiers

Linux offre plusieurs outils pour lire le contenu des fichiers, adaptés à des contextes différents : affichage intégral, lecture paginée, consultation des premières ou dernières lignes, ou surveillance en temps réel.

### cat — concaténer et afficher

`cat` (*concatenate*) est le plus direct : il lit le contenu d'un ou plusieurs fichiers et l'écrit sur la sortie standard. Son nom vient de sa capacité à **concaténer** plusieurs fichiers.

```bash
# Afficher le contenu d'un fichier
cat fichier.txt

# Afficher plusieurs fichiers successivement
cat fichier1.txt fichier2.txt fichier3.txt

# Concaténer plusieurs fichiers dans un nouveau fichier
cat partie1.txt partie2.txt partie3.txt > complet.txt

# -n : numéroter toutes les lignes
cat -n fichier.txt

# -b : numéroter seulement les lignes non vides
cat -b fichier.txt

# -A : afficher les caractères non imprimables
# (^ pour les caractères de contrôle, $ en fin de ligne)
cat -A fichier.txt

# -s : compresser les lignes vides consécutives en une seule
cat -s fichier.txt

# Lecture depuis l'entrée standard (terminer avec Ctrl+D)
cat > nouveau_fichier.txt
# Tout ce que vous tapez sera écrit dans le fichier
# Ctrl+D pour terminer
```

```{prf:remark}
:label: remark-05-01
L'usage de `cat fichier | commande` est souvent appelé **UUOC** (*Useless Use Of Cat*) par les puristes Unix, car la plupart des commandes acceptent directement un nom de fichier : `grep motif fichier` est équivalent à `cat fichier | grep motif`. Cependant, `cat` reste utile pour la concaténation, la création de fichiers et la lisibilité dans les scripts complexes.
```

### less — lecture paginée

`less` est le pagineur de référence sous Linux. Contrairement à `cat`, il n'affiche pas tout le fichier d'un coup mais présente une page à la fois, permettant de naviguer librement dans le fichier. Son nom est un jeu de mots : *less is more* (allusion au pagineur `more` qu'il remplace avantageusement).

```bash
# Ouvrir un fichier avec less
less fichier.txt
less /var/log/syslog

# Navigation dans less :
# Espace ou f      : page suivante
# b                : page précédente
# Entrée ou ↓      : ligne suivante
# ↑                : ligne précédente
# g ou <           : aller au début du fichier
# G ou >           : aller à la fin du fichier
# /motif           : rechercher en avant
# ?motif           : rechercher en arrière
# n                : occurrence suivante
# N                : occurrence précédente
# q                : quitter

# Ouvrir plusieurs fichiers
less fichier1.txt fichier2.txt
# :n passe au fichier suivant, :p revient au précédent

# -N : afficher les numéros de ligne
less -N fichier.txt

# -i : recherche insensible à la casse
less -i fichier.txt

# -S : ne pas couper les longues lignes (défilement horizontal)
less -S fichier.csv

# +G : ouvrir directement à la fin du fichier
less +G /var/log/auth.log

# +/motif : ouvrir et chercher immédiatement
less +/ERROR /var/log/syslog
```

```{prf:definition} less vs more vs most
:label: definition-05-01
- **`more`** : pagineur historique (1978), simple. Il ne permet que d'avancer dans le fichier (pas de retour en arrière). Encore présent sur tous les systèmes pour la compatibilité.
- **`less`** : pagineur avancé (1983, Mark Nudelman), navigation bidirectionnelle, recherche, support des couleurs ANSI, plus rapide que `more` sur les grands fichiers (ne charge pas tout le fichier en mémoire). C'est le pagineur par défaut de `man`.
- **`most`** : pagineur alternatif avec défilement horizontal et fenêtres multiples. Moins courant mais utile pour les fichiers avec de longues lignes.
```

### head — afficher le début d'un fichier

```bash
# Afficher les 10 premières lignes (par défaut)
head fichier.txt

# -n : spécifier le nombre de lignes
head -n 20 fichier.txt
head -20 fichier.txt    # raccourci équivalent

# -c : afficher les N premiers octets
head -c 100 fichier.txt
head -c 1K fichier.txt
head -c 1M fichier.bin

# Afficher les N premières lignes de plusieurs fichiers
head -n 5 fichier1.txt fichier2.txt fichier3.txt

# Tout sauf les N dernières lignes (syntaxe GNU)
head -n -5 fichier.txt    # tout sauf les 5 dernières lignes
```

### tail — afficher la fin d'un fichier

`tail` est particulièrement précieux pour surveiller les fichiers de log en temps réel.

```bash
# Afficher les 10 dernières lignes (par défaut)
tail fichier.txt

# -n : spécifier le nombre de lignes
tail -n 20 fichier.txt
tail -20 fichier.txt

# -c : afficher les N derniers octets
tail -c 500 fichier.txt

# À partir de la N-ème ligne
tail -n +10 fichier.txt   # à partir de la ligne 10 (incluse)

# -f : suivre le fichier en temps réel (follow)
# S'arrête avec Ctrl+C
tail -f /var/log/nginx/access.log

# -F : comme -f, mais gère la rotation des logs
# (suit le fichier même si nginx renomme et recrée le fichier)
tail -F /var/log/nginx/access.log

# Suivre plusieurs fichiers simultanément
tail -f /var/log/nginx/access.log /var/log/nginx/error.log

# Combiner head et tail pour extraire des lignes spécifiques
# Afficher les lignes 15 à 25 d'un fichier
head -n 25 fichier.txt | tail -n 11
```

```{prf:example} Surveillance des logs en temps réel
:label: example-05-01

```bash
# Surveiller les connexions SSH
tail -F /var/log/auth.log | grep "sshd"

# Surveiller les erreurs nginx en temps réel
tail -F /var/log/nginx/error.log

# Surveiller un log et filtrer en temps réel avec grep
tail -f /var/log/syslog | grep -i "error\|warning\|critical"

# Surveiller plusieurs logs avec des couleurs différentes (multitail)
# sudo apt install multitail
multitail /var/log/nginx/access.log /var/log/nginx/error.log
```
```

### wc — compter

`wc` (*word count*) compte les lignes, mots et octets d'un fichier.

```bash
# Compter lignes, mots et octets
wc fichier.txt
# Sortie : "  142   1205  8342 fichier.txt"
#           lignes  mots  octets

# -l : compter uniquement les lignes
wc -l fichier.txt
wc -l /etc/passwd           # nombre d'utilisateurs

# -w : compter uniquement les mots
wc -w fichier.txt

# -c : compter uniquement les octets
wc -c fichier.txt

# -m : compter les caractères (utile avec UTF-8)
wc -m fichier.txt

# Compter plusieurs fichiers et afficher un total
wc -l fichier1.txt fichier2.txt fichier3.txt

# Compter les fichiers dans un répertoire
ls /etc | wc -l

# Compter les lignes de code Python dans un projet
find . -name "*.py" | xargs wc -l | tail -1
```

## grep — chercher dans le contenu

`grep` (*Global Regular Expression Print*) est l'outil de recherche textuelle le plus puissant et le plus utilisé de Linux. Il parcourt un ou plusieurs fichiers ligne par ligne et affiche les lignes qui correspondent à un motif de recherche.

```{prf:definition} grep
:label: definition-05-02
`grep` lit l'entrée ligne par ligne et affiche les lignes correspondant à un **motif** (*pattern*). Ce motif est par défaut une **expression régulière de base** (BRE — *Basic Regular Expression*). La syntaxe générale est :

```bash
grep [options] motif [fichier(s)]
```

Si aucun fichier n'est spécifié, `grep` lit depuis l'entrée standard, ce qui permet de l'utiliser dans des pipelines.
```

### Options essentielles de grep

```bash
# Recherche de base
grep "erreur" fichier.log

# -i : insensible à la casse
grep -i "erreur" fichier.log
grep -i "Error\|error\|ERREUR" fichier.log

# -r ou -R : récursif (parcourir les sous-répertoires)
grep -r "TODO" /home/alice/projets/
grep -R "mot_de_passe" /etc/

# -n : afficher le numéro de ligne
grep -n "def main" programme.py

# -l : afficher seulement les noms de fichiers qui contiennent le motif
grep -rl "import numpy" /home/alice/

# -L : afficher les noms de fichiers qui NE contiennent PAS le motif
grep -rL "encoding" /etc/*.conf

# -v : inverser la correspondance (lignes ne correspondant PAS)
grep -v "^#" /etc/nginx/nginx.conf    # exclure les commentaires
grep -v "^$" fichier.txt              # exclure les lignes vides

# -c : compter le nombre de lignes correspondantes
grep -c "ERROR" /var/log/syslog

# -E : utiliser les expressions régulières étendues (ERE)
grep -E "erreur|warning|critique" fichier.log

# -F : traiter le motif comme une chaîne fixe (pas de regex)
# Plus rapide quand on ne cherche pas de motif complexe
grep -F "http://exemple.com/page?id=42" access.log

# -o : afficher uniquement la partie correspondante (pas toute la ligne)
grep -o "[0-9]\{1,3\}\(\.[0-9]\{1,3\}\)\{3\}" access.log  # extraire les IPs

# -A n : afficher n lignes Après chaque correspondance
grep -A 3 "CRITICAL" /var/log/syslog

# -B n : afficher n lignes Avant chaque correspondance
grep -B 2 "CRITICAL" /var/log/syslog

# -C n : afficher n lignes de contexte (avant et après)
grep -C 5 "CRITICAL" /var/log/syslog

# --color=auto : colorier les correspondances (activé par défaut avec alias)
grep --color=auto "erreur" fichier.log

# -w : correspondance sur un mot entier (délimité par des séparateurs de mots)
grep -w "int" programme.c    # ne correspond pas à "integer" ou "point"

# -x : correspondance sur une ligne entière
grep -x "root" /etc/group

# -m n : s'arrêter après n correspondances
grep -m 5 "ERROR" /var/log/syslog

# Exclure des répertoires lors d'une recherche récursive
grep -r "motif" . --exclude-dir=".git" --exclude-dir="node_modules"

# Filtrer par extension de fichier
grep -r "motif" . --include="*.py"
grep -r "motif" . --include="*.{py,js,ts}"
```

```{prf:example} Pipelines grep courants
:label: example-05-02

```bash
# Trouver les processus qui consomment de la mémoire
ps aux | grep -v "^USER" | sort -k4 -rn | head -10

# Chercher les erreurs dans les logs des dernières 24h
find /var/log -name "*.log" -mtime -1 | xargs grep -l "ERROR"

# Extraire toutes les adresses email d'un fichier
grep -oE "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}" contacts.txt

# Compter les codes de statut HTTP dans les logs nginx
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn

# Trouver les fichiers Python contenant une classe spécifique
grep -rn "class MonModèle" /home/alice/projet/ --include="*.py"

# Vérifier si un service est actif dans les logs
sudo grep -c "started" /var/log/syslog | tail -1
```
```

## Expressions régulières

Les expressions régulières (*regular expressions* ou *regex*) sont un mini-langage pour décrire des motifs de texte. Elles sont utilisées par `grep`, `sed`, `awk`, Python, JavaScript et presque tous les langages de programmation modernes. Comprendre les regex est l'une des compétences les plus rentables en programmation.

### Expressions régulières de base (BRE)

Linux distingue les **BRE** (*Basic Regular Expressions*, utilisées par `grep` sans option) et les **ERE** (*Extended Regular Expressions*, utilisées par `grep -E` ou `egrep`).

```{prf:definition} Métacaractères des expressions régulières
:label: definition-05-03

**Ancres :**
- **`^`** : début de ligne. `^bonjour` correspond à une ligne commençant par « bonjour ».
- **`$`** : fin de ligne. `monde$` correspond à une ligne se terminant par « monde ».
- **`\b`** : limite de mot (*word boundary*). `\bfoo\b` correspond à « foo » mais pas à « foobar ».

**Correspondance de caractères :**
- **`.`** : n'importe quel caractère unique (sauf newline).
- **`[abc]`** : l'un des caractères a, b ou c (classe de caractères).
- **`[^abc]`** : tout caractère sauf a, b, c (classe négative).
- **`[a-z]`** : un caractère entre a et z (intervalle).
- **`[0-9]`** : un chiffre de 0 à 9.
- **`[a-zA-Z0-9_]`** : lettre, chiffre ou underscore.

**Quantificateurs :**
- **`*`** : zéro ou plusieurs répétitions du caractère/groupe précédent.
- **`+`** : une ou plusieurs répétitions (ERE uniquement, ou `\+` en BRE).
- **`?`** : zéro ou une répétition (ERE uniquement, ou `\?` en BRE).
- **`{n}`** : exactement n répétitions.
- **`{n,}`** : n ou plus de répétitions.
- **`{n,m}`** : entre n et m répétitions.

**Groupement et alternatives (ERE) :**
- **`(abc)`** : groupe. Permet d'appliquer un quantificateur à un groupe.
- **`a|b`** : alternative (a ou b).

**Classes POSIX :**
- **`[:alpha:]`** : lettres (`[a-zA-Z]`).
- **`[:digit:]`** : chiffres (`[0-9]`).
- **`[:alnum:]`** : lettres et chiffres.
- **`[:space:]`** : espaces, tabulations, newlines.
- **`[:upper:]`** : lettres majuscules.
- **`[:lower:]`** : lettres minuscules.
```

```{prf:example} Expressions régulières en pratique
:label: example-05-03

```bash
# Trouver les lignes vides (début = fin de ligne)
grep "^$" fichier.txt

# Trouver les lignes commençant par un commentaire
grep "^#" /etc/nginx/nginx.conf
grep "^[[:space:]]*#" fichier.py    # aussi celles indentées

# Trouver les lignes contenant une adresse IP (simplifiée)
grep -E "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" access.log

# Trouver les URLs http ou https
grep -E "https?://[^ ]+" fichier.txt

# Trouver les adresses email
grep -E "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}" fichier.txt

# Trouver les lignes avec un nombre de 3 à 5 chiffres
grep -E "\b[0-9]{3,5}\b" données.txt

# Trouver les fonctions Python (def suivi d'un nom)
grep -E "^def [a-zA-Z_][a-zA-Z0-9_]*\(" programme.py

# Trouver les imports Python
grep -E "^(import|from) " programme.py

# Trouver les lignes qui ne contiennent que des espaces
grep "^[[:space:]]*$" fichier.txt

# Mot au début de ligne, avec n'importe quelle fin
grep "^ERROR.*database" /var/log/app.log
```
```

### Variantes de grep

Linux fournit plusieurs variantes de `grep` :

- **`grep`** : utilise les BRE par défaut.
- **`grep -E`** ou **`egrep`** : utilise les ERE (*Extended*).
- **`grep -F`** ou **`fgrep`** : correspondance de chaîne fixe (pas de regex), très rapide.
- **`grep -P`** : utilise les PCRE (*Perl-Compatible Regular Expressions*), la syntaxe la plus riche (`\d`, `\w`, `\s`, lookaheads, etc.).

```bash
# PCRE : extraire les URLs avec lookbehind
grep -oP '(?<=href=")[^"]+' page.html

# PCRE : \d pour les chiffres, \w pour les caractères de mot
grep -P "\d{4}-\d{2}-\d{2}" fichier.txt    # dates YYYY-MM-DD
grep -P "\bimport\s+\w+" programme.py

# ERE : alternatives et groupes
grep -E "(ERROR|WARN|CRIT)" /var/log/syslog
grep -E "^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])" fichier.txt
```

## find avancé : combinaisons et actions

Nous avons introduit `find` au chapitre précédent. Explorons ici ses capacités combinatoires et ses actions avancées.

### Combinaisons logiques

```bash
# AND implicite (critères successifs)
find /var/log -name "*.log" -size +1M -mtime -7

# AND explicite
find /var/log -name "*.log" -and -size +1M

# OR
find /home -name "*.jpg" -or -name "*.png" -or -name "*.gif"
find /home \( -name "*.jpg" -o -name "*.png" \)

# NOT
find /etc -not -name "*.bak"
find /etc ! -name "*.bak"    # syntaxe alternative

# Combinaison complexe : fichiers Python modifiés récemment
# mais pas dans les répertoires __pycache__ ni .git
find . \( -name "*.py" -not -path "*/__pycache__/*" \
       -not -path "*/.git/*" \) -mtime -3
```

### Actions avancées

```bash
# -exec commande {} \; : exécuter une commande pour chaque fichier
find /tmp -mtime +7 -exec rm -v {} \;

# -exec commande {} + : passer tous les fichiers en un seul appel
find /tmp -mtime +7 -exec rm -v {} +

# -execdir : exécuter la commande dans le répertoire du fichier
# (plus sûr que -exec pour éviter les race conditions)
find /home -name "*.py" -execdir python3 -m py_compile {} \;

# -ok : comme -exec mais demande confirmation pour chaque fichier
find /home/alice -name "*.bak" -ok rm {} \;

# -print : afficher le chemin (action par défaut)
find /etc -name "*.conf" -print

# -print0 : afficher avec \0 comme séparateur (pour les noms avec espaces)
find /home -name "* *" -print0 | xargs -0 ls -l

# -printf : affichage formaté
find /var/log -name "*.log" -printf "%p\t%s\t%TY-%Tm-%Td\n"
# %p = chemin, %s = taille, %TY-%Tm-%Td = date année-mois-jour

# Combiner find et xargs (plus efficace que -exec pour de grandes listes)
find . -name "*.pyc" -print0 | xargs -0 rm
find . -name "*.py" -print0 | xargs -0 grep -l "deprecated"

# -delete : supprimer les fichiers trouvés (plus rapide que -exec rm)
find /tmp -mtime +30 -delete
find . -name "*.pyc" -delete
find . -name "__pycache__" -type d -exec rm -r {} +
```

```{prf:example} Cas d'usage find avancés
:label: example-05-04

```bash
# Trouver les fichiers dupliqués (même taille) — première étape d'un déduplicateur
find /home -type f -size +1M -printf "%s\t%p\n" | sort -n | \
  awk '{if($1==prev) print $2; prev=$1}'

# Trouver les fichiers de configuration qui ont été modifiés récemment
find /etc -name "*.conf" -newer /etc/os-release -type f

# Archiver les logs de plus de 30 jours
find /var/log -name "*.log" -mtime +30 \
  -exec gzip -v {} \;

# Corriger les permissions de tous les fichiers d'un projet web
find /var/www/html -type f -exec chmod 644 {} +
find /var/www/html -type d -exec chmod 755 {} +

# Trouver les scripts sans shebang
find /opt/scripts -name "*.sh" -type f | while read f; do
    head -1 "$f" | grep -q "^#!" || echo "Sans shebang : $f"
done

# Trouver et supprimer les fichiers temporaires d'un projet
find . \( -name "*.pyc" -o -name "*.pyo" -o -name "__pycache__" \
       -o -name ".DS_Store" -o -name "Thumbs.db" \) -delete
```
```

## locate et la base de données de fichiers

`find` est puissant mais peut être lent sur de grandes arborescences car il parcourt le système de fichiers en temps réel. **`locate`** est une alternative rapide qui interroge une **base de données pré-construite** de tous les fichiers du système.

```bash
# Installer locate (s'il n'est pas disponible)
sudo apt install plocate   # version moderne et rapide
# ou
sudo apt install mlocate   # version classique

# Mettre à jour la base de données (exécuté automatiquement par cron)
sudo updatedb

# Chercher un fichier par nom (insensible à la casse par défaut avec plocate)
locate nginx.conf

# -i : insensible à la casse (avec mlocate)
locate -i "*.PDF"

# -r : utiliser une regex
locate -r "\.conf$"

# -c : compter le nombre de résultats
locate -c "*.log"

# -l n : limiter le nombre de résultats
locate -l 10 "*.conf"

# Limites de locate vs find :
# - locate peut retourner des fichiers qui ont été supprimés depuis la dernière
#   mise à jour de la base de données
# - locate ne peut pas filtrer par permissions, taille ou date de modification
# - find est toujours à jour ; locate est rapide mais potentiellement périmé
```

## which, whereis et type

Ces trois commandes permettent de localiser les exécutables et d'obtenir des informations sur les commandes.

### which — trouver le chemin d'un exécutable

```bash
# Trouver où se trouve une commande dans le PATH
which ls              # /usr/bin/ls
which python3         # /usr/bin/python3
which bash            # /usr/bin/bash

# Afficher toutes les occurrences dans le PATH (pas seulement la première)
which -a python       # peut retourner /usr/bin/python et /usr/local/bin/python

# Vérifier si une commande est disponible dans un script
if which docker > /dev/null 2>&1; then
    echo "Docker est installé"
else
    echo "Docker n'est pas installé"
fi
```

### whereis — localiser binaire, sources et pages de manuel

```bash
# Localiser le binaire, les sources et la page de manuel d'une commande
whereis bash
# bash: /usr/bin/bash /etc/bash.bashrc /usr/share/doc/bash /usr/share/man/man1/bash.1.gz

# -b : seulement les binaires
whereis -b gcc

# -m : seulement les pages de manuel
whereis -m ls

# -s : seulement les sources
whereis -s bash
```

### type — identifier la nature d'une commande

`type` est une commande **intégrée** (*builtin*) de Bash particulièrement utile. Elle indique comment Bash interpréterait une commande donnée.

```bash
# Identifier le type d'une commande
type ls
# ls is /usr/bin/ls  (programme externe)

type cd
# cd is a shell builtin  (commande intégrée à Bash)

type ll
# ll is aliased to 'ls -alF --color=auto'  (alias)

type grep
# grep is /usr/bin/grep  (programme externe)

# -a : afficher toutes les définitions (commande externe, alias, fonction…)
type -a python
# python is aliased to 'python3'
# python is /usr/bin/python

# -t : afficher seulement le type (alias, keyword, function, builtin, file)
type -t ls        # file
type -t cd        # builtin
type -t if        # keyword
type -t ll        # alias
type -t mafonction # function (si définie)

# Vérifier si quelque chose est une fonction Bash
type -t mafonction
```

```{prf:remark}
:label: remark-05-02
La distinction entre **builtins** et **programmes externes** a des implications pratiques. Les builtins (`cd`, `echo`, `export`, `read`, `source`, `test`, `[`, etc.) sont exécutés directement par le shell sans créer un nouveau processus. Les programmes externes (`ls`, `grep`, `find`, `awk`) requièrent un `fork()` + `exec()` — deux appels système. Dans les scripts traitant de grandes quantités de données, privilégier les builtins ou les outils qui évitent de créer des processus inutiles peut améliorer significativement les performances.
```

## Schéma : pipeline grep avec regex

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

fig, axes = plt.subplots(1, 2, figsize=(18, 9))

# --- Graphique 1 : pipeline de traitement grep ---
ax = axes[0]
ax.set_xlim(-0.5, 14)
ax.set_ylim(-1, 10)
ax.axis('off')
ax.set_title('Pipeline grep : de la source au résultat filtré',
             fontsize=13, fontweight='bold')

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

def draw_pipeline_box(ax, x, y, w, h, color, title, subtitle=""):
    b = patches.FancyBboxPatch(
        (x, y), w, h, boxstyle="round,pad=0.15",
        facecolor=color, edgecolor=color, alpha=0.2, linewidth=2)
    ax.add_patch(b)
    border = patches.FancyBboxPatch(
        (x, y), w, h, boxstyle="round,pad=0.15",
        facecolor='none', edgecolor=color, linewidth=2)
    ax.add_patch(border)
    y_text = y + h / 2 + (0.15 if subtitle else 0)
    ax.text(x + w / 2, y_text, title, ha='center', va='center',
            fontsize=10, fontweight='bold', color=color)
    if subtitle:
        ax.text(x + w / 2, y - 0.25, subtitle,
                ha='center', va='top', fontsize=8,
                color='#555555', style='italic')

# Étapes du pipeline
steps = [
    # (x, y, w, h, couleur, titre, sous-titre)
    (0.2, 7.5, 3.2, 1.5, palette[0],
     "Source d'entrée", "fichier.log\n/var/log/syslog\npipeline |"),
    (0.2, 5.0, 3.2, 1.5, palette[1],
     "Lecture ligne à ligne", "chaque ligne\nest traitée\nséparément"),
    (0.2, 2.5, 3.2, 1.5, palette[2],
     "Évaluation du motif", "regex compilée\nappliquée\nà chaque ligne"),
    (0.2, 0.0, 3.2, 1.5, palette[3],
     "Sortie filtrée", "lignes\ncorrespondantes\nvers stdout"),
]

for (x, y, w, h, color, title, subtitle) in steps:
    draw_pipeline_box(ax, x, y, w, h, color, title, subtitle)

# Flèches entre étapes
for i in range(len(steps) - 1):
    _, y_top, _, h, _, _, _ = steps[i]
    _, y_bot, _, _, _, _, _ = steps[i + 1]
    mid_x = steps[i][0] + steps[i][2] / 2
    ax.annotate('', xy=(mid_x, y_bot + steps[i+1][3]),
                xytext=(mid_x, y_top),
                arrowprops=dict(arrowstyle='->', color='#444444', lw=2))

# Options de grep sur le côté
options_data = [
    (5.0, 8.0, palette[0], "-r / -R", "récursif"),
    (5.0, 7.0, palette[1], "-i", "insensible casse"),
    (5.0, 6.0, palette[2], "-n", "numéro de ligne"),
    (5.0, 5.0, palette[3], "-l / -L", "noms de fichiers"),
    (5.0, 4.0, palette[4], "-v", "inverser"),
    (5.0, 3.0, palette[5], "-E", "regex étendue"),
    (5.0, 2.0, palette[6], "-c", "compter"),
    (5.0, 1.0, palette[7], "-o", "only matching"),
    (5.0, 0.0, palette[8], "-A/-B/-C", "contexte"),
]

ax.text(8.5, 9.2, "Options grep", ha='center', va='center',
        fontsize=11, fontweight='bold', color='#333333')

for (x, y, color, opt, desc) in options_data:
    b = patches.FancyBboxPatch(
        (x, y), 5.5, 0.75, boxstyle="round,pad=0.1",
        facecolor=color, edgecolor=color, alpha=0.2, linewidth=1.5)
    ax.add_patch(b)
    ax.text(x + 1.5, y + 0.375, opt, ha='center', va='center',
            fontsize=9, fontweight='bold', color=color,
            fontfamily='monospace')
    ax.text(x + 3.5, y + 0.375, desc, ha='center', va='center',
            fontsize=8.5, color='#444444')

# --- Graphique 2 : exemples de regex visuels ---
ax2 = axes[1]
ax2.set_xlim(-0.5, 13)
ax2.set_ylim(-0.5, 10.5)
ax2.axis('off')
ax2.set_title('Anatomie des expressions régulières',
              fontsize=13, fontweight='bold')

regex_examples = [
    # (motif, explication, exemple_match, couleur)
    ("^ERROR",
     "Lignes commençant par ERROR",
     "ERROR: connexion refusée", palette[0]),
    ("[0-9]{1,3}",
     "Séquence de 1 à 3 chiffres",
     "IP: 192.168.1.10", palette[1]),
    ("https?://",
     "http:// ou https://",
     "https://exemple.com", palette[2]),
    ("\\.conf$",
     "Fichiers se terminant par .conf",
     "nginx.conf", palette[3]),
    ("[a-zA-Z]+@[a-z]+\\.[a-z]{2,}",
     "Adresse email simplifiée",
     "alice@exemple.fr", palette[4]),
    ("(ERROR|WARN|CRIT)",
     "L'un de ces trois mots",
     "WARN: espace disque faible", palette[5]),
    ("^[[:space:]]*$",
     "Lignes vides ou avec espaces",
     "(ligne blanche)", palette[6]),
    ("\\bimport\\b",
     "Mot 'import' (pas 'reimport')",
     "import numpy as np", palette[7]),
]

for i, (motif, expl, exemple, color) in enumerate(regex_examples):
    y = 9.5 - i * 1.2

    # Fond de la ligne
    bg = patches.FancyBboxPatch(
        (-0.2, y - 0.55), 13, 1.0,
        boxstyle="round,pad=0.1",
        facecolor=color, edgecolor=color, alpha=0.08, linewidth=1)
    ax2.add_patch(bg)

    # Motif (monospace)
    ax2.text(0.0, y, motif, ha='left', va='center',
             fontsize=9, fontweight='bold', color=color,
             fontfamily='monospace',
             bbox=dict(boxstyle='round,pad=0.2', facecolor=color,
                       alpha=0.15, edgecolor=color, linewidth=1.2))

    # Explication
    ax2.text(4.5, y + 0.2, expl, ha='left', va='center',
             fontsize=8.5, color='#444444')

    # Exemple de correspondance
    ax2.text(4.5, y - 0.2, f"ex : {exemple}", ha='left', va='center',
             fontsize=8, color='#666666', style='italic')

ax2.text(0.0, 10.2, "Motif", ha='left', fontsize=10,
         fontweight='bold', color='#333333')
ax2.text(4.5, 10.2, "Signification / Exemple", ha='left', fontsize=10,
         fontweight='bold', color='#333333')
ax2.axhline(9.9, xmin=0, xmax=1, color='#cccccc', lw=1.5)

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

## Résumé

Ce chapitre a couvert les outils essentiels pour lire et chercher du contenu sous Linux :

- **Lecture de fichiers** : `cat` pour afficher rapidement, `less` pour naviguer de façon interactive, `head`/`tail` pour les débuts/fins de fichiers, `tail -f` pour surveiller un fichier en temps réel, `wc` pour compter lignes, mots et octets.
- **`grep`** est l'outil central de recherche textuelle. Ses options essentielles sont `-i` (casse), `-r` (récursif), `-n` (numéros de ligne), `-l`/`-L` (noms de fichiers), `-v` (inversion), `-E` (regex étendue), `-c` (comptage), `-A`/`-B`/`-C` (contexte), `-o` (portion correspondante), `-P` (PCRE).
- Les **expressions régulières** (BRE, ERE, PCRE) permettent de décrire des motifs complexes : ancres (`^`, `$`, `\b`), métacaractère universel (`.`), classes de caractères (`[a-z]`, `[:digit:]`), quantificateurs (`*`, `+`, `?`, `{n,m}`), alternatives (`|`), groupes (`()`).
- **`find` avancé** : combinaisons logiques (`-and`, `-or`, `-not`), actions (`-exec`, `-execdir`, `-delete`, `-printf`), intégration avec `xargs` pour traiter de grandes listes de fichiers efficacement.
- **`locate`** / **`plocate`** offrent une recherche rapide via une base de données pré-indexée, complémentaire à `find` pour les recherches par nom.
- **`which`** localise un exécutable dans le PATH, **`whereis`** trouve binaire, sources et manuel, **`type`** révèle la nature d'une commande (builtin, alias, fonction, programme externe).

Dans le prochain chapitre, nous aborderons les **redirections et les pipes** — les mécanismes qui permettent de composer des pipelines de traitement puissants en connectant les entrées et sorties des commandes.
