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

# Redirections et pipes

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

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

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

La puissance du shell Unix repose sur un concept remarquablement simple : les programmes ne connaissent pas la source de leurs données ni la destination de leurs résultats. Ils lisent sur un canal appelé *entrée standard* et écrivent sur deux canaux appelés *sortie standard* et *sortie d'erreur*. Le shell peut **rediriger** ces canaux vers des fichiers ou les **connecter** entre programmes à l'aide de pipes. Ce mécanisme, conçu dans les années 1970 par Douglas McIlroy aux Bell Labs, reste aujourd'hui l'un des fondements les plus élégants et les plus puissants de l'informatique en ligne de commande.

## Les trois flux standard

Chaque processus Unix hérite à sa création de trois **descripteurs de fichiers** ouverts, numérotés de 0 à 2 :

```{prf:definition} Les trois flux standard
:label: definition-07-01
Les trois flux standard sont des canaux de communication prédéfinis associés à tout processus :

- **stdin** (descripteur **0**) — *Standard Input* : le canal par lequel le processus lit ses données. Par défaut, il est connecté au clavier. Toute commande qui « attend » que vous tapiez quelque chose lit sur stdin.
- **stdout** (descripteur **1**) — *Standard Output* : le canal sur lequel le processus écrit ses résultats. Par défaut, il est connecté au terminal. C'est ce que l'on voit normalement s'afficher.
- **stderr** (descripteur **2**) — *Standard Error* : le canal réservé aux messages d'erreur et de diagnostic. Par défaut, lui aussi connecté au terminal, mais **indépendant de stdout**. Cette séparation est fondamentale : elle permet de rediriger les erreurs séparément des résultats.
```

Cette séparation entre stdout et stderr est un choix de conception délibéré. Elle permet par exemple d'écrire les résultats dans un fichier tout en voyant les erreurs s'afficher dans le terminal, ou à l'inverse de rediriger les erreurs dans un fichier de log sans polluer la sortie principale.

```{prf:remark}
:label: remark-07-01
Le système de descripteurs de fichiers est bien plus général : chaque processus peut ouvrir jusqu'à des milliers de descripteurs supplémentaires (3, 4, 5…) pour lire ou écrire des fichiers, des sockets réseau, des pipes, etc. Les redirections du shell manipulent ces descripteurs : elles les ferment, les rouvrent sur d'autres fichiers ou les font pointer vers d'autres descripteurs. La commande `ls -la /proc/$$/fd` permet de lister les descripteurs ouverts du shell courant.
```

## Les redirections de base

### Redirection de stdout (`>` et `>>`)

L'opérateur `>` redirige la sortie standard vers un fichier. Si le fichier existe, il est **écrasé** ; s'il n'existe pas, il est créé :

```bash
# Écrire la liste des fichiers dans un fichier
ls -la > liste_fichiers.txt

# Créer un fichier vide (ou vider un fichier existant)
> fichier_vide.txt

# Écrire un message dans un fichier
echo "Bonjour le monde" > message.txt
```

L'opérateur `>>` **ajoute** (append) à la fin du fichier sans l'écraser :

```bash
# Ajouter une entrée à un fichier de log
echo "$(date): Démarrage du service" >> /var/log/mon_service.log

# Accumuler des résultats
echo "Résultats du test 1 :" >> rapport.txt
./test1.sh >> rapport.txt
echo "Résultats du test 2 :" >> rapport.txt
./test2.sh >> rapport.txt
```

### Redirection de stdin (`<`)

L'opérateur `<` connecte le fichier spécifié à l'entrée standard de la commande :

```bash
# Trier le contenu d'un fichier
sort < données.txt

# Équivalent (les deux formes sont équivalentes)
sort données.txt
cat données.txt | sort

# Envoyer un fichier comme corps d'un email
mail -s "Rapport quotidien" destinataire@example.com < rapport.txt

# Alimenter une commande interactive avec des données préparées
mysql -u root -p ma_base < script.sql
```

### Redirection de stderr (`2>`, `2>>`)

```bash
# Rediriger les erreurs vers un fichier
ls /repertoire_inexistant 2> erreurs.txt

# Ajouter les erreurs à un fichier de log existant
find / -name "*.conf" 2>> erreurs_find.log

# Supprimer les messages d'erreur (les envoyer dans le néant)
find / -name "*.conf" 2> /dev/null

# Afficher les erreurs mais pas les résultats normaux
find / -name "*.conf" 1>/dev/null
```

