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

# Environnement et configuration du shell

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

L'environnement shell est l'ensemble des variables, alias, fonctions et paramètres qui définissent le comportement de votre shell et de tous les processus qu'il lance. Comprendre comment Bash initialise cet environnement — quels fichiers il lit, dans quel ordre, et dans quelles circonstances — est indispensable pour configurer un environnement de travail cohérent, diagnostiquer des comportements inattendus, et automatiser des tâches qui dépendent de l'environnement.

Ce chapitre couvre les fichiers de démarrage de Bash, la définition d'alias et de fonctions persistantes, le fonctionnement du `PATH`, les principales variables d'environnement système, l'outil `tmux` pour les sessions persistantes, et `direnv` pour les environnements par répertoire.

## Fichiers de configuration de Bash

Bash distingue plusieurs types de shells selon leur mode d'invocation, et chaque type lit des fichiers de configuration différents.

```{prf:definition} Shell de login vs shell interactif
:label: definition-15-01
Bash reconnaît deux dimensions indépendantes pour son mode d'invocation :

- **Shell de login** (*login shell*) : shell lancé lors d'une ouverture de session (connexion SSH, TTY virtuel, `su -`, etc.). Il lit les fichiers de configuration globaux et personnels de démarrage.
- **Shell non-login** : shell lancé dans un terminal existant (ouvrir un nouveau terminal dans GNOME, lancer `bash` depuis un script, etc.).
- **Shell interactif** : shell qui lit et écrit sur un terminal, attend des commandes de l'utilisateur.
- **Shell non-interactif** : shell lancé pour exécuter un script sans intervention humaine.

Ces deux dimensions se combinent : on peut avoir un shell interactif non-login (terminal dans une session graphique), un shell de login non-interactif (rare), etc.
```

### Fichiers lus selon le type de shell

#### Shell interactif de LOGIN

Un shell de login lit les fichiers suivants, **dans l'ordre** :

1. `/etc/profile` — configuration système globale
2. `~/.bash_profile` — si ce fichier existe, les suivants **ne sont pas lus**
3. `~/.bash_login` — lu si `~/.bash_profile` n'existe pas
4. `~/.profile` — lu si ni `~/.bash_profile` ni `~/.bash_login` n'existent

À la **fermeture** d'un shell de login, Bash lit `~/.bash_logout`.

#### Shell interactif NON-LOGIN

Un shell interactif non-login lit uniquement :

1. `/etc/bash.bashrc` — configuration système globale
2. `~/.bashrc` — configuration personnelle

```{prf:remark}
:label: remark-15-01
La convention standard sur Ubuntu/Debian est que `~/.bash_profile` appelle `~/.bashrc` explicitement :

```bash
# Contenu typique de ~/.bash_profile
if [[ -f ~/.bashrc ]]; then
    source ~/.bashrc
fi
```

Cela garantit que la configuration définie dans `~/.bashrc` s'applique aussi bien aux shells de login qu'aux shells non-login interactifs. La plupart des personnalisations (alias, fonctions, apparence du prompt, `PATH`) doivent donc être placées dans `~/.bashrc`.
```

### Rôle de chaque fichier

#### `/etc/profile`

Exécuté pour tous les utilisateurs lors d'un shell de login. Sur Debian/Ubuntu, il exécute aussi les scripts dans `/etc/profile.d/` :

```bash
# Exemple de /etc/profile
# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/bin" ] ; then
    PATH="$HOME/bin:$PATH"
fi

# Exécuter les scripts dans /etc/profile.d/
if [ -d /etc/profile.d ]; then
  for i in /etc/profile.d/*.sh; do
    if [ -r $i ]; then
      . $i
    fi
  done
fi
```

#### `~/.bash_profile` ou `~/.profile`

Configuration personnelle de login. À placer ici : les variables d'environnement exportées vers les processus enfants, le `PATH` pour les outils utilisateur (`~/.local/bin`, etc.).

```bash
# ~/.bash_profile
# Charger .bashrc si interactif
[[ -f ~/.bashrc ]] && source ~/.bashrc

# Variables d'environnement permanentes
export EDITOR="vim"
export VISUAL="vim"
export PAGER="less"
export LANG="fr_FR.UTF-8"

# Outils installés par l'utilisateur
export PATH="$HOME/.local/bin:$HOME/bin:$PATH"

# NVM, rbenv, pyenv, etc.
export NVM_DIR="$HOME/.nvm"
[[ -s "$NVM_DIR/nvm.sh" ]] && source "$NVM_DIR/nvm.sh"
```

#### `~/.bashrc`

Le fichier de configuration le plus important pour l'usage quotidien. Il est lu à chaque ouverture d'un terminal interactif.

