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

# Variables et types

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

Bash est un langage à typage faible et dynamique : par défaut, toute variable est une chaîne de caractères, et le contexte détermine si elle est traitée comme un entier, un tableau ou une autre valeur. Cette flexibilité est pratique pour les scripts simples, mais elle est aussi source de subtilités — et parfois de bugs — que seule une bonne compréhension des mécanismes sous-jacents permet d'éviter. Ce chapitre couvre la déclaration et l'affectation des variables, les différents types de guillemets, la gestion de l'environnement, les variables spéciales du shell, la substitution de commandes et l'arithmétique en Bash.

## Déclaration et affectation

En Bash, une variable se déclare simplement en l'affectant. Il n'y a pas de mot-clé de déclaration obligatoire (contrairement à d'autres langages). La règle la plus importante est qu'il **ne doit pas y avoir d'espaces autour du signe `=`** :

```{prf:definition} Règle d'affectation en Bash
:label: definition-08-01
L'affectation d'une variable suit la syntaxe stricte `NOM=valeur`, sans espace autour du signe égal. La présence d'un espace transformerait la ligne en appel de commande : `NOM = valeur` serait interprété comme « exécuter la commande `NOM` avec les arguments `=` et `valeur` », ce qui produit généralement une erreur de type « commande introuvable ».

Par convention, les **variables d'environnement exportées** et les **constantes** sont nommées en `MAJUSCULES_AVEC_UNDERSCORES`, tandis que les **variables locales** aux scripts utilisent des `minuscules_avec_underscores`.
```

```bash
# Affectation correcte
prénom="Alice"
âge=32
chemin_config="/etc/mon_app/config.yaml"

# ERREUR : espace autour de =
prénom = "Alice"   # bash: prénom: command not found

# Les valeurs sans espaces n'ont pas besoin de guillemets
compteur=0
fichier=rapport.txt

# Mais les guillemets sont recommandés pour les chaînes
message="Bonjour, monde !"

# Affectation avec expansion immédiate
répertoire_courant=$(pwd)
horodatage=$(date +%Y%m%d_%H%M%S)
```

Pour lire la valeur d'une variable, on la préfixe du symbole `$`. Pour éviter les ambiguïtés lorsque le nom de la variable est suivi d'autres caractères alphanumériques, on l'entoure d'accolades `${}` :

```bash
animal="chat"
echo $animal       # chat
echo ${animal}     # chat (équivalent, plus explicite)
echo "${animal}s"  # chats (les accolades séparent le nom de la variable du 's')
echo "$animals"    # (vide : la variable 'animals' n'existe pas)
```

## Affichage : `echo` et `printf`

Deux commandes permettent d'afficher du texte : `echo` et `printf`. Elles ont des comportements différents qu'il est important de comprendre.

```{prf:definition} `echo` vs `printf`
:label: definition-08-02
**`echo`** est la commande la plus simple pour afficher du texte. Elle ajoute automatiquement un retour à la ligne en fin de sortie. Son comportement avec les options (`-n`, `-e`) **varie selon les implémentations** (bash built-in, `/bin/echo`, `ksh`, etc.), ce qui la rend peu fiable dans les scripts portables. L'option `-n` supprime le retour à la ligne final, et `-e` active l'interprétation des séquences d'échappement comme `\n`, `\t`, `\\`.

**`printf`** est l'outil recommandé pour un affichage précis et portable. Sa syntaxe s'inspire de `printf` en C : `printf FORMAT [ARGUMENTS...]`. Le format peut contenir des spécificateurs (`%s` pour chaîne, `%d` pour entier, `%f` pour flottant, `%x` pour hexadécimal) et des séquences d'échappement (`\n`, `\t`, `\\`). Contrairement à `echo`, `printf` **ne termine pas automatiquement par un retour à la ligne** : il faut l'inclure explicitement dans le format.
```

```bash
# Exemples echo
echo "Bonjour"              # Bonjour (avec \n final)
echo -n "Sans newline"      # Sans newline (pas de \n)
echo -e "Tab:\there"        # Tab:    ici (si -e est supporté)

# Exemples printf — à préférer dans les scripts
printf "Bonjour\n"                        # Bonjour
printf "Nom : %s, Âge : %d\n" "Alice" 32 # Nom : Alice, Âge : 32
printf "Pi ≈ %.4f\n" 3.14159             # Pi ≈ 3.1416
printf "%05d\n" 42                        # 00042 (zéro-padded)
printf "%-20s %10s\n" "Alice" "admin"     # alignement colonne gauche/droite
printf "Hexadécimal : %x\n" 255          # Hexadécimal : ff

# printf dans une boucle pour générer un tableau
printf "%-15s %-10s %-5s\n" "Nom" "Ville" "Âge"
printf "%-15s %-10s %-5s\n" "---" "-----" "---"
for ligne in "Alice:Paris:32" "Bob:Lyon:25" "Carol:Marseille:41"; do
    IFS=':' read -r nom ville âge <<< "$ligne"
    printf "%-15s %-10s %-5s\n" "$nom" "$ville" "$âge"
done
```

```{prf:remark}
:label: remark-08-01
La règle pratique est simple : utilisez `echo` pour les messages rapides et interactifs dans le terminal, et `printf` pour tout affichage dans les scripts devant être précis, formaté ou portable. En particulier, `printf '%s\n' "$variable"` est plus sûr que `echo "$variable"` quand la variable pourrait commencer par `-` (ce qui serait interprété comme une option de `echo`).
```

## Les guillemets : le mécanisme fondamental

La gestion des guillemets est l'un des aspects les plus importants — et les plus mal compris — de Bash. Elle détermine comment les variables sont expansées, comment les espaces sont gérés et comment les caractères spéciaux sont interprétés.

```{prf:definition} Les quatre mécanismes de citation en Bash
:label: definition-08-03
Bash reconnaît quatre formes de citation, chacune avec un comportement distinct :

1. **Guillemets doubles** `"..."` : permettent l'**interpolation** des variables (`$var`), des substitutions de commandes (`$(cmd)`) et des expansions arithmétiques (`$((expr))`). Les caractères `$`, `` ` ``, `\` et `!` conservent leur signification spéciale. Tous les autres caractères (espaces, `*`, `?`, `{`, `}`, etc.) sont traités littéralement.

2. **Guillemets simples** `'...'` : **aucune interprétation**. Tout ce qui est entre guillemets simples est traité comme une chaîne littérale, y compris `$`, les backslashes et les retours à la ligne. Il est impossible d'inclure un guillemet simple à l'intérieur d'une chaîne à guillemets simples.

3. **Backticks** `` `cmd` `` : substitution de commande (remplacée par la sortie de `cmd`). **Obsolète** : préférer `$(cmd)`, qui est plus lisible et peut être imbriqué.

4. **Pas de guillemets** : le shell effectue toutes les expansions (*word splitting*, *globbing*, etc.). Les espaces dans les valeurs de variables séparent les mots. À **éviter** autour des variables (`$var` sans guillemets est dangereux si `var` contient des espaces ou des caractères spéciaux).
```

### Guillemets doubles — interpolation contrôlée

```bash
nom="Alice"
ville="New York"

# Interpolation de variable dans une chaîne
echo "Bonjour, $nom !"        # Bonjour, Alice !
echo "Ville : ${ville}"       # Ville : New York

# Les espaces sont préservés dans la valeur
echo "$ville"                  # New York (un seul argument)
echo $ville                    # New York (interprété comme deux mots : New et York)

# Interpolation de sous-commande
echo "Date : $(date +%Y-%m-%d)"   # Date : 2024-03-15
echo "Fichiers : $(ls | wc -l)"   # Fichiers : 42

# Backslash d'échappement dans les guillemets doubles
echo "Guillemet double : \""       # Guillemet double : "
echo "Backslash : \\"              # Backslash : \
echo "Variable non interpolée : \$nom"  # Variable non interpolée : $nom
```

### Guillemets simples — texte littéral

```bash
# Aucune interprétation
echo 'Bonjour, $nom !'        # Bonjour, $nom !  (pas d'interpolation)
echo 'Date : $(date)'         # Date : $(date)   (pas de substitution)
echo 'Tab : \t'               # Tab : \t         (pas d'échappement)

# Utilité : passer des expressions régulières ou du code sans interférences
grep 'prix = \$[0-9]\+' catalogue.txt
sed 's/\(.*\)/[&]/'  # pas d'expansion Bash des parenthèses

# Guillemet simple impossible à l'intérieur de guillemets simples
# Astuce : terminer, ajouter le guillemet échappé, recommencer
echo 'C'\''est une apostrophe'   # C'est une apostrophe
# Ou utiliser $'...' (ANSI C quoting) :
echo $'C\'est une apostrophe'    # C'est une apostrophe
```

### La syntaxe `$'...'` — ANSI C quoting

```bash
# Séquences d'échappement dans les guillemets simples (syntaxe $'...')
echo $'Ligne 1\nLigne 2'   # Deux lignes
echo $'Tab\there'           # Tab puis here
echo $'\a'                  # Bip sonore (bell)
echo $'\e[31mTexte rouge\e[0m'  # Texte en rouge (codes ANSI)

# Caractères Unicode
echo $'\u00e9'    # é
echo $'\u00e0'    # à
echo $'\u2603'    # ☃ (bonhomme de neige)
```

```{prf:remark}
:label: remark-08-02
La règle d'or du scripting Bash est de **toujours entourer les variables de guillemets doubles** : `"$variable"`. Sans guillemets, une variable contenant des espaces sera soumise au *word splitting* (découpage en plusieurs mots) et au *globbing* (expansion des caractères `*`, `?`, etc.), ce qui peut provoquer des comportements inattendus voire dangereux (imaginez `rm $fichier` si `$fichier` vaut `* important.txt`). Les guillemets doubles protègent de ces expansions non souhaitées tout en permettant l'interpolation.
```

## Variables d'environnement

L'**environnement** d'un processus est un ensemble de paires `NOM=valeur` héritées du processus parent et transmises aux processus enfants. Il constitue un mécanisme de configuration global.

```{prf:definition} Variables d'environnement
:label: definition-08-04
Une **variable d'environnement** est une variable Bash qui a été **exportée** : elle est transmise aux processus enfants créés par le shell courant. Une variable non exportée n'est visible que dans le shell qui l'a définie — ses enfants ne la voient pas.

La commande **`export NOM`** (ou `export NOM=valeur`) marque une variable pour l'exportation. La commande **`unset NOM`** supprime une variable (exportée ou non) de l'environnement courant.
```

### Inspecter l'environnement

```bash
# Lister toutes les variables d'environnement
env
printenv

# Afficher la valeur d'une variable spécifique
printenv PATH
printenv HOME

# Variables d'environnement importantes
echo $HOME        # Répertoire personnel de l'utilisateur
echo $PATH        # Chemins de recherche des exécutables
echo $USER        # Nom de l'utilisateur courant
echo $SHELL       # Shell par défaut de l'utilisateur
echo $LANG        # Paramètre de langue et d'encodage
echo $TERM        # Type de terminal
echo $EDITOR      # Éditeur de texte par défaut
echo $PWD         # Répertoire courant
echo $OLDPWD      # Répertoire précédent (utilisé par cd -)
echo $HOSTNAME    # Nom de la machine
```

### Exporter des variables

```bash
# Définir et exporter en une seule commande
export MA_VARIABLE="valeur"

# Définir puis exporter séparément
MA_VARIABLE="valeur"
export MA_VARIABLE

# Vérifier qu'une variable est bien exportée
export | grep MA_VARIABLE

# Exécuter une commande avec une variable d'environnement temporaire
NOM=Alice ./script.sh     # La variable NOM est définie uniquement pour script.sh

# Méthode explicite avec env
env NOM=Alice PORT=8080 ./serveur.sh

# Supprimer une variable de l'environnement
unset MA_VARIABLE

# Supprimer de l'export sans supprimer la variable
export -n MA_VARIABLE
```

### Modifier le PATH

```bash
# Ajouter un répertoire au début du PATH (priorité maximale)
export PATH="/mon/répertoire/bin:$PATH"

# Ajouter à la fin (priorité minimale)
export PATH="$PATH:/mon/répertoire/bin"

# Afficher les répertoires du PATH, un par ligne
echo $PATH | tr ':' '\n'

# Trouver la localisation d'un exécutable
which python3
type python3      # Plus complet : alias, fonctions, builtins
command -v python3  # Portable, retourne le chemin sans message d'erreur
```

## Variables spéciales du shell

Bash définit un ensemble de variables spéciales dont les valeurs sont gérées automatiquement par le shell. Elles sont essentielles pour l'écriture de scripts robustes.

```{prf:definition} Variables spéciales Bash
:label: definition-08-05
Les variables spéciales ne peuvent pas être affectées directement (sauf quelques exceptions) :

| Variable | Signification |
|----------|---------------|
| `$0` | Nom du script ou du shell en cours d'exécution |
| `$1` à `$9` | Arguments positionnels passés au script ou à la fonction |
| `${10}`, `${11}`, … | Arguments positionnels au-delà du 9e (accolades obligatoires) |
| `$@` | Tous les arguments positionnels, **chaque argument étant un mot séparé** (avec guillemets doubles : `"$@"` donne `"$1" "$2" ...`) |
| `$*` | Tous les arguments positionnels en **un seul mot** (avec guillemets doubles : `"$*"` donne `"$1 $2 ..."` avec l'IFS comme séparateur) |
| `$#` | Nombre d'arguments positionnels |
| `$?` | Code de retour de la dernière commande exécutée (0 = succès, ≠ 0 = erreur) |
| `$$` | PID (identifiant de processus) du shell courant |
| `$!` | PID du dernier processus lancé en arrière-plan avec `&` |
| `$-` | Options du shell courant (les flags actifs, comme `himBH`) |
| `$_` | Dernier argument de la commande précédente |
```

### Utilisation des variables spéciales

```bash
#!/usr/bin/env bash
# Démonstration des variables spéciales

echo "Nom du script : $0"
echo "Nombre d'arguments : $#"
echo "Premier argument : $1"
echo "Deuxième argument : $2"
echo "Tous les arguments (\$@) :"
for arg in "$@"; do
    echo "  - '$arg'"
done

# Code de retour
ls /répertoire_inexistant 2>/dev/null
echo "Code de retour de ls : $?"   # 2

ls /tmp 2>/dev/null
echo "Code de retour de ls : $?"   # 0 (succès)

# PID du shell
echo "Mon PID : $$"

# Lancer un processus en arrière-plan
sleep 100 &
echo "PID du processus en arrière-plan : $!"
```

### La différence cruciale entre `$@` et `$*`

```bash
# Avec les arguments : "premier argument" "deuxième" "troisième"

# "$@" — chaque argument est un mot séparé (RECOMMANDÉ)
for arg in "$@"; do
    echo "Arg : '$arg'"
done
# Arg : 'premier argument'
# Arg : 'deuxième'
# Arg : 'troisième'

# "$*" — tous les arguments en un seul mot
for arg in "$*"; do
    echo "Arg : '$arg'"
done
# Arg : 'premier argument deuxième troisième'

# Sans guillemets, $@ et $* se comportent pareil (word splitting)
# — et c'est rarement ce qu'on veut
```

## Substitution de commandes

La **substitution de commandes** permet d'utiliser la sortie d'une commande comme valeur dans une expression. La forme moderne est `$(commande)`.

```bash
# Stocker la sortie d'une commande dans une variable
date_actuelle=$(date +%Y-%m-%d)
fichiers=$(ls /tmp/*.log 2>/dev/null)
nb_processus=$(ps aux | wc -l)

# Utiliser directement dans une chaîne
echo "Rapport généré le $(date '+%d/%m/%Y à %H:%M')"
echo "Noyau : $(uname -r)"
echo "Espace libre : $(df -h / | awk 'NR==2 {print $4}')"

# Imbrication (possible avec $(), impossible avec backticks)
fichier_plus_récent=$(ls -t $(find . -name "*.log") | head -1)

# Capturer stdout et stderr séparément
résultat=$(commande 2>/tmp/erreurs.txt)
erreurs=$(cat /tmp/erreurs.txt)

# Capturer les deux ensemble
tout=$(commande 2>&1)
```

```{prf:remark}
:label: remark-08-03
La substitution `$(commande)` remplace les backticks `` `commande` `` qui sont l'ancienne syntaxe. Les backticks sont déconseillés car ils sont plus difficiles à lire (confusion visuelle avec les guillemets simples), ils ne peuvent pas être imbriqués facilement et leur comportement avec le backslash est plus complexe. La syntaxe `$()` est claire, imbriquée facilement et recommandée dans tous les scripts modernes.
```

## Arithmétique en Bash

Bash propose plusieurs mécanismes pour effectuer des calculs sur des entiers. Pour les calculs en virgule flottante, il faut faire appel à des outils externes.

```{prf:definition} Mécanismes d'arithmétique en Bash
:label: definition-08-06
Bash offre trois approches pour l'arithmétique sur les entiers :

1. **`$(( expression ))`** — expansion arithmétique : évalue une expression arithmétique et retourne le résultat sous forme de chaîne. C'est l'approche recommandée.
2. **`(( expression ))`** — commande arithmétique : évalue l'expression pour son effet de bord (affectation) ou son code de retour (0 si l'expression est non nulle, 1 si elle est nulle — utile dans les conditions).
3. **`let "expression"`** — ancienne syntaxe équivalente à `(( ))`. Déconseillée au profit de `(( ))`.

Pour les calculs en **virgule flottante**, il faut utiliser `bc` (calculatrice en ligne de commande) ou `awk`.
```

### Arithmétique entière avec `$(( ))`

```bash
# Opérations de base
echo $((2 + 3))       # 5
echo $((10 - 4))      # 6
echo $((3 * 7))       # 21
echo $((17 / 5))      # 3 (division entière)
echo $((17 % 5))      # 2 (modulo)
echo $((2 ** 10))     # 1024 (puissance)

# Avec des variables (pas besoin de $ à l'intérieur de (( )))
a=10
b=3
echo $((a + b))       # 13
echo $((a * b))       # 30
echo $((a / b))       # 3 (division entière)

# Affectation dans $(( ))
résultat=$((a ** 2 + b ** 2))
echo $résultat        # 109

# Incrémentation
compteur=0
compteur=$((compteur + 1))
# Ou avec (( )) :
((compteur++))
((compteur += 5))

# Opérateurs bit à bit
echo $((0xFF))        # 255 (littéral hexadécimal)
echo $((10 & 6))      # 2 (AND bit à bit)
echo $((10 | 6))      # 14 (OR bit à bit)
echo $((10 ^ 6))      # 12 (XOR bit à bit)
echo $((~10))         # -11 (NOT bit à bit)
echo $((10 << 2))     # 40 (décalage gauche)
echo $((10 >> 1))     # 5 (décalage droit)
```

### La commande arithmétique `(( ))`

```bash
# (( )) retourne 0 (succès) si l'expression est non nulle
# et 1 (échec) si l'expression vaut 0 — utile dans les conditions
a=5
if (( a > 3 )); then
    echo "$a est supérieur à 3"
fi

# Boucle avec compteur
for ((i=0; i<5; i++)); do
    echo "Itération $i"
done

# Affectation conditionnelle
(( valeur = (a > 3) ? 100 : 0 ))  # Opérateur ternaire
echo $valeur   # 100

# Plusieurs affectations
((a = 10, b = 20, c = a + b))
echo $c   # 30
```

### Calculs en virgule flottante avec `bc`

```bash
# bc lit des expressions depuis stdin
echo "scale=4; 355/113" | bc        # 3.1415 (π approché)
echo "scale=2; sqrt(2)" | bc        # 1.41
echo "scale=6; e(1)" | bc -l        # 2.718281 (e, avec math library)
echo "scale=10; 4*a(1)" | bc -l     # 3.1415926535 (π via arctan)

# Stocker dans une variable
π=$(echo "scale=10; 4*a(1)" | bc -l)
echo "π ≈ $π"

# Calcul avec des variables shell
prix=19.99
quantité=3
total=$(echo "scale=2; $prix * $quantité" | bc)
echo "Total : ${total}€"   # Total : 59.97€

# Comparaison de flottants (bc retourne 0 ou 1)
if [ $(echo "$prix > 15.0" | bc) -eq 1 ]; then
    echo "Prix supérieur à 15€"
fi
```

### Calculs en virgule flottante avec `awk`

```bash
# awk peut effectuer des calculs flottants directement
awk 'BEGIN {printf "%.4f\n", 355/113}'        # 3.1416
awk 'BEGIN {printf "%.6f\n", sqrt(2)}'         # 1.414214
awk 'BEGIN {printf "%.8f\n", exp(1)}'          # 2.71828182

# Calcul à partir de variables shell
prix=19.99; quantité=3
awk "BEGIN {printf \"Total : %.2f€\n\", $prix * $quantité}"
```

## La commande `declare` — typage explicite

Bien que Bash soit à typage faible, la commande `declare` permet d'attribuer des attributs aux variables pour contrôler leur comportement.

```{prf:definition} Attributs de `declare`
:label: definition-08-07
La commande `declare` (ou son synonyme `typeset`) permet de définir des attributs sur les variables :

- **`-r`** (readonly) : la variable ne peut plus être modifiée ni supprimée.
- **`-i`** (integer) : la valeur est traitée comme un entier. Toute affectation est évaluée arithmétiquement.
- **`-l`** (lowercase) : la valeur est automatiquement convertie en minuscules.
- **`-u`** (uppercase) : la valeur est automatiquement convertie en majuscules.
- **`-x`** (export) : équivalent à `export`.
- **`-a`** (array) : déclare un tableau indexé.
- **`-A`** (associative array) : déclare un tableau associatif.
- **`-p`** : affiche la déclaration et la valeur de la variable.
- **`-f`** : s'applique aux fonctions.
```

```bash
# Variable en lecture seule
declare -r VERSION="1.0.0"
VERSION="2.0.0"   # bash: VERSION: variable en lecture seule

# Variable entière (les affectations sont évaluées arithmétiquement)
declare -i compteur=0
compteur+=5     # compteur vaut 5
compteur="3+2"  # compteur vaut 5 (évalué comme 3+2)
compteur="abc"  # compteur vaut 0 (abc n'est pas un entier valide)

# Variables normalisées en casse
declare -l prénom="ALICE"
echo "$prénom"    # alice

declare -u code="erreur_critique"
echo "$code"      # ERREUR_CRITIQUE

# Afficher la déclaration complète d'une variable
declare -p PATH
declare -p BASH_VERSION

# Variables readonly du shell (non modifiables même avec declare)
echo $BASH_VERSION    # Version de Bash (ex: 5.2.15(1)-release)
echo $BASH_PID        # PID du Bash courant
echo $RANDOM          # Nombre pseudo-aléatoire entre 0 et 32767
echo $LINENO          # Numéro de la ligne courante dans le script
echo $SECONDS         # Secondes écoulées depuis le démarrage du shell
```

## Expansion de variables avancée

Bash propose une syntaxe très riche pour manipuler les valeurs des variables directement dans les expansions `${}`.

```{prf:example} Valeurs par défaut et substitutions conditionnelles
:label: example-08-01
Bash dispose d'un ensemble d'opérateurs de substitution très puissants :

```bash
# ${variable:-valeur_défaut} : utiliser valeur_défaut si variable est vide ou non définie
echo "${NOM:-Inconnu}"      # "Inconnu" si NOM n'est pas défini

# ${variable:=valeur_défaut} : affecter valeur_défaut si variable est vide ou non définie
echo "${TIMEOUT:=30}"       # Affecte 30 à TIMEOUT et l'affiche

# ${variable:+valeur_alt} : utiliser valeur_alt si variable EST définie et non vide
echo "${DEBUG:+--verbose}"  # Affiche "--verbose" seulement si DEBUG est défini

# ${variable:?message_erreur} : erreur fatale si variable est vide ou non définie
echo "${FICHIER:?Le fichier doit être spécifié}"

# Longueur d'une variable
chaîne="Bonjour"
echo ${#chaîne}        # 7

# Extraction d'une sous-chaîne : ${variable:offset:longueur}
echo ${chaîne:2:3}     # njo (3 caractères à partir du 2e)
echo ${chaîne: -3}     # our (3 derniers caractères)

# Suppression de préfixe
chemin="/home/alice/documents/rapport.pdf"
echo ${chemin#*/}      # home/alice/documents/rapport.pdf (supprime le plus court préfixe */)
echo ${chemin##*/}     # rapport.pdf (supprime le plus long préfixe */)

# Suppression de suffixe
echo ${chemin%.*}      # /home/alice/documents/rapport (supprime .pdf)
echo ${chemin%%/*}     # (vide — supprime le plus long suffixe /*)

# Remplacement de sous-chaîne
echo ${chemin/alice/bob}    # /home/bob/documents/rapport.pdf (première occurrence)
echo ${chemin//a/A}         # /home/Alice/documents/rApport.pdf (toutes les occurrences)
```
```

## Visualisation : guillemets et interpolation

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

fig, axes = plt.subplots(2, 2, figsize=(16, 10))
fig.suptitle('Types de guillemets et comportements en Bash',
             fontsize=15, fontweight='bold', y=1.01)

palette = sns.color_palette("muted", 6)
couleurs = {
    'double': palette[0],
    'simple': palette[1],
    'aucun':  palette[2],
    'dollar': palette[3],
}

# --- Tableau comparatif des types de guillemets ---
ax = axes[0, 0]
ax.axis('off')
ax.set_title('Comportement des guillemets', fontsize=13, fontweight='bold')

types = [
    ('"..."',     'Doubles',     'Oui', 'Oui', 'Oui', 'Non', couleurs['double']),
    ("'...'",     'Simples',     'Non', 'Non', 'Non', 'Non', couleurs['simple']),
    ('Aucun',     'Aucun',       'Oui', 'Oui', 'Oui', 'Oui', couleurs['aucun']),
    ("$'...'",    'ANSI C',      'Non', 'Non', '\\n \\t', 'Non', couleurs['dollar']),
]

colonnes = ['Syntaxe', 'Type', 'Vars $', 'Cmds $()', 'Échap.', 'Globbing']
largeurs = [0.14, 0.14, 0.12, 0.14, 0.14, 0.14]
x_cols = [0.05, 0.19, 0.33, 0.45, 0.59, 0.73]
y_debut = 0.88

# En-tête
for j, (col, x_c) in enumerate(zip(colonnes, x_cols)):
    ax.text(x_c, y_debut, col, ha='left', va='center',
            fontsize=10, fontweight='bold', color='#2c3e50',
            transform=ax.transAxes)

ax.axhline(y=y_debut - 0.05, xmin=0.02, xmax=0.98, color='#cccccc',
           linewidth=1, transform=ax.transAxes)

for i, (synt, nom, var, cmd, esc, glob, c) in enumerate(types):
    y = y_debut - 0.13 - i * 0.16
    # Fond coloré
    fond = patches.FancyBboxPatch(
        (0.03, y - 0.06), 0.94, 0.13,
        boxstyle="round,pad=0.01", linewidth=1.5,
        edgecolor=c, facecolor=(*c[:3], 0.12),
        transform=ax.transAxes
    )
    ax.add_patch(fond)
    valeurs = [synt, nom, var, cmd, esc, glob]
    for j, (val, x_c) in enumerate(zip(valeurs, x_cols)):
        couleur_texte = c if j <= 1 else ('#27ae60' if val == 'Oui' else ('#e74c3c' if val == 'Non' else '#e67e22'))
        ax.text(x_c, y, val, ha='left', va='center',
                fontsize=9.5, color=couleur_texte,
                fontfamily='monospace' if j == 0 else 'sans-serif',
                fontweight='bold' if j == 0 else 'normal',
                transform=ax.transAxes)

# --- Exemples d'interpolation ---
ax2 = axes[0, 1]
ax2.axis('off')
ax2.set_title('Exemples : variable = "Alice"', fontsize=13, fontweight='bold')

exemples = [
    ('"Bonjour, $nom !"',   '→ Bonjour, Alice !',    couleurs['double']),
    ('"Date: $(date)"',     '→ Date: ven. 15 mars',  couleurs['double']),
    ('"$(( 2+3 ))"',        '→ 5',                    couleurs['double']),
    ("'Bonjour, $nom !'",   "→ Bonjour, $nom !",     couleurs['simple']),
    ('$\'Tab:\\there\'',    '→ Tab:	here',           couleurs['dollar']),
    ('$nom (sans guillemets)', '→ Alice (dangereux)', couleurs['aucun']),
]

for i, (expr, résultat, c) in enumerate(exemples):
    y_base = 0.88 - i * 0.14
    ax2.text(0.04, y_base, expr, ha='left', va='center',
             fontsize=9.5, fontweight='bold', color=c,
             fontfamily='monospace', transform=ax2.transAxes)
    ax2.text(0.04, y_base - 0.065, résultat, ha='left', va='center',
             fontsize=9, color='#555555', style='italic',
             transform=ax2.transAxes)
    ax2.axhline(y=y_base - 0.11, xmin=0.02, xmax=0.98, color='#eeeeee',
                linewidth=0.8, transform=ax2.transAxes)

# --- Variables spéciales ---
ax3 = axes[1, 0]
ax3.axis('off')
ax3.set_title('Variables spéciales ($0, $@, $?, $$, …)', fontsize=13, fontweight='bold')

vars_spéciales = [
    ('$0',  'Nom du script',          '#2980b9'),
    ('$1…$9', 'Arguments positionnels', '#2980b9'),
    ('$#',  'Nombre d\'arguments',    '#27ae60'),
    ('$@',  'Tous les args (séparés)', '#27ae60'),
    ('$*',  'Tous les args (un mot)', '#e67e22'),
    ('$?',  'Code de retour',         '#e74c3c'),
    ('$$',  'PID du shell',           '#8e44ad'),
    ('$!',  'PID du dernier &',       '#8e44ad'),
    ('$-',  'Flags du shell',         '#95a5a6'),
    ('$_',  'Dernier argument',       '#95a5a6'),
]

for i, (var, desc, c) in enumerate(vars_spéciales):
    col = 0 if i < 5 else 1
    row = i if i < 5 else i - 5
    x_base = 0.04 + col * 0.5
    y_base = 0.88 - row * 0.17
    ax3.text(x_base, y_base, var, ha='left', va='center',
             fontsize=11, fontweight='bold', color=c,
             fontfamily='monospace', transform=ax3.transAxes)
    ax3.text(x_base + 0.11, y_base, desc, ha='left', va='center',
             fontsize=8.5, color='#555555', transform=ax3.transAxes)

# --- Opérations arithmétiques ---
ax4 = axes[1, 1]
ax4.axis('off')
ax4.set_title('Mécanismes arithmétiques', fontsize=13, fontweight='bold')

mécanismes = [
    ('$((expr))',   'Expansion — retourne le résultat', palette[0]),
    ('((expr))',    'Commande — code de retour (if/for)', palette[1]),
    ('let "expr"',  'Ancienne syntaxe (déconseillée)', palette[4]),
    ('bc',          'Virgule flottante et précision', palette[2]),
    ('awk BEGIN',   'Calculs flottants dans awk', palette[3]),
]

exemples_arith = [
    'x=$((a*b + c))',
    'if (( x > 10 )); then',
    'let "x = a + b"',
    'echo "scale=4; $x/$y" | bc',
    'awk "BEGIN {printf \"%.2f\\n\", $x/$y}"',
]

for i, ((méca, desc, c), ex) in enumerate(zip(mécanismes, exemples_arith)):
    y_base = 0.88 - i * 0.17
    fond = patches.FancyBboxPatch(
        (0.02, y_base - 0.07), 0.96, 0.15,
        boxstyle="round,pad=0.01", linewidth=1.5,
        edgecolor=c, facecolor=(*c[:3], 0.12),
        transform=ax4.transAxes
    )
    ax4.add_patch(fond)
    ax4.text(0.05, y_base + 0.025, méca, ha='left', va='center',
             fontsize=10, fontweight='bold', color=c,
             fontfamily='monospace', transform=ax4.transAxes)
    ax4.text(0.38, y_base + 0.025, desc, ha='left', va='center',
             fontsize=8.5, color='#555555', transform=ax4.transAxes)
    ax4.text(0.05, y_base - 0.04, ex, ha='left', va='center',
             fontsize=8, color='#888888', style='italic',
             fontfamily='monospace', transform=ax4.transAxes)

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

## Bonnes pratiques avec les variables

```{prf:remark}
:label: remark-08-04
Quelques règles à respecter systématiquement pour écrire des scripts Bash sûrs et lisibles :

1. **Toujours citer les variables** : `"$variable"` et non `$variable`. Les espaces et les caractères spéciaux dans les valeurs de variables peuvent causer des comportements inattendus.

2. **Utiliser `${variable}` quand nécessaire** : pour lever les ambiguïtés quand le nom de variable est suivi de caractères alphanumériques (`${var}name` et non `$varname`).

3. **Préférer `$(commande)` aux backticks** : meilleure lisibilité, imbrication possible.

4. **Déclarer les constantes avec `declare -r`** ou `readonly` : cela protège les valeurs critiques contre les modifications accidentelles.

5. **Utiliser des valeurs par défaut** : `${variable:-valeur_par_défaut}` évite les erreurs dues aux variables non définies quand `set -u` est actif.

6. **Nommage explicite** : des noms de variables descriptifs (`chemin_fichier_log` plutôt que `f`) améliorent considérablement la lisibilité.
```

## Résumé

Dans ce chapitre, nous avons exploré le système de variables de Bash dans sa totalité :

- L'**affectation** ne tolère aucun espace autour du `=`. Par convention, les variables locales sont en minuscules et les variables d'environnement en majuscules.
- **`echo`** est pratique mais peu portable ; **`printf`** est recommandé dans les scripts pour son contrôle précis du format de sortie.
- Les **guillemets doubles** permettent l'interpolation des variables et des substitutions de commandes tout en protégeant des espaces et du globbing. Les **guillemets simples** traitent tout littéralement. La syntaxe **`$'...'`** permet les séquences d'échappement dans une chaîne littérale. L'absence de guillemets active le word splitting et le globbing — à éviter autour des variables.
- Les **variables d'environnement** sont exportées avec `export` et transmises aux processus enfants. `PATH`, `HOME`, `USER`, `SHELL` et `LANG` sont parmi les plus importantes.
- Les **variables spéciales** — `$0`, `$1`…`$#`, `$@`, `$*`, `$?`, `$$`, `$!` — sont gérées automatiquement par le shell et essentielles pour tout script.
- La **substitution de commandes** `$(commande)` insère la sortie d'une commande dans une expression.
- L'**arithmétique entière** s'effectue avec `$(( ))` ou `(( ))`. Les calculs en **virgule flottante** nécessitent `bc` ou `awk`.
- **`declare`** permet d'attribuer des types aux variables : `-r` (readonly), `-i` (entier), `-l`/`-u` (normalisation de casse), `-a`/`-A` (tableaux).

Dans le chapitre suivant, nous aborderons les **structures de contrôle** — conditions, boucles et ruptures de flux — qui permettent de construire des scripts avec une logique de branchement et d'itération.