### Rediriger stderr vers stdout (`2>&1`)

La syntaxe `2>&1` signifie : « connecte le descripteur 2 (stderr) au même endroit que le descripteur 1 (stdout) ». L'ordre des redirections est crucial :

```bash
# Capturer stdout et stderr dans le même fichier
commande > sortie_totale.txt 2>&1

# ORDRE IMPORTANT : cette commande est différente
# Ici, stderr va vers le terminal (l'ancien stdout), stdout va dans le fichier
commande 2>&1 > fichier.txt   # INCORRECT pour capturer les deux

# La forme abrégée &> (bash uniquement) redirige les deux simultanément
commande &> sortie_totale.txt

# Ajouter stdout et stderr à un fichier
commande >> journal.txt 2>&1
```

```{prf:remark}
:label: remark-07-02
La syntaxe `2>&1` se lit de droite à gauche : « le descripteur 2 prend comme cible ce que pointe actuellement le descripteur 1 ». C'est pourquoi l'ordre importe : dans `> fichier 2>&1`, au moment où `2>&1` est évalué, le descripteur 1 pointe déjà vers `fichier`, donc stderr ira aussi dans `fichier`. Dans `2>&1 > fichier`, au moment où `2>&1` est évalué, le descripteur 1 pointe encore vers le terminal, donc stderr ira vers le terminal.
```

### Le périphérique `/dev/null`

`/dev/null` est un fichier spécial qui se comporte comme un puits sans fond : tout ce qui y est écrit est silencieusement discardé, et toute lecture sur `/dev/null` retourne immédiatement EOF.

```bash
# Supprimer complètement la sortie d'une commande
commande > /dev/null 2>&1

# Lancer une commande en silence total
./script_bruyant.sh &>/dev/null

# Vérifier si une commande réussit sans afficher de sortie
if grep -q "motif" fichier.txt 2>/dev/null; then
    echo "Motif trouvé"
fi
```

## Les pipes : connecter les commandes

Le pipe `|` est l'opérateur qui connecte la sortie standard d'une commande à l'entrée standard de la suivante. C'est le mécanisme fondamental de la composition de commandes Unix.

```{prf:definition} Pipe (tube)
:label: definition-07-02
Un **pipe** est un canal de communication unidirectionnel entre deux processus. Dans le shell, l'opérateur `|` crée un pipe et :

1. **Lance les deux commandes simultanément** (pas l'une après l'autre).
2. Connecte le stdout de la commande gauche au stdin de la commande droite.
3. Chaque commande s'exécute dans un **sous-shell** séparé.
4. Le pipe se ferme automatiquement quand la commande productrice termine (envoi de SIGPIPE à la commande consommatrice si elle tente d'écrire après la fermeture du pipe).
```

### Construction de pipelines

```bash
# Pipeline simple : lister et trier
ls -la | sort -k5,5rn

# Pipeline en trois étapes
cat /etc/passwd | cut -d':' -f1 | sort

# Les 10 fichiers les plus récemment modifiés
find . -type f -printf "%T@ %p\n" | sort -rn | head -10 | cut -d' ' -f2-

# Compter le nombre de connexions par état TCP
ss -tan | awk 'NR>1 {print $1}' | sort | uniq -c | sort -rn

# Analyser l'utilisation des commandes dans l'historique
history | awk '{print $2}' | sort | uniq -c | sort -rn | head -20
```

### Sous-shells et variables

Un aspect important des pipes est que chaque commande d'un pipeline s'exécute dans un sous-shell. Cela a des implications sur les variables :

```bash
# Ce code ne fonctionne PAS comme attendu
total=0
cat nombres.txt | while read n; do
    total=$((total + n))
done
echo "Total : $total"   # Affiche 0 ! La variable a été modifiée dans un sous-shell.

# Solution avec redirection (pas de sous-shell pour while)
total=0
while read n; do
    total=$((total + n))
done < nombres.txt
echo "Total : $total"   # Fonctionne correctement

# Solution avec lastpipe (bash 4.2+)
set +m  # Désactiver le job control
shopt -s lastpipe
total=0
cat nombres.txt | while read n; do
    total=$((total + n))
done
echo "Total : $total"   # Fonctionne avec lastpipe
```