```bash
# ~/.bashrc — structure typique

# Sortir immédiatement si shell non interactif
case $- in
    *i*) ;;      # Shell interactif : continuer
      *) return;; # Non interactif : ne rien faire
esac

# --- Historique ---
HISTSIZE=10000
HISTFILESIZE=20000
HISTCONTROL=ignoreboth    # ignorer les doublons et les lignes commençant par espace
shopt -s histappend       # Ajouter à l'historique, ne pas écraser

# --- Options shell ---
shopt -s checkwinsize     # Mettre à jour LINES et COLUMNS après chaque commande
shopt -s globstar         # Activer ** pour la récursion
shopt -s cdspell          # Corriger les fautes de frappe dans cd

# --- Prompt ---
PS1='\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '

# --- Aliases ---
alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'

# --- PATH ---
export PATH="$HOME/.local/bin:$PATH"

# --- Chargement des fichiers supplémentaires ---
[[ -f ~/.bash_aliases ]] && source ~/.bash_aliases
[[ -f ~/.bash_functions ]] && source ~/.bash_functions
```

#### `~/.bash_logout`

Exécuté à la fermeture d'un shell de login. Utile pour des nettoyages.

```bash
# ~/.bash_logout — exemples d'utilisation
clear                    # Effacer l'écran à la déconnexion
history -w               # Sauvegarder l'historique avant de quitter
echo "Session fermée le $(date)" >> ~/.sessions.log
```

## Alias

Un **alias** est un raccourci de commande : il remplace une chaîne de caractères par une autre avant l'interprétation de la ligne de commande.

```bash
# Définir un alias
alias ll='ls -alF --color=auto'
alias la='ls -A'
alias grep='grep --color=auto'
alias ..='cd ..'
alias ...='cd ../..'
alias ....='cd ../../..'

# Alias avec arguments ? Impossible directement — utiliser une fonction
alias df='df -h'
alias du='du -h'
alias free='free -h'

# Git
alias gs='git status'
alias ga='git add'
alias gc='git commit'
alias gp='git push'
alias gl='git log --oneline --graph --decorate'
alias gd='git diff'

# Sécurité : demander confirmation avant les opérations destructives
alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'

# Voir tous les alias définis
alias

# Voir la définition d'un alias spécifique
alias ll

# Supprimer un alias (pour la session courante)
unalias ll

# Appeler la commande originale sans l'alias (utile quand on a aliasé rm)
\rm fichier.txt      # Le \ désactive l'alias
command rm fichier.txt   # Alternative explicite
```

### Persistance des alias

Un alias défini dans le terminal ne survit pas à la fermeture de la session. Pour le rendre permanent, il faut l'ajouter à `~/.bashrc` ou à un fichier dédié `~/.bash_aliases` sourcé par `~/.bashrc`.

```bash
# Ajouter un alias permanent
echo "alias ll='ls -alF'" >> ~/.bash_aliases

# Recharger la configuration sans fermer le terminal
source ~/.bashrc
# Ou forme courte :
. ~/.bashrc
```

## Fonctions permanentes dans `.bashrc`

Les fonctions Bash permettent des raccourcis plus puissants que les alias car elles acceptent des arguments, des structures de contrôle et plusieurs commandes.

```bash
# Exemples de fonctions utiles à mettre dans ~/.bashrc

# Navigation rapide vers le projet courant
proj() {
    local base="$HOME/projets"
    if [[ -z "$1" ]]; then
        cd "$base"
    else
        cd "$base/$1"
    fi
}

# mkdir puis cd dans le nouveau répertoire
mkcd() {
    mkdir -p "$1" && cd "$1"
}

# Extraire n'importe quelle archive
extraire() {
    if [[ -f "$1" ]]; then
        case "$1" in
            *.tar.bz2)  tar -xjf "$1"  ;;
            *.tar.gz)   tar -xzf "$1"  ;;
            *.tar.xz)   tar -xJf "$1"  ;;
            *.bz2)      bunzip2 "$1"   ;;
            *.gz)       gunzip "$1"    ;;
            *.zip)      unzip "$1"     ;;
            *.7z)       7z x "$1"      ;;
            *.rar)      unrar x "$1"   ;;
            *)          echo "Format non reconnu : $1" >&2; return 1 ;;
        esac
    else
        echo "'$1' n'est pas un fichier." >&2
        return 1
    fi
}

# Chercher dans l'historique
h() { history | grep --color=auto "$*"; }

# Afficher la météo (via wttr.in)
meteo() { curl -s "wttr.in/${1:-Paris}?lang=fr"; }

# Sauvegarder un fichier avec horodatage
sauvegarder() {
    cp "$1" "${1}.$(date +%Y%m%d_%H%M%S).bak"
    echo "Sauvegarde créée : ${1}.$(date +%Y%m%d_%H%M%S).bak"
}
```

