Blocs de code Shell & Bash dans Org Babel
Org Babel offre un support complet des scripts shell sur les shells Unix et POSIX : Bash, Zsh, Sh, Fish, Dash, Csh, Ksh et Ash.
Les blocs shell transforment Org Mode en un carnet DevOps interactif, un runbook et une console d'administration système capable d'exécution locale, de traitement asynchrone en arrière-plan, et d'administration de serveurs distants via SSH avec TRAMP.
1. Prérequis et Configuration
1.1 Configuration Emacs
Contrairement à d'autres langages Babel qui nécessitent des modules séparés, tous les shells sont gérés par une seule bibliothèque Babel (ob-shell). Vous avez seulement besoin de (shell . t) dans votre org-babel-load-languages :
(org-babel-do-load-languages
'org-babel-load-languages
'((emacs-lisp . t)
(shell . t)))
(Note : Avant Org 8.2, cela s'appelait sh. Utilisez shell dans toutes les versions modernes d'Org).
1.2 Spécification du Shell
Spécifiez le nom exact du binaire shell comme étiquette de langage du bloc (#+BEGIN_SRC <shell>) :
# Bash :
#+BEGIN_SRC bash
echo "Exécuté avec $BASH_VERSION"
#+END_SRC
# Zsh :
#+BEGIN_SRC zsh
echo "Exécuté avec Zsh $ZSH_VERSION"
#+END_SRC
# POSIX / Dash (exécution plus rapide et légère) :
#+BEGIN_SRC dash
echo "Exécuté avec dash"
#+END_SRC
# Fish shell :
#+BEGIN_SRC fish
set greeting "Bonjour de fish"
echo $greeting
#+END_SRC
Le binaire shell spécifié doit être accessible dans votre $PATH système.
2. Modes d'Exécution : Isolé, Session et Asynchrone
2.1 Sous-shell Isolé (Par défaut, :session none)
Chaque bloc s'exécute dans un nouveau processus sous-shell temporaire. Les variables d'environnement et les répertoires de travail ne sont pas conservés :
#+BEGIN_SRC bash :results output
echo "PID : $$"
export WORKSPACE="/tmp/build"
#+END_SRC
#+BEGIN_SRC bash :results output
# $WORKSPACE est vide ici car ceci s'exécute dans un nouveau processus sous-shell :
echo "PID : $$, WORKSPACE : $WORKSPACE"
#+END_SRC
2.2 Sessions Interactives (:session <nom>)
S'exécute dans un tampon comint Emacs persistant (par exemple *session-dev*). Les variables, fonctions et répertoires de travail persistent à travers plusieurs blocs :
#+BEGIN_SRC bash :session *session-dev* :results output
export CLUSTER_ID="prod-est-01"
cd /var/log
#+END_SRC
#+BEGIN_SRC bash :session *session-dev* :results output
# Utilise l'état défini dans le bloc précédent :
echo "Déploiement vers $CLUSTER_ID depuis $(pwd)"
#+END_SRC
#+RESULTS:
: Déploiement vers prod-est-01 depuis /var/log
2.3 Exécution Asynchrone Non-Bloquante (:async yes)
Les scripts longs (sauvegardes, constructions de conteneurs, entraînement de modèles) figent Emacs lorsqu'ils sont exécutés de manière synchrone. Ajoutez :async yes pour les exécuter en arrière-plan sans verrouiller l'éditeur !
Règles :
:asyncnécessite:session <nom>.- Pendant l'exécution, Babel insère un jeton UUID temporaire dans le bloc
#+RESULTS:. - Une fois l'exécution terminée en arrière-plan, l'UUID est automatiquement remplacé par la sortie standard de la commande.
#+BEGIN_SRC bash :session *tache-bg* :async yes :results output
echo "Démarrage de la lourde sauvegarde de base de données à $(date)..."
sleep 5
echo "Sauvegarde terminée avec succès à $(date) !"
#+END_SRC
#+RESULTS:
: Démarrage de la lourde sauvegarde de base de données à Sun May 18 14:00:00 UTC 2025...
: Sauvegarde terminée avec succès à Sun May 18 14:00:05 UTC 2025 !
Pour exécuter de manière synchrone dans une session, passez :async no.
3. Paramètres d'En-Tête Spécifiques au Shell
Org Babel fournit trois paramètres d'en-tête uniques spécialement conçus pour les opérations shell :
3.1 :stdin <nom-élément> (Entrée Standard)
Redirige des éléments Org nommés (tableaux, texte brut, ou résultats d'autres blocs de code) vers l'entrée standard de la commande shell :
# Entrée texte brut :
#+NAME: payload_serveur_brut
{"nœud": "alpha", "cœurs": 8, "statut": "actif"}
# Rediriger le payload directement dans jq :
#+BEGIN_SRC bash :stdin payload_serveur_brut :results output
jq -r '.nœud + " a " + (.cœurs|tostring) + " cœurs"'
#+END_SRC
#+RESULTS:
: alpha a 8 cœurs
(Note : Les blocs utilisant :stdin s'évaluent dans un sous-shell temporaire, ignorant :session).
3.2 :cmdline <args> (Arguments de Ligne de Commande)
Passe des arguments positionnels ($1, $2, $@) dans le corps du script :
#+BEGIN_SRC bash :cmdline "staging" 8080 :results output
ENV="$1"
PORT="$2"
echo "Démarrage du serveur sur le port $PORT en mode $ENV..."
#+END_SRC
#+RESULTS:
: Démarrage du serveur sur le port 8080 en mode staging...
⚠️ Avertissement de Sécurité : Les arguments :cmdline sont développés directement par le shell hôte. Ne passez pas de chaînes non fiables ou non échappées pour éviter une injection de commande.
3.3 :shebang <directive> (Scripts Autonomes et Sortie Propre)
Force le bloc à être écrit dans un fichier exécutable temporaire et exécuté via sa directive shebang :
#+BEGIN_SRC bash :shebang "#!/usr/bin/env bash" :results output
for i in {1..3}; do
echo "Étape $i"
done
#+END_SRC
Pourquoi :shebang est essentiel :
Dans les blocs :session multilignes sans shebang, les lignes sont envoyées ligne par ligne au shell interactif, ce qui peut produire des chaînes d'invite parasites (par exemple, bash-5.1$ ) dans vos résultats. Spécifier :shebang ou exécuter sans session produit une sortie complètement propre.
4. Administration de Serveurs Distants via TRAMP (:dir)
Vous pouvez exécuter des commandes sur des serveurs distants, des machines virtuelles ou des conteneurs Docker en spécifiant un chemin TRAMP dans :dir :
:dir /ssh:<utilisateur>@<hôte>:<chemin-distant>
:dir /docker:<id-conteneur>:<chemin>
:dir /sudo::/etc
Exemple : Workflow d'Administration Système Distante
# Vérifier l'utilisation du disque et la mémoire sur un serveur distant :
#+BEGIN_SRC bash :dir /ssh:[email protected]:/tmp :results output
hostname
uptime
df -h / | awk 'NR==2 {print "Utilisation Disque Racine : " $5}'
#+END_SRC
#+RESULTS:
: bastion.example.com
: 14:22:05 up 42 days, 3:18, 1 user, load average: 0.12, 0.08, 0.05
: Utilisation Disque Racine : 48%
Sessions Distantes
Combinez :dir avec :session pour maintenir une connexion SSH active à travers plusieurs blocs sans vous reconnecter à chaque étape :
#+BEGIN_SRC bash :dir /ssh:[email protected]:/home/admin :session *bastion-distant* :results output
WORKDIR=$(mktemp -d)
cd "$WORKDIR"
pwd
#+END_SRC
5. Formats de Résultats et Extraction de Données Tabulaires
5.1 Sortie Tabulaire à Tableau Org (:results output table)
Lorsque les commandes shell émettent une sortie tabulaire structurée ou séparée par des tabulations, Babel les analyse en tableaux Org natifs :
#+BEGIN_SRC bash :results output table
printf "PID\tUtilisateur\tCommande\n"
printf "101\troot\tsystemd\n"
printf "405\tpostgres\tpostgres\n"
printf "892\tnginx\tnginx: worker\n"
#+END_SRC
#+RESULTS:
| PID | Utilisateur | Commande |
|-----+-------------+----------------|
| 101 | root | systemd |
| 405 | postgres | postgres |
| 892 | nginx | nginx: worker |
5.2 Redirection de la Sortie vers un Lien de Fichier (:results file :file <chemin>)
#+BEGIN_SRC bash :results file :file rapports/infos_systeme.txt
uname -a > rapports/infos_systeme.txt
cat /etc/os-release >> rapports/infos_systeme.txt
#+END_SRC
#+RESULTS:
[[file:rapports/infos_systeme.txt]]
6. Passage de Variables et Tableaux Bash (:var)
6.1 Scalaires et Chaînes
#+BEGIN_SRC bash :var port=8000 hôte="localhost" :results output
echo "Connexion à $hôte sur le port $port..."
#+END_SRC
6.2 Tableaux Bash 1D
Bash prend en charge les paramètres de tableau 1D définis avec la syntaxe de liste :
#+BEGIN_SRC bash :results output :var serveurs='("web-01" "web-02" "db-01")'
echo "Nombre total de serveurs : ${#serveurs[@]}"
for s in "${serveurs[@]}"; do
echo "Ping de $s..."
done
#+END_SRC
#+RESULTS:
: Nombre total de serveurs : 3
: Ping de web-01...
: Ping de web-02...
: Ping de db-01...
7. Pièges Courants et Bonnes Pratiques de Sécurité
| Piège | Cause | Solution |
|---|---|---|
| Emacs se fige sur une commande SSH ou ffmpeg | La commande tente de lire stdin depuis le sous-shell | Redirigez stdin explicitement (</dev/null) ou utilisez ssh -n. |
Caractères d'invite bash-5.1$ parasites dans les résultats | Écho ligne par ligne interactif dans :session | Ajoutez :shebang #!/usr/bin/env bash ou évaluez en dehors d'une session. |
| Développement intempestif de variables dans les heredocs | L'utilisation de <<EOF permet au shell de développer $VARS prématurément | Utilisez un délimiteur entre guillemets <<"EOF" pour garder le texte littéral et éviter un développement précoce. |
Syntaxe non-POSIX échouant dans les blocs sh | #+BEGIN_SRC sh correspond à /bin/sh (souvent dash sur Debian/Ubuntu) | Utilisez #+BEGIN_SRC bash si votre script utilise des bashismes ([[, ((, tableaux). |
Injection de commande via :cmdline | Les arguments shell sont évalués textuellement | Citationnez les arguments avec soin ou passez les données via :stdin ou :var à la place. |
| La sortie du bloc asynchrone n'apparaît pas | :async yes nécessite une :session active | Fournissez toujours une :session <nom> lors de l'utilisation de :async yes. |
8. Exemple Complet de Runbook DevOps
#+TITLE: Runbook de Santé des Services de Production
#+PROPERTY: header-args:bash :session *runbook* :results output
* Étape 1 : Configuration de la Vérification de Santé
#+NAME: nœuds_cluster
| Nom d'hôte | Rôle |
|--------------------+---------|
| prod-app-01.lan | backend |
| prod-app-02.lan | backend |
| prod-db-01.lan | stockage |
* Étape 2 : Sonder les Nœuds du Cluster
#+BEGIN_SRC bash :var nœuds=nœuds_cluster :colnames yes :results output table
printf "Hôte\tRôle\tStatut\n"
for ligne in "${nœuds[@]}"; do
# Analyser les données de ligne séparées par tabulation/espace :
hôte=$(echo "$ligne" | awk '{print $1}')
rôle=$(echo "$ligne" | awk '{print $2}')
# Sondage ping rapide :
if ping -c 1 -W 1 "$hôte" > /dev/null 2>&1; then
statut="EN LIGNE"
else
statut="HORS LIGNE"
fi
printf "%s\t%s\t%s\n" "$hôte" "$rôle" "$statut"
done
#+END_SRC
#+RESULTS:
| Hôte | Rôle | Statut |
|------------------+---------+-----------|
| prod-app-01.lan | backend | EN LIGNE |
| prod-app-02.lan | backend | EN LIGNE |
| prod-db-01.lan | stockage | EN LIGNE |
* Étape 3 : Exécuter la Maintenance du Service de manière Asynchrone
#+BEGIN_SRC bash :session *runbook* :async yes
echo "Lancement de la purge roulante du cache sur les nœuds..."
sleep 4
echo "Purge du cache terminée avec succès."
#+END_SRC