### SIGPIPE et gestion des erreurs

Quand la commande de droite d'un pipe arrête de lire (par exemple `head -5` après avoir lu 5 lignes), le noyau envoie le signal `SIGPIPE` à la commande de gauche pour lui signaler qu'elle peut s'arrêter. Ce comportement est généralement transparent, mais il peut générer des messages d'erreur avec certaines commandes :

```bash
# yes génère "y" en boucle infinie, head l'interrompt proprement
yes | head -5

# Certaines commandes affichent un avertissement à la réception de SIGPIPE
# On peut ignorer le code de retour avec || true
quelque_chose | head -1 || true
```

## Substitution de processus

La substitution de processus est une fonctionnalité avancée de Bash qui permet d'utiliser la sortie d'une commande comme si c'était un fichier.

```{prf:definition} Substitution de processus
:label: definition-07-03
La **substitution de processus** prend deux formes :

- **`<(commande)`** : exécute la commande et rend sa sortie disponible comme un fichier en lecture. Bash crée un fichier spécial dans `/dev/fd/` (ou un tube nommé selon le système) que la commande cible peut ouvrir comme un fichier ordinaire.
- **`>(commande)`** : de manière symétrique, crée un fichier en écriture dont le contenu est envoyé vers stdin de la commande spécifiée.

Cette fonctionnalité est particulièrement utile quand une commande attend des **arguments de type fichier** et ne peut pas lire sur stdin.
```

### Comparer des sorties de commandes

```bash
# Comparer le contenu de deux répertoires
diff <(ls répertoire1/) <(ls répertoire2/)

# Comparer deux fichiers triés sans créer de fichiers temporaires
diff <(sort fichier1.txt) <(sort fichier2.txt)

# Vérifier que deux commandes produisent le même résultat
diff <(commande1) <(commande2) && echo "Identiques" || echo "Différents"
```

### Alimenter des commandes nécessitant des fichiers

```bash
# wc -l peut prendre plusieurs fichiers et afficher les totaux par fichier
wc -l <(grep "ERREUR" app.log) <(grep "ERREUR" access.log)

# join nécessite des fichiers (et non stdin) pour les deux entrées
join <(sort fichier1.csv) <(sort fichier2.csv)

# Comparer la sortie de deux branches Git
diff <(git show branche1:fichier.py) <(git show branche2:fichier.py)
```

### Substitution de processus en écriture

```bash
# Écrire simultanément dans deux commandes
commande | tee >(gzip > sortie.gz) >(wc -l > compte.txt) > /dev/null

# Journaliser et afficher simultanément
./script.sh > >(tee -a journal.log) 2> >(tee -a erreurs.log >&2)
```

## La commande `xargs` — construire des arguments

La commande `xargs` lit des données sur stdin et les transforme en arguments de la commande spécifiée. C'est essentiel car beaucoup de commandes ne lisent pas sur stdin mais attendent des arguments.

```{prf:definition} Fonctionnement de `xargs`
:label: definition-07-04
`xargs` lit les éléments de stdin (séparés par défaut par des espaces et des retours à la ligne) et les passe en argument à la commande cible. Sans options, `xargs` passe le maximum d'arguments possible en une seule invocation de la commande.

Options importantes :
- **`-I {}`** : substitution. Chaque occurrence de `{}` dans la commande est remplacée par l'élément lu sur stdin. La commande est invoquée une fois par élément.
- **`-n N`** : passe au maximum N arguments par invocation de la commande.
- **`-P N`** (parallel) : exécute jusqu'à N processus en parallèle.
- **`-0`** (null) : utilise le caractère nul (`\0`) comme séparateur. À utiliser avec `find -print0` pour gérer les noms de fichiers contenant des espaces.
- **`-t`** (trace) : affiche chaque commande avant de l'exécuter.
- **`-r`** (no-run-if-empty) : ne rien faire si stdin est vide.
```

### Usages courants de `xargs`