```{prf:remark}
:label: remark-15-02
La différence entre un **alias** et une **fonction** est que les alias ne peuvent pas traiter d'arguments : `alias sauve='cp $1 $1.bak'` ne fonctionnera pas car `$1` est développé lors de la définition de l'alias, pas lors de son appel. Pour tout raccourci nécessitant des arguments, des conditions ou plusieurs commandes, il faut utiliser une **fonction**. Une règle pratique : alias pour les options fixes d'une commande existante, fonction pour toute logique.
```

## Le `PATH` : résolution des commandes

Quand on tape `vim` dans le terminal, Bash doit retrouver l'exécutable correspondant. Il parcourt pour cela les répertoires listés dans la variable `PATH`, de gauche à droite, et utilise le premier exécutable `vim` trouvé.

```{prf:definition} Variable PATH
:label: definition-15-02
La variable d'environnement `PATH` est une liste de répertoires séparés par des deux-points (`:`) dans lesquels Bash cherche les commandes à exécuter. Quand une commande ne contient pas de `/`, Bash parcourt les répertoires du `PATH` dans l'ordre et exécute le premier fichier exécutable correspondant trouvé. Si aucun n'est trouvé, Bash retourne l'erreur `command not found`.
```

```bash
# Afficher le PATH actuel
echo $PATH
# /home/alice/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:...

# Trouver l'emplacement d'une commande
which vim          # /usr/bin/vim
which python3      # /usr/bin/python3
type vim           # vim is /usr/bin/vim  (aussi : alias, fonctions, builtins)
type ll            # ll is aliased to 'ls -alF'
command -v vim     # /usr/bin/vim  (portable, retourne 1 si absent)

# Ajouter un répertoire au début du PATH (priorité max)
export PATH="$HOME/.local/bin:$PATH"

# Ajouter à la fin (priorité minimale)
export PATH="$PATH:/opt/mon_outil/bin"

# Ajouter plusieurs répertoires
export PATH="$HOME/.local/bin:$HOME/bin:/opt/outil/bin:$PATH"

# Rendre la modification permanente (dans ~/.bashrc ou ~/.bash_profile)
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc

# Conventions de répertoires dans le PATH
# /bin et /usr/bin    : commandes système essentielles
# /sbin et /usr/sbin  : commandes d'administration
# /usr/local/bin      : logiciels compilés localement
# ~/.local/bin        : commandes personnelles (PEP 517, pipx, etc.)
# ~/bin               : scripts personnels (créer et ajouter si besoin)
```

### Gestion des binaires utilisateur

```bash
# Créer un répertoire de scripts personnels
mkdir -p ~/.local/bin

# Exemple : créer un script personnel
cat > ~/.local/bin/bienvenue << 'EOF'
#!/usr/bin/env bash
echo "Bonjour, $USER ! Il est $(date +%H:%M)."
EOF
chmod +x ~/.local/bin/bienvenue

# Vérifier que le répertoire est dans le PATH
echo $PATH | grep -o "$HOME/.local/bin" && echo "OK" || echo "À ajouter au PATH"

# Exécuter
bienvenue
```

## Variables d'environnement système importantes

Les variables d'environnement sont des variables exportées, accessibles non seulement dans le shell courant mais aussi dans tous les processus enfants qu'il lance.

```{prf:definition} Variable d'environnement
:label: definition-15-03
Une **variable d'environnement** est une variable shell marquée pour être **exportée** (`export`) : elle est transmise automatiquement à tout processus enfant créé par le shell. Les commandes, éditeurs, compilateurs et programmes utilisent ces variables pour adapter leur comportement sans nécessiter de paramètres explicites.
```

```bash
# Afficher toutes les variables d'environnement
env
printenv
export -p    # Afficher les variables exportées avec leur valeur

# Afficher une variable spécifique
echo $HOME
printenv HOME

# Variables essentielles
echo $HOME      # /home/alice — répertoire personnel
echo $USER      # alice — nom de l'utilisateur courant
echo $LOGNAME   # alice — identique à USER dans la plupart des cas
echo $SHELL     # /bin/bash — shell courant
echo $UID       # 1000 — identifiant numérique de l'utilisateur
echo $HOSTNAME  # machine.local — nom de la machine
echo $PWD       # /home/alice/projets — répertoire courant
echo $OLDPWD    # répertoire précédent (utilisé par cd -)
echo $LANG      # fr_FR.UTF-8 — locale système
echo $LC_ALL    # (peut écraser LANG)
echo $TERM      # xterm-256color — type de terminal
echo $COLORTERM # truecolor — support des couleurs
echo $EDITOR    # vim — éditeur de texte en ligne de commande
echo $VISUAL    # vim — éditeur visuel (pour git commit, cron, etc.)
echo $PAGER     # less — paginateur (man, git log, etc.)
echo $PS1       # Chaîne du prompt principal
echo $PS2       # Prompt secondaire (lignes continuées)
echo $IFS       # Séparateur de champs interne (' \t\n' par défaut)
echo $RANDOM    # Nombre aléatoire (0-32767) — re-évalué à chaque lecture
echo $LINENO    # Numéro de ligne dans le script courant
echo $SECONDS   # Secondes depuis le démarrage du shell
```