```bash
# Supprimer tous les fichiers .tmp trouvés
find . -name "*.tmp" | xargs rm -f

# Version sûre pour les noms de fichiers avec espaces
find . -name "*.tmp" -print0 | xargs -0 rm -f

# Compter les lignes de tous les fichiers Python du projet
find . -name "*.py" | xargs wc -l

# Convertir des images en parallèle (4 processus simultanés)
find . -name "*.png" | xargs -P4 -I{} convert {} -quality 85 {}.jpg

# Télécharger une liste d'URLs
cat urls.txt | xargs -n1 -P4 wget -q

# Passer des arguments à une commande complexe
echo "alice bob carol" | xargs -n1 | xargs -I{} bash -c 'echo "Bonjour, {}!"'
```

### `xargs` avec des commandes complexes

```bash
# Rechercher un motif dans tous les fichiers d'une liste
cat fichiers_a_analyser.txt | xargs grep -l "motif_important"

# Créer des répertoires à partir d'une liste
cat liste_projets.txt | xargs -I{} mkdir -p projets/{}

# Archiver des fichiers modifiés récemment
find . -newer référence.txt -type f | xargs tar czf archive.tar.gz

# Tester des URLs en parallèle et afficher le code HTTP
cat urls.txt | xargs -P10 -I{} curl -s -o /dev/null -w "{}: %{http_code}\n" {}
```

## La commande `tee` — dupliquer le flux

La commande `tee` lit sur stdin et écrit simultanément sur stdout **et** dans un ou plusieurs fichiers. Son nom fait référence au raccord en T de la plomberie.

```bash
# Afficher la sortie ET l'enregistrer dans un fichier
commande | tee journal.txt

# Ajouter à un fichier existant (-a pour append)
commande | tee -a journal.txt

# Écrire dans plusieurs fichiers simultanément
commande | tee fichier1.txt fichier2.txt

# Utilisation classique : voir la sortie ET continuer le pipeline
find . -name "*.py" | tee liste_py.txt | wc -l
# Affiche le nombre de fichiers ET enregistre la liste dans liste_py.txt

# Journaliser stdout et stderr séparément tout en continuant le pipeline
./script.sh 2> >(tee erreurs.log >&2) | tee sortie.log | analyse_résultats
```

## Here-documents et here-strings

Les here-documents et here-strings sont des mécanismes permettant de fournir des données directement dans le code shell, sans fichier externe.

```{prf:definition} Here-document (`<<EOF`)
:label: definition-07-05
Un **here-document** est une forme de redirection stdin qui permet d'écrire du texte multi-lignes directement dans le script. La syntaxe est :

```
commande <<MARQUEUR
ligne 1
ligne 2
...
MARQUEUR
```

Le texte entre les deux occurrences du marqueur est envoyé sur stdin de la commande. **L'interpolation des variables et des substitutions de commandes** est active par défaut. Pour la désactiver et traiter le texte littéralement, on place le marqueur entre guillemets simples ou on l'échappe : `<<'EOF'` ou `<<\EOF`.
```

### Here-documents en pratique

```bash
# Écrire un fichier de configuration multi-lignes
cat > /etc/mon_service.conf <<EOF
# Configuration générée le $(date)
serveur=localhost
port=8080
utilisateur=$USER
mode=production
EOF

# Envoyer un email avec contenu HTML
mail -s "Rapport" -a "Content-Type: text/html" destinataire@example.com <<EOF
<h1>Rapport du $(date +%Y-%m-%d)</h1>
<p>Bonjour,</p>
<p>Veuillez trouver ci-joint le rapport quotidien.</p>
EOF

# Exécuter plusieurs commandes MySQL
mysql -u root -p ma_base <<'EOF'
SELECT COUNT(*) FROM utilisateurs;
SELECT nom, email FROM utilisateurs WHERE actif = 1;
UPDATE logs SET traité = TRUE WHERE date < NOW() - INTERVAL 30 DAY;
EOF

# Here-document avec indentation (<<- supprime les tabulations initiales)
if condition; then
    cat <<-EOF
        Ce texte est indenté dans le script
        mais s'affiche sans les tabulations initiales
        (seules les tabulations, pas les espaces, sont supprimées)
    EOF
fi
```

### Here-strings (`<<<`)