### Variables de la session

```bash
# Définir et exporter une variable
export MA_VARIABLE="valeur"

# Définir une variable pour une seule commande (sans export permanent)
LANG=en_US.UTF-8 sort fichier.txt

# Supprimer une variable
unset MA_VARIABLE

# Variables importantes pour les outils de développement
export GOPATH="$HOME/go"
export GOBIN="$GOPATH/bin"
export CARGO_HOME="$HOME/.cargo"
export JAVA_HOME="/usr/lib/jvm/java-21-openjdk-amd64"
export PYTHONPATH="$HOME/lib/python"
export NODE_PATH="$HOME/.nvm/versions/node/v20/lib/node_modules"

# Variables de proxy (pour les environnements d'entreprise)
export http_proxy="http://proxy.entreprise.fr:8080"
export https_proxy="http://proxy.entreprise.fr:8080"
export no_proxy="localhost,127.0.0.1,.local,.entreprise.fr"
```

## tmux : sessions persistantes et multiplexage de terminal

`tmux` (*Terminal MUltiplexer*) est un outil indispensable pour quiconque travaille sur des serveurs distants. Il permet de créer des **sessions persistantes** qui survivent à la déconnexion SSH, de diviser un terminal en plusieurs **fenêtres** et **panneaux**, et de reprendre le travail là où on l'avait laissé.

```{prf:definition} tmux
:label: definition-15-04
**tmux** est un multiplexeur de terminal qui fonctionne selon une hiérarchie à trois niveaux :
- **Session** : contexte de travail indépendant du terminal. Une session peut contenir plusieurs fenêtres. Les sessions persistent même quand aucun terminal n'y est attaché.
- **Fenêtre** (*window*) : équivalent d'un onglet. Chaque fenêtre occupe la totalité de l'écran et peut contenir un ou plusieurs panneaux.
- **Panneau** (*pane*) : subdivision d'une fenêtre. Chaque panneau exécute un shell ou une commande indépendante.
```

### Commandes essentielles

```bash
# Démarrer une nouvelle session
tmux
tmux new-session
tmux new -s nom_session    # Avec un nom explicite (recommandé)

# Lister les sessions actives
tmux list-sessions
tmux ls

# Se rattacher à une session existante
tmux attach
tmux attach -t nom_session
tmux a -t nom_session      # Forme courte

# Se détacher d'une session (la session continue en arrière-plan)
# Ctrl+b puis d  (d = detach)

# Tuer une session
tmux kill-session -t nom_session
tmux kill-server            # Tuer toutes les sessions
```

### Le préfixe tmux

Toutes les commandes tmux commencent par un **préfixe** : `Ctrl+b` par défaut. On appuie sur `Ctrl+b`, on relâche, puis on tape la touche de commande.

**Gestion des sessions :**

| Raccourci | Action |
|-----------|--------|
| `Ctrl+b d` | Détacher de la session (session reste active) |
| `Ctrl+b $` | Renommer la session courante |
| `Ctrl+b s` | Lister et changer de session |
| `Ctrl+b (` / `)` | Session précédente / suivante |

**Gestion des fenêtres :**

| Raccourci | Action |
|-----------|--------|
| `Ctrl+b c` | Créer une nouvelle fenêtre |
| `Ctrl+b n` | Fenêtre suivante |
| `Ctrl+b p` | Fenêtre précédente |
| `Ctrl+b 0-9` | Aller à la fenêtre numérotée |
| `Ctrl+b ,` | Renommer la fenêtre courante |
| `Ctrl+b w` | Lister toutes les fenêtres |
| `Ctrl+b &` | Fermer la fenêtre courante |

**Gestion des panneaux :**

| Raccourci | Action |
|-----------|--------|
| `Ctrl+b %` | Diviser horizontalement (deux panneaux côte à côte) |
| `Ctrl+b "` | Diviser verticalement (deux panneaux haut/bas) |
| `Ctrl+b ←↑↓→` | Naviguer entre les panneaux |
| `Ctrl+b Ctrl+←↑↓→` | Redimensionner le panneau |
| `Ctrl+b z` | Agrandir/réduire un panneau en plein écran |
| `Ctrl+b x` | Fermer le panneau courant |
| `Ctrl+b {` / `}` | Déplacer le panneau |
| `Ctrl+b q` | Afficher les numéros de panneaux |

**Copie et défilement :**

| Raccourci | Action |
|-----------|--------|
| `Ctrl+b [` | Mode copie (défilement dans l'historique) |
| `Ctrl+b ]` | Coller le tampon tmux |
| `Ctrl+b PageUp` | Remonter dans l'historique |
| `q` (en mode copie) | Quitter le mode copie |

### Le fichier `~/.tmux.conf`

```bash
# ~/.tmux.conf — configuration personnalisée de tmux

# Changer le préfixe de Ctrl+b à Ctrl+a (comme screen)
unbind C-b
set-option -g prefix C-a
bind-key C-a send-prefix

# Recharger la configuration
bind r source-file ~/.tmux.conf \; display-message "Config rechargée"

# Division plus intuitive (| pour horizontal, - pour vertical)
bind | split-window -h -c "#{pane_current_path}"
bind - split-window -v -c "#{pane_current_path}"
unbind '"'
unbind %

# Navigation entre panneaux avec Alt+flèches (sans préfixe)
bind -n M-Left  select-pane -L
bind -n M-Right select-pane -R
bind -n M-Up    select-pane -U
bind -n M-Down  select-pane -D

# Numérotation des fenêtres à partir de 1
set -g base-index 1
set -g pane-base-index 1

# Couleurs 256
set -g default-terminal "screen-256color"

# Barre de statut personnalisée
set -g status-bg '#2d2d2d'
set -g status-fg '#ffffff'
set -g status-left '#[fg=#98be65,bold] [#S] '
set -g status-right '#[fg=#c678dd] %Y-%m-%d #[fg=#61afef] %H:%M '
set -g status-right-length 50

# Historique plus long
set-option -g history-limit 50000

# Mode souris (optionnel)
set -g mouse on
```

```{prf:example} Workflow serveur distant avec tmux
:label: example-15-01
Voici un workflow typique pour administrer un serveur distant :

```bash
# 1. Connexion SSH au serveur
ssh alice@serveur.exemple.fr

# 2. Créer une session tmux nommée
tmux new -s travail

# 3. Ouvrir une fenêtre pour les logs
# Ctrl+b c pour une nouvelle fenêtre
# Dans la nouvelle fenêtre :
tail -f /var/log/app.log

# 4. Créer une fenêtre pour les commandes
# Ctrl+b c, puis Ctrl+b , pour la renommer "commandes"

# 5. Diviser pour surveiller les ressources
# Ctrl+b % pour diviser horizontalement
htop

# 6. Travailler normalement. Si la connexion tombe :
# Se reconnecter en SSH et :
tmux attach -t travail
# On retrouve exactement l'état d'avant !
```
```

```{prf:remark}
:label: remark-15-03
**Pourquoi tmux est indispensable sur un serveur distant ?** Sans tmux, si votre connexion SSH est interrompue (coupure réseau, fermeture accidentelle du terminal, timeout), tous vos processus en avant-plan reçoivent `SIGHUP` et se terminent. Avec tmux, la session tourne dans un processus `tmux server` sur le serveur, complètement indépendant de votre connexion SSH. Vous pouvez vous déconnecter (`Ctrl+b d`), éteindre votre ordinateur, vous reconnecter plus tard depuis une autre machine et reprendre exactement là où vous étiez, avec tous vos processus en cours.
```

## `screen` : l'alternative classique

`screen` est l'ancêtre de tmux, plus simple mais moins puissant. Il reste utile sur des systèmes anciens ou minimalistes où tmux n'est pas disponible.

```bash
# Démarrer screen
screen

# Démarrer avec un nom
screen -S nom_session

# Lister les sessions
screen -ls

# Se rattacher à une session
screen -r nom_session
screen -r    # S'il n'y en a qu'une

# Rattachement forcé (si la session est marquée "Attached")
screen -dr nom_session

# Raccourcis dans screen (préfixe : Ctrl+a)
# Ctrl+a d     : détacher
# Ctrl+a c     : nouvelle fenêtre
# Ctrl+a n     : fenêtre suivante
# Ctrl+a p     : fenêtre précédente
# Ctrl+a "     : liste des fenêtres
# Ctrl+a |     : diviser verticalement
# Ctrl+a S     : diviser horizontalement
# Ctrl+a Tab   : passer au panneau suivant
# Ctrl+a k     : tuer la fenêtre courante
```

## `direnv` : variables d'environnement par répertoire

`direnv` est un outil qui charge et décharge automatiquement des variables d'environnement selon le répertoire courant. C'est la solution la plus propre pour gérer des environnements de développement différents par projet.

```{prf:definition} direnv
:label: definition-15-05
**direnv** est un outil d'extension du shell qui surveille les déplacements dans le système de fichiers. Quand on entre dans un répertoire contenant un fichier `.envrc`, direnv charge automatiquement les variables définies dans ce fichier. Quand on quitte ce répertoire, ces variables sont automatiquement déchargées. direnv supporte Bash, Zsh, Fish et d'autres shells.
```

### Installation et configuration

```bash
# Installer direnv
sudo apt install direnv

# Activer direnv dans Bash (ajouter à ~/.bashrc)
eval "$(direnv hook bash)"

# Recharger la configuration
source ~/.bashrc
```

### Utilisation

```bash
# Dans un répertoire de projet, créer un .envrc
cd ~/projets/mon-api/
cat > .envrc << 'EOF'
export DATABASE_URL="postgresql://localhost:5432/mon_api_dev"
export REDIS_URL="redis://localhost:6379"
export API_KEY="dev_key_locale_123"
export LOG_LEVEL="debug"
export PORT=3000
EOF

# direnv bloque par sécurité les nouveaux .envrc :
# direnv: error /home/alice/projets/mon-api/.envrc is blocked.
# Autoriser explicitement
direnv allow .
# direnv: loading ~/projets/mon-api/.envrc
# direnv: export +API_KEY +DATABASE_URL +LOG_LEVEL +PORT +REDIS_URL

# Naviguer hors du répertoire décharge les variables
cd ~
# direnv: unloading

# Revenir dans le répertoire les recharge
cd ~/projets/mon-api/
# direnv: loading ~/projets/mon-api/.envrc

# Recharger manuellement après modification du .envrc
direnv reload
```

### `.envrc` avancé

```bash
# .envrc avec fonctionnalités avancées

# Charger un fichier .env (style Docker Compose)
dotenv .env.local
dotenv_if_exists .env.local   # Ne pas échouer si le fichier n'existe pas

# Ajouter au PATH
PATH_add ./node_modules/.bin
PATH_add ./bin
PATH_add ./venv/bin    # Virtualenv Python

# Activer un virtualenv Python
source_env_if_exists venv/bin/activate
layout python3    # Créer et activer un venv automatiquement

# Variables conditionnelles selon le hostname
if [[ "$(hostname)" == "prod-server" ]]; then
    export ENV="production"
else
    export ENV="development"
fi

# Utiliser un secret depuis un gestionnaire de mots de passe
export API_SECRET=$(pass show monprojet/api_secret)

# Hériter d'un .envrc parent
source_up_if_exists   # Charger le .envrc du répertoire parent si existant
```

```{prf:remark}
:label: remark-15-04
Il ne faut **jamais committer** le fichier `.envrc` s'il contient des valeurs secrètes (clés API, mots de passe). La bonne pratique est de versionner un fichier `.envrc.example` avec des valeurs fictives, et d'ajouter `.envrc` au `.gitignore`. Pour la même raison, `direnv` ne charge pas automatiquement un `.envrc` modifié sans que l'utilisateur ait explicitement accordé sa confiance via `direnv allow` : cela prévient les attaques par dépôts malveillants.
```

## Personnalisation du prompt

Le prompt Bash est contrôlé par la variable `PS1`. Il peut afficher des informations dynamiques grâce à des séquences spéciales.

```bash
# Variables disponibles dans PS1
# \u = nom d'utilisateur
# \h = nom d'hôte (court)
# \H = nom d'hôte complet (FQDN)
# \w = répertoire courant (complet)
# \W = répertoire courant (nom seulement)
# \$ = # si root, $ sinon
# \d = date
# \t = heure HH:MM:SS
# \j = nombre de jobs actifs
# \! = numéro de commande dans l'historique

# Codes couleur ANSI dans PS1
# \[\033[01;32m\] = vert gras
# \[\033[01;34m\] = bleu gras
# \[\033[01;31m\] = rouge gras
# \[\033[00m\]    = réinitialisation

# Prompt coloré classique : user@host:dossier$
PS1='\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '

# Prompt avec heure et jobs
PS1='[\t \j jobs] \u@\h:\w\$ '

# Prompt sur deux lignes
PS1='\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\n\$ '

# Inclure la branche git dans le prompt (nécessite git)
parse_git_branch() {
    git branch 2>/dev/null | grep '^*' | sed 's/* //'
}
PS1='\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[33m\]$(parse_git_branch)\[\033[00m\]\$ '
```

## Visualisation : initialisation du shell Bash

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

fig, axes = plt.subplots(1, 2, figsize=(16, 10))
palette = sns.color_palette("muted", 10)

def draw_box(ax, x, y, w, h, titre, detail, color, fontsize_t=9.5):
    rect = patches.FancyBboxPatch(
        (x, y), w, h,
        boxstyle="round,pad=0.1", linewidth=2,
        edgecolor=color, facecolor=color, alpha=0.2
    )
    ax.add_patch(rect)
    border = patches.FancyBboxPatch(
        (x, y), w, h,
        boxstyle="round,pad=0.1", linewidth=2,
        edgecolor=color, facecolor='none'
    )
    ax.add_patch(border)
    ax.text(x + w/2, y + h * 0.65, titre, ha='center', va='center',
            fontsize=fontsize_t, fontweight='bold',
            fontfamily='monospace', color=color)
    if detail:
        ax.text(x + w/2, y + h * 0.3, detail, ha='center', va='center',
                fontsize=7.5, color='#555', style='italic')

def arrow(ax, x1, y1, x2, y2, color='#555', label=''):
    ax.annotate('', xy=(x2, y2), xytext=(x1, y1),
                arrowprops=dict(arrowstyle='->', color=color, lw=2))
    if label:
        mx, my = (x1+x2)/2, (y1+y2)/2
        ax.text(mx + 0.1, my, label, fontsize=8, color=color, va='center')

# ---- Graphique 1 : shell de LOGIN interactif ----
ax = axes[0]
ax.set_xlim(0, 10)
ax.set_ylim(0, 11)
ax.axis('off')
ax.set_title('Shell interactif de LOGIN\n(ssh, tty, su -)', fontsize=12,
             fontweight='bold', color=palette[0])

etapes_login = [
    (2.5, 9.5, 5.0, 0.8, "/etc/profile",        "Config globale système",        palette[0]),
    (2.5, 8.2, 5.0, 0.8, "/etc/profile.d/*.sh",  "Scripts système additionnels",  palette[1]),
    (2.5, 6.5, 5.0, 0.8, "~/.bash_profile",      "Config perso login (priorité 1)", palette[2]),
    (2.5, 5.2, 5.0, 0.8, "~/.bash_login",        "Si pas de .bash_profile",       palette[3]),
    (2.5, 3.9, 5.0, 0.8, "~/.profile",           "Si pas des deux précédents",    palette[4]),
    (2.5, 2.3, 5.0, 0.8, "~/.bashrc",            "Sourcé par .bash_profile",      palette[5]),
    (2.5, 1.0, 5.0, 0.8, "~/.bash_logout",       "À la fermeture de session",     palette[6]),
]

for i, (x, y, w, h, titre, detail, col) in enumerate(etapes_login[:-1]):
    draw_box(ax, x, y, w, h, titre, detail, col)

# bash_logout séparé (en bas, avec flèche différente)
x, y, w, h, titre, detail, col = etapes_login[-1]
draw_box(ax, x, y, w, h, titre, detail, col)

# Flèches de séquence
for i in range(len(etapes_login) - 3):
    x1 = etapes_login[i][0] + etapes_login[i][2] / 2
    y1 = etapes_login[i][1]
    y2 = etapes_login[i+1][1] + etapes_login[i+1][3]
    arrow(ax, x1, y1, x1, y2, palette[i+1])

# Flèche spéciale vers .bashrc depuis .bash_profile
ax.annotate('', xy=(5.0, 3.05), xytext=(5.0, 5.2),
            arrowprops=dict(arrowstyle='->', color=palette[5], lw=1.8,
                            linestyle='dashed'))
ax.text(5.3, 4.1, 'source ~/.bashrc', fontsize=7.5, color=palette[5],
        style='italic')

# Flèche de sortie vers .bash_logout
ax.annotate('', xy=(5.0, 1.8), xytext=(5.0, 0.5),
            arrowprops=dict(arrowstyle='<-', color=palette[6], lw=1.8,
                            linestyle='dashed'))
ax.text(5.1, 1.3, 'à la sortie', fontsize=7.5, color=palette[6], style='italic')

# ---- Graphique 2 : shell NON-LOGIN interactif et non-interactif ----
ax = axes[1]
ax.set_xlim(0, 10)
ax.set_ylim(0, 11)
ax.axis('off')
ax.set_title('Shell non-login interactif vs non-interactif',
             fontsize=12, fontweight='bold', color=palette[2])

# Bloc titre gauche
ax.text(2.5, 10.3, 'Non-login interactif', ha='center', fontsize=10.5,
        fontweight='bold', color=palette[2],
        bbox=dict(boxstyle='round,pad=0.3', facecolor=palette[2], alpha=0.2))
ax.text(2.5, 9.7, '(terminal GNOME, bash dans bash)', ha='center',
        fontsize=8, color='#555', style='italic')

draw_box(ax, 0.5, 8.5, 4.0, 0.8, "/etc/bash.bashrc", "Config Bash système", palette[1])
draw_box(ax, 0.5, 7.2, 4.0, 0.8, "~/.bashrc", "Config perso (TOUT va ici)", palette[2])
arrow(ax, 2.5, 8.5, 2.5, 8.0, palette[2])

# Bloc titre droite
ax.text(7.5, 10.3, 'Non-interactif', ha='center', fontsize=10.5,
        fontweight='bold', color=palette[5],
        bbox=dict(boxstyle='round,pad=0.3', facecolor=palette[5], alpha=0.2))
ax.text(7.5, 9.7, '(scripts bash, cron, CI/CD)', ha='center',
        fontsize=8, color='#555', style='italic')

draw_box(ax, 5.5, 8.5, 4.0, 0.8, "$BASH_ENV", "Si défini, sourcé", palette[5])
draw_box(ax, 5.5, 7.2, 4.0, 0.8, "(aucun autre fichier)", "Environnement hérité", palette[6])
arrow(ax, 7.5, 8.5, 7.5, 8.0, palette[5])

# Séparateur
ax.plot([5.0, 5.0], [6.5, 11.0], color='#ccc', linewidth=1.5, linestyle='--')

# Récap contenu de .bashrc
draw_box(ax, 0.5, 4.5, 9.0, 2.2, "~/.bashrc — contenu typique", "", palette[2], fontsize_t=10)

contenus = [
    "Alias (ll, gs, grep --color…)",
    "Fonctions personnalisées",
    "Options shell (shopt -s histappend…)",
    "Historique (HISTSIZE, HISTCONTROL)",
    "Prompt (PS1)",
    "PATH (export PATH=…)",
    "Chargement outils (nvm, pyenv, cargo…)",
]
for i, item in enumerate(contenus):
    col_i = i % 2
    row_i = i // 2
    x_item = 0.7 + col_i * 4.5
    y_item = 6.35 - row_i * 0.45
    ax.text(x_item, y_item, f"• {item}", va='center', fontsize=8.5,
            color='#333')

# Récap .bash_profile
draw_box(ax, 0.5, 2.2, 9.0, 1.8, "~/.bash_profile — contenu typique", "", palette[0], fontsize_t=10)
items_profile = [
    "source ~/.bashrc",
    "export EDITOR, VISUAL, PAGER",
    "export PATH (répertoires login)",
    "Initialisation outils lourds (pyenv, rbenv…)",
]
for i, item in enumerate(items_profile):
    col_i = i % 2
    row_i = i // 2
    x_item = 0.7 + col_i * 4.5
    y_item = 3.7 - row_i * 0.45
    ax.text(x_item, y_item, f"• {item}", va='center', fontsize=8.5,
            color='#333')

# Note sur .profile
draw_box(ax, 0.5, 0.5, 9.0, 1.3,
         "~/.profile — utilisé si pas de .bash_profile",
         "Syntaxe POSIX sh (pas spécifique à Bash)", palette[3])

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

## Résumé

Ce chapitre a couvert la configuration complète de l'environnement shell Bash :

- Bash distingue les shells de **login** (qui lisent `/etc/profile` puis `~/.bash_profile`) des shells **non-login interactifs** (qui lisent `~/.bashrc`). La convention est de faire sourcer `~/.bashrc` depuis `~/.bash_profile` pour unifier la configuration.
- Les **alias** permettent des raccourcis de commandes simples et sont rendus permanents dans `~/.bashrc` ou `~/.bash_aliases`. Les **fonctions** permettent des raccourcis plus complexes acceptant des arguments.
- Le **PATH** est la liste ordonnée des répertoires dans lesquels Bash cherche les commandes. `~/.local/bin` est le répertoire conventionnel pour les commandes personnelles.
- Les **variables d'environnement** (`HOME`, `USER`, `SHELL`, `PATH`, `LANG`, `EDITOR`, `TERM`, etc.) configurent le comportement du shell et de tous les processus enfants.
- **tmux** est l'outil indispensable pour les sessions persistantes sur serveur distant : il permet de se détacher (`Ctrl+b d`) et de se rattacher sans perdre le travail en cours. La hiérarchie sessions/fenêtres/panneaux offre une organisation flexible de l'espace de travail.
- **direnv** permet de charger et décharger automatiquement des variables d'environnement selon le répertoire courant, via des fichiers `.envrc` par projet, sans polluer l'environnement global.

Ces outils de configuration forment la base d'un environnement de travail Linux professionnel, reproductible et efficace. Le chapitre suivant abordera les **expressions régulières et les outils de traitement de texte** : `grep`, `sed`, `awk` et leurs usages avancés dans les scripts de traitement de données.