```bash
# Envoyer une chaîne sur stdin d'une commande
grep "motif" <<< "texte à analyser contenant le motif"

# Équivalent de echo "texte" | commande, mais sans sous-shell
read prénom nom <<< "Alice Dupont"
echo "$prénom"  # Alice
echo "$nom"     # Dupont

# Tester une expression régulière
if grep -qE "^[0-9]+$" <<< "$variable"; then
    echo "$variable est un entier"
fi

# Passer une chaîne à bc pour calcul
résultat=$(bc <<< "scale=4; 355/113")
echo "π ≈ $résultat"
```

```{prf:remark}
:label: remark-07-03
La here-string `<<<` a un avantage sur `echo "..." | commande` : elle ne crée pas de sous-shell. Cela signifie que les variables modifiées à l'intérieur d'une structure lisant depuis une here-string sont visibles dans le shell parent. De plus, avec la here-string, la commande cible peut utiliser un pseudo-fichier au lieu d'un vrai pipe, ce qui peut être plus efficace pour les petites données.
```

## Redirections avancées

### Ouvrir et fermer des descripteurs de fichiers

Le shell permet de manipuler directement les descripteurs de fichiers, ce qui est utile dans les scripts avancés :

```bash
# Ouvrir le descripteur 3 en lecture sur un fichier
exec 3< données.txt
# Lire une ligne depuis le descripteur 3
read -u 3 ligne
# Fermer le descripteur 3
exec 3<&-

# Ouvrir le descripteur 4 en écriture
exec 4> journal.txt
# Écrire sur le descripteur 4
echo "Entrée de journal" >&4
# Fermer le descripteur 4
exec 4>&-

# Sauvegarder et restaurer stdout
exec 5>&1          # Sauvegarder stdout dans le descripteur 5
exec > sortie.txt  # Rediriger stdout vers un fichier
echo "Ceci va dans le fichier"
exec 1>&5          # Restaurer stdout depuis le descripteur 5
exec 5>&-          # Fermer le descripteur 5
echo "Ceci s'affiche à nouveau dans le terminal"
```

### Redirections dans les blocs

```bash
# Rediriger la sortie de tout un bloc
{
    echo "En-tête du rapport"
    date
    df -h
    free -h
} > rapport_système.txt

# Rediriger l'entrée d'un bloc
while read ligne; do
    echo "Lu : $ligne"
done < données.txt

# Redirections dans les fonctions
générer_rapport() {
    echo "=== Rapport ==="
    echo "Date : $(date)"
    echo "Utilisateur : $USER"
} 2>&1 | tee rapport.txt
```

## Visualisation : les flux stdin/stdout/stderr

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

fig, axes = plt.subplots(1, 2, figsize=(16, 8))

palette = sns.color_palette("muted", 8)
couleur_stdin  = palette[0]   # bleu
couleur_stdout = palette[2]   # vert
couleur_stderr = palette[3]   # orange/rouge
couleur_prog   = palette[1]   # indigo
couleur_fichier = palette[5]  # violet
couleur_null   = '#aaaaaa'

# ============================================================
# Schéma 1 : Les trois flux sans redirection
# ============================================================
ax = axes[0]
ax.set_xlim(-1, 11)
ax.set_ylim(-1, 9)
ax.axis('off')
ax.set_title('Les trois flux standard (sans redirection)',
             fontsize=13, fontweight='bold')

def boite(ax, x, y, w, h, couleur, texte, sous_texte=None, alpha=0.25):
    rect = patches.FancyBboxPatch(
        (x - w/2, y - h/2), w, h,
        boxstyle="round,pad=0.15", linewidth=2,
        edgecolor=couleur, facecolor=(*couleur[:3], alpha)
    )
    ax.add_patch(rect)
    ax.text(x, y + (0.3 if sous_texte else 0), texte,
            ha='center', va='center', fontsize=11, fontweight='bold',
            color=couleur)
    if sous_texte:
        ax.text(x, y - 0.45, sous_texte,
                ha='center', va='center', fontsize=8.5, color='#555555',
                style='italic')

def fleche(ax, x1, y1, x2, y2, couleur, label=None, style='->', lw=2.5):
    ax.annotate('', xy=(x2, y2), xytext=(x1, y1),
                arrowprops=dict(arrowstyle=style, color=couleur, lw=lw))
    if label:
        mx, my = (x1+x2)/2, (y1+y2)/2
        ax.text(mx + 0.1, my + 0.25, label, ha='left', va='center',
                fontsize=9, fontweight='bold', color=couleur,
                fontfamily='monospace')

# Clavier
boite(ax, 2, 7, 2.5, 1.2, couleur_stdin, 'Clavier', 'source stdin')
# Terminal (stdout)
boite(ax, 8, 7, 2.5, 1.2, couleur_stdout, 'Terminal', 'sortie stdout')
# Terminal (stderr)
boite(ax, 8, 2, 2.5, 1.2, couleur_stderr, 'Terminal', 'sortie stderr')
# Programme
boite(ax, 5, 4.5, 2.8, 1.6, couleur_prog, 'Programme', 'processus', alpha=0.35)
ax.text(5, 4.1, 'pid = $$', ha='center', va='center', fontsize=8,
        fontfamily='monospace', color=palette[1], style='italic')

# Descripteurs
for desc, x_desc, y_desc, couleur_d, nom in [
    ('fd 0', 3.8, 6.0, couleur_stdin, 'stdin'),
    ('fd 1', 6.5, 6.0, couleur_stdout, 'stdout'),
    ('fd 2', 6.5, 3.3, couleur_stderr, 'stderr'),
]:
    ax.text(x_desc, y_desc, f'{desc}\n({nom})', ha='center', va='center',
            fontsize=8.5, fontweight='bold', color=couleur_d,
            fontfamily='monospace',
            bbox=dict(boxstyle='round,pad=0.2', facecolor='white',
                      edgecolor=couleur_d, alpha=0.8))

# Flèches
fleche(ax, 2, 6.38, 3.5, 5.3, couleur_stdin, '0')
fleche(ax, 6.5, 5.3, 7.5, 6.38, couleur_stdout, '1')
fleche(ax, 6.5, 3.8, 7.5, 2.62, couleur_stderr, '2')

# Légende des numéros
for num, y_l, couleur_l, nom_l in [(0, 1.2, couleur_stdin, 'stdin'),
                                     (1, 0.7, couleur_stdout, 'stdout'),
                                     (2, 0.2, couleur_stderr, 'stderr')]:
    ax.add_patch(patches.Circle((1.0, y_l), 0.2, color=couleur_l, zorder=5))
    ax.text(1.0, y_l, str(num), ha='center', va='center', fontsize=9,
            fontweight='bold', color='white', zorder=6)
    ax.text(1.4, y_l, nom_l, ha='left', va='center', fontsize=9,
            color=couleur_l, fontweight='bold')

# ============================================================
# Schéma 2 : Les redirections courantes
# ============================================================
ax2 = axes[1]
ax2.set_xlim(-0.5, 11)
ax2.set_ylim(-0.5, 9)
ax2.axis('off')
ax2.set_title('Redirections courantes', fontsize=13, fontweight='bold')

redirections = [
    ('cmd > fichier',   '>',    'stdout → fichier (écrase)',    '#27ae60'),
    ('cmd >> fichier',  '>>',   'stdout → fichier (ajoute)',    '#2ecc71'),
    ('cmd < fichier',   '<',    'fichier → stdin',              '#2980b9'),
    ('cmd 2> fichier',  '2>',   'stderr → fichier',             '#e67e22'),
    ('cmd 2>&1',        '2>&1', 'stderr → même cible que stdout','#e74c3c'),
    ('cmd &> fichier',  '&>',   'stdout+stderr → fichier',      '#8e44ad'),
    ('cmd 2>/dev/null', '2>…',  'stderr → /dev/null (silence)', '#95a5a6'),
    ('cmd | cmd2',      '|',    'stdout → stdin de cmd2',       '#16a085'),
]

for i, (syntaxe, opérateur, description, couleur) in enumerate(redirections):
    y = 8.2 - i * 1.0
    # Fond de la ligne
    fond = patches.FancyBboxPatch(
        (0.1, y - 0.35), 10.7, 0.72,
        boxstyle="round,pad=0.05", linewidth=1.5,
        edgecolor=couleur, facecolor=couleur, alpha=0.10
    )
    ax2.add_patch(fond)
    # Opérateur en monospace
    ax2.text(1.0, y, opérateur, ha='center', va='center',
             fontsize=12, fontweight='bold', color=couleur,
             fontfamily='monospace',
             bbox=dict(boxstyle='round,pad=0.2', facecolor='white',
                       edgecolor=couleur, alpha=0.9))
    # Syntaxe complète
    ax2.text(3.2, y + 0.08, syntaxe, ha='left', va='center',
             fontsize=10, fontweight='bold', color='#2c3e50',
             fontfamily='monospace')
    # Description
    ax2.text(3.2, y - 0.22, description, ha='left', va='center',
             fontsize=8.5, color='#555555', style='italic')

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

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

# Visualisation : pipeline multi-étapes avec flux
fig, ax = plt.subplots(figsize=(16, 5))
ax.set_xlim(-0.5, 16)
ax.set_ylim(-1.5, 5.5)
ax.axis('off')
ax.set_title('Pipeline : connexion des commandes via les pipes',
             fontsize=14, fontweight='bold', pad=15)

palette2 = sns.color_palette("muted", 5)

commandes = [
    ('find .\n-name "*.log"', 2.0),
    ('grep\n"ERROR"', 5.5),
    ('awk\n\'{print $5}\'', 9.0),
    ('sort |\nuniq -c', 12.5),
    ('head -10', 15.0),
]

boite_w, boite_h = 2.6, 2.0
boite_y = 1.5

for i, (cmd, x) in enumerate(commandes):
    c = palette2[i % len(palette2)]
    rect = patches.FancyBboxPatch(
        (x - boite_w/2, boite_y), boite_w, boite_h,
        boxstyle="round,pad=0.12", linewidth=2.2,
        edgecolor=c, facecolor=(*c[:3], 0.20)
    )
    ax.add_patch(rect)
    ax.text(x, boite_y + boite_h/2, cmd,
            ha='center', va='center', fontsize=9.5, fontweight='bold',
            color=c, fontfamily='monospace')

    if i < len(commandes) - 1:
        next_x = commandes[i+1][1]
        # Ligne de pipe
        x_start = x + boite_w/2 + 0.05
        x_end   = next_x - boite_w/2 - 0.05
        mid_x   = (x_start + x_end) / 2
        mid_y   = boite_y + boite_h/2

        ax.annotate('',
                    xy=(x_end, mid_y),
                    xytext=(x_start, mid_y),
                    arrowprops=dict(arrowstyle='->', color='#555555', lw=2.2))
        # Symbole pipe sur la flèche
        ax.text(mid_x, mid_y + 0.4, '|', ha='center', va='center',
                fontsize=16, color='#888888', fontweight='bold')
        # Label stdin/stdout
        ax.text(mid_x, mid_y - 0.5, 'stdout → stdin',
                ha='center', va='center', fontsize=7.5, color='#888888',
                style='italic')

# Entrée et sortie finales
ax.text(0.2, boite_y + boite_h/2, 'Système\nde fichiers',
        ha='center', va='center', fontsize=8, color='#666666',
        style='italic')
ax.annotate('', xy=(commandes[0][1] - boite_w/2 - 0.1, boite_y + boite_h/2),
            xytext=(0.7, boite_y + boite_h/2),
            arrowprops=dict(arrowstyle='->', color='#666666', lw=1.5, linestyle='dashed'))

ax.text(15.9, boite_y + boite_h/2 + 0.3, 'Terminal\n(stdout)',
        ha='left', va='center', fontsize=8, color='#27ae60', style='italic')

# Note en bas
ax.text(8, -0.8,
        'Chaque commande s\'exécute dans un sous-shell — '
        'les pipes sont créés avant le lancement des processus',
        ha='center', va='center', fontsize=9.5, color='#555555',
        style='italic',
        bbox=dict(boxstyle='round,pad=0.3', facecolor='#f8f9fa',
                  edgecolor='#dee2e6', alpha=0.9))

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

## Exemples avancés de pipelines

### Monitoring et analyse en temps réel

```bash
# Surveiller les nouvelles lignes d'un log en les filtrant
tail -f /var/log/syslog | grep --line-buffered "ERREUR\|WARN" | \
    while read ligne; do
        echo "[$(date '+%H:%M:%S')] $ligne"
    done

# Calculer la taille cumulée des fichiers de log
find /var/log -name "*.log" -type f | \
    xargs du -b 2>/dev/null | \
    awk '{total += $1} END {printf "Total : %.2f Mo\n", total/1048576}'

# Identifier les fichiers en double (même contenu, noms différents)
find . -type f -exec md5sum {} \; | \
    sort | \
    uniq -d -w32 | \
    awk '{print $2}'
```

### Traitement de données structurées

```bash
# Extraire et reformater des données JSON avec jq et awk
curl -s "https://api.example.com/users" | \
    jq -r '.[] | "\(.nom),\(.email),\(.age)"' | \
    sort -t',' -k3,3n | \
    awk -F',' 'BEGIN {print "Nom;Email;Âge"} {print $1";"$2";"$3}' | \
    tee rapport_utilisateurs.csv | \
    wc -l

# Convertir un fichier de log en CSV
awk '{
    match($0, /\[([^\]]+)\]/, date_arr)
    match($0, /"([A-Z]+) ([^ ]+)/, méthode_arr)
    print date_arr[1] "," méthode_arr[1] "," $9 "," $10
}' /var/log/apache2/access.log | \
    sort -t',' -k3,3n | \
    tee accès.csv | head -5
```

```{prf:example} Pipeline de traitement de données de capteurs
:label: example-07-01
Supposons un fichier `capteurs.txt` avec des relevés de température toutes les minutes, au format `timestamp,capteur_id,température` :

```bash
# Calculer les statistiques par capteur
sort -t',' -k2,2 capteurs.txt | \
    awk -F',' '
    {
        somme[$2] += $3
        n[$2]++
        if (!min[$2] || $3 < min[$2]) min[$2] = $3
        if ($3 > max[$2]) max[$2] = $3
    }
    END {
        printf "%-15s %8s %8s %8s %8s\n", "Capteur", "Moyenne", "Min", "Max", "N"
        printf "%-15s %8s %8s %8s %8s\n", "-------", "-------", "---", "---", "-"
        for (c in somme)
            printf "%-15s %8.2f %8.2f %8.2f %8d\n",
                   c, somme[c]/n[c], min[c], max[c], n[c]
    }' | sort -k2,2rn

# Détecter les anomalies (température > 80°C)
awk -F',' '$3 > 80 {
    print "ALERTE:", $1, "- Capteur", $2, "- Température:", $3 "°C"
}' capteurs.txt | tee alertes.log | wc -l
```
```

## Résumé

Dans ce chapitre, nous avons maîtrisé les mécanismes de communication entre processus et le shell :

- Les **trois flux standard** — stdin (0), stdout (1) et stderr (2) — sont des descripteurs de fichiers hérités par chaque processus. Leur séparation permet un contrôle fin de la communication entre programmes.
- Les **redirections** permettent de réorienter ces flux : `>` écrase un fichier, `>>` ajoute, `<` connecte un fichier à stdin. La redirection `2>` cible stderr, `2>&1` fusionne stderr dans stdout, et `&>` redirige les deux simultanément. `/dev/null` absorbe silencieusement tout ce qui lui est envoyé.
- Les **pipes** `|` connectent le stdout d'une commande au stdin de la suivante. Les commandes s'exécutent en parallèle dans des sous-shells ; SIGPIPE gère proprement l'arrêt de la chaîne.
- La **substitution de processus** `<(cmd)` et `>(cmd)` permet d'utiliser la sortie ou l'entrée d'une commande comme un fichier, contournant la limitation des programmes qui n'acceptent que des arguments de type fichier.
- **`xargs`** transforme stdin en arguments de commande, avec `-I{}` pour la substitution, `-n` pour limiter le nombre d'arguments par appel et `-P` pour la parallélisation.
- **`tee`** duplique un flux : il écrit simultanément sur stdout et dans un fichier, permettant d'observer et d'enregistrer sans interrompre le pipeline.
- Les **here-documents** (`<<EOF`) et **here-strings** (`<<<`) permettent d'inclure des données multi-lignes directement dans les scripts sans fichiers temporaires.

Dans le chapitre suivant, nous aborderons le cœur du scripting Bash : les **variables et les types** — comment déclarer des variables, maîtriser les différents types de guillemets, gérer l'environnement et effectuer des calculs arithmétiques.
