Was macht Shell- & Bash-Codeblöcke in Org Babel?
Org Babel bietet umfassende Unterstützung für Shell-Scripting in Unix- und POSIX-Shells: Bash, Zsh, Sh, Fish, Dash, Csh, Ksh und Ash.
Shell-Blöcke verwandeln Org Mode in ein interaktives DevOps-Notizbuch, Runbook und eine Systemverwaltungskonsole, die lokale Ausführung, asynchrone Hintergrundverarbeitung und Remote-Server-Verwaltung über SSH mittels TRAMP ermöglicht.
1. Anforderungen & Konfiguration
1.1 Emacs-Konfiguration
Anders als bei anderen Babel-Sprachen, die separate Module benötigen, werden alle Shells durch eine einzige Babel-Bibliothek (ob-shell) gesteuert. Es ist lediglich (shell . t) in org-babel-load-languages erforderlich:
(org-babel-do-load-languages
'org-babel-load-languages
'((emacs-lisp . t)
(shell . t)))
(Hinweis: Vor Org 8.2 hieß dies sh. Verwenden Sie in allen modernen Org-Versionen shell).
1.2 Shell festlegen
Geben Sie den genauen Namen der Shell-Binärdatei als Block-Sprach-Tag an (#+BEGIN_SRC <shell>):
# Bash:
#+BEGIN_SRC bash
echo "Running in $BASH_VERSION"
#+END_SRC
# Zsh:
#+BEGIN_SRC zsh
echo "Running in Zsh $ZSH_VERSION"
#+END_SRC
# POSIX / Dash (schnellere, schlanke Ausführung):
#+BEGIN_SRC dash
echo "Running in dash"
#+END_SRC
# Fish shell:
#+BEGIN_SRC fish
set greeting "Hello from fish"
echo $greeting
#+END_SRC
Die angegebene Shell-Binärdatei muss über Ihren System-$PATH erreichbar sein.
2. Ausführungsmodi: Isoliert, Sitzung & Asynchron
2.1 Isolierte Subshell (Standard, :session none)
Jeder Block läuft in einem frischen, temporären Subshell-Prozess. Umgebungsvariablen und Arbeitsverzeichnisse werden nicht übernommen:
#+BEGIN_SRC bash :results output
echo "PID: $$"
export WORKSPACE="/tmp/build"
#+END_SRC
#+BEGIN_SRC bash :results output
# $WORKSPACE ist hier leer, da dieser Block in einem neuen Subshell-Prozess läuft:
echo "PID: $$, WORKSPACE: $WORKSPACE"
#+END_SRC
2.2 Interaktive Sitzungen (:session <name>)
Läuft innerhalb eines persistenten Emacs-comint-Puffers (z. B. *dev-session*). Variablen, Funktionen und Arbeitsverzeichnisse bleiben über mehrere Blöcke hinweg erhalten:
#+BEGIN_SRC bash :session *dev-session* :results output
export CLUSTER_ID="prod-east-01"
cd /var/log
#+END_SRC
#+BEGIN_SRC bash :session *dev-session* :results output
# Verwendet den Zustand aus dem vorherigen Block:
echo "Deploying to $CLUSTER_ID from $(pwd)"
#+END_SRC
#+RESULTS:
: Deploying to prod-east-01 from /var/log
2.3 Nicht-blockierende asynchrone Ausführung (:async yes)
Lang laufende Skripte (Backups, Container-Builds, Modelltraining) frieren Emacs ein, wenn sie synchron ausgeführt werden. Fügen Sie :async yes hinzu, um sie im Hintergrund auszuführen, ohne den Editor zu blockieren!
Regeln:
:asyncerfordert:session <name>.- Während der Ausführung fügt Babel einen temporären UUID-Token im
#+RESULTS:-Block ein. - Sobald die Ausführung im Hintergrund abgeschlossen ist, wird die UUID automatisch durch die Standardausgabe des Befehls ersetzt.
#+BEGIN_SRC bash :session *bg-job* :async yes :results output
echo "Starting heavy database backup at $(date)..."
sleep 5
echo "Backup finished successfully at $(date)!"
#+END_SRC
#+RESULTS:
: Starting heavy database backup at Sun May 18 14:00:00 UTC 2025...
: Backup finished successfully at Sun May 18 14:00:05 UTC 2025!
Um synchron in einer Sitzung auszuführen, übergeben Sie :async no.
3. Shell-spezifische Header-Argumente
Org Babel bietet drei einzigartige Header-Argumente, die speziell auf Shell-Operationen zugeschnitten sind:
3.1 :stdin <element-name> (Standard-Eingabe)
Leitet benannte Org-Elemente (Tabellen, Rohtext oder Ergebnisse anderer Code-Blöcke) an die Standardeingabe des Shell-Befehls weiter:
# Rohtext-Eingabe:
#+NAME: raw_server_payload
{"node": "alpha", "cores": 8, "status": "active"}
# Übergibt die Daten direkt an jq:
#+BEGIN_SRC bash :stdin raw_server_payload :results output
jq -r '.node + " has " + (.cores|tostring) + " cores"'
#+END_SRC
#+RESULTS:
: alpha has 8 cores
(Hinweis: Blöcke mit :stdin werden in einer temporären Subshell ausgewertet und ignorieren :session).
3.2 :cmdline <args> (Kommandozeilen-Argumente)
Übergibt Positionsargumente ($1, $2, $@) an den Skriptkörper:
#+BEGIN_SRC bash :cmdline "staging" 8080 :results output
ENV="$1"
PORT="$2"
echo "Starting server on port $PORT in $ENV mode..."
#+END_SRC
#+RESULTS:
: Starting server on port 8080 in staging mode...
⚠️ Sicherheitswarnung: :cmdline-Argumente werden von der Host-Shell direkt expandiert. Übergeben Sie keine unsicheren oder nicht maskierten Zeichenfolgen, um eine Befehlsinjektion zu vermeiden.
3.3 :shebang <directive> (Eigenständige Skripte & saubere Ausgabe)
Erzwingt, dass der Block in eine temporäre ausführbare Datei geschrieben und über ihre Shebang-Direktive ausgeführt wird:
#+BEGIN_SRC bash :shebang "#!/usr/bin/env bash" :results output
for i in {1..3}; do
echo "Step $i"
done
#+END_SRC
Warum :shebang essenziell ist:
In mehrzeiligen :session-Blöcken ohne Shebang werden Zeilen zeilenweise an die interaktive Shell gesendet, was zu störenden Eingabeaufforderungen (z. B. bash-5.1$ ) in Ihren Ergebnissen führen kann. Die Angabe von :shebang oder die Ausführung ohne Sitzung liefert vollständig saubere Ausgaben.
4. Remote-Server-Verwaltung via TRAMP (:dir)
Sie können Befehle auf Remote-Servern, virtuellen Maschinen oder Docker-Containern ausführen, indem Sie einen TRAMP-Pfad in :dir angeben:
:dir /ssh:<user>@<host>:<remote-path>
:dir /docker:<container-id>:<path>
:dir /sudo::/etc
Beispiel: Remote-Sysadmin-Workflow
# Festplattennutzung und Speicher auf einem Remote-Server prüfen:
#+BEGIN_SRC bash :dir /ssh:[email protected]:/tmp :results output
hostname
uptime
df -h / | awk 'NR==2 {print "Root Disk Usage: " $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
: Root Disk Usage: 48%
Remote-Sitzungen
Kombinieren Sie :dir mit :session, um eine aktive SSH-Verbindung über mehrere Blöcke hinweg aufrechtzuerhalten, ohne sich bei jedem Schritt neu zu verbinden:
#+BEGIN_SRC bash :dir /ssh:[email protected]:/home/admin :session *remote-bastion* :results output
WORKDIR=$(mktemp -d)
cd "$WORKDIR"
pwd
#+END_SRC
5. Ergebnisformate & tabellarische Datenextraktion
5.1 Tab-getrennte Ausgabe in Org-Tabelle (:results output table)
Wenn Shell-Befehle tab-getrennte oder strukturierte tabellarische Ausgaben erzeugen, parst Babel sie in native Org-Tabellen:
#+BEGIN_SRC bash :results output table
printf "PID\tUser\tCommand\n"
printf "101\troot\tsystemd\n"
printf "405\tpostgres\tpostgres\n"
printf "892\tnginx\tnginx: worker\n"
#+END_SRC
#+RESULTS:
| PID | User | Command |
|-----+----------+---------------|
| 101 | root | systemd |
| 405 | postgres | postgres |
| 892 | nginx | nginx: worker |
5.2 Ausgabe in einen Dateilink lenken (:results file :file <path>)
#+BEGIN_SRC bash :results file :file reports/sysinfo.txt
uname -a > reports/sysinfo.txt
cat /etc/os-release >> reports/sysinfo.txt
#+END_SRC
#+RESULTS:
[[file:reports/sysinfo.txt]]
6. Variablen & Bash-Arrays übergeben (:var)
6.1 Skalare und Zeichenketten
#+BEGIN_SRC bash :var port=8000 host="localhost" :results output
echo "Connecting to $host on port $port..."
#+END_SRC
6.2 Bash 1D-Arrays
Bash unterstützt 1D-Array-Parameter, die mit Listensyntax definiert werden:
#+BEGIN_SRC bash :results output :var servers='("web-01" "web-02" "db-01")'
echo "Total servers: ${#servers[@]}"
for s in "${servers[@]}"; do
echo "Pinging $s..."
done
#+END_SRC
#+RESULTS:
: Total servers: 3
: Pinging web-01...
: Pinging web-02...
: Pinging db-01...
7. Häufige Fallstricke & Sicherheitsempfehlungen
| Fallstricke | Ursache | Lösung |
|---|---|---|
| Emacs hängt bei SSH- oder ffmpeg-Befehl | Befehl versucht, von der Subshell stdin zu lesen | stdin explizit umleiten (</dev/null) oder ssh -n verwenden. |
Störende bash-5.1$-Eingabeaufforderungen in Ergebnissen | Interaktives zeilenweises Echo in :session | :shebang #!/usr/bin/env bash hinzufügen oder außerhalb einer Sitzung auswerten. |
| Unbeabsichtigte Variablenexpansion in Heredocs | <<EOF erlaubt der Shell, $VARS vorzeitig zu expandieren | Verwenden Sie das begrenzende Anführungszeichen <<"EOF", um den Text literal zu halten und eine vorzeitige Expansion zu verhindern. |
Nicht-POSIX-Syntax schlägt in sh-Blöcken fehl | #+BEGIN_SRC sh wird auf /bin/sh abgebildet (oft dash auf Debian/Ubuntu) | Verwenden Sie #+BEGIN_SRC bash, wenn Ihr Skript Bashismen verwendet ([[, ((, Arrays). |
Befehlsinjektion via :cmdline | Shell-Argumente werden wörtlich ausgewertet | Argumente sorgfältig maskieren oder Daten stattdessen via :stdin oder :var übergeben. |
| Asynchrone Blockausgabe erscheint nicht | :async yes erfordert eine aktive :session | Geben Sie immer eine :session <name> an, wenn Sie :async yes verwenden. |
8. Vollständiges DevOps-Runbook-Beispiel
#+TITLE: Production Service Health Runbook
#+PROPERTY: header-args:bash :session *runbook* :results output
* Schritt 1: Health-Check-Konfiguration
#+NAME: cluster_nodes
| Hostname | Role |
|-----------------+---------|
| prod-app-01.lan | backend |
| prod-app-02.lan | backend |
| prod-db-01.lan | storage |
* Schritt 2: Cluster-Knoten untersuchen
#+BEGIN_SRC bash :var nodes=cluster_nodes :colnames yes :results output table
printf "Host\tRole\tStatus\n"
for row in "${nodes[@]}"; do
# Parst tab-/leerzeichengetrennte Zeilendaten:
host=$(echo "$row" | awk '{print $1}')
role=$(echo "$row" | awk '{print $2}')
# Schnell-Ping-Prüfung:
if ping -c 1 -W 1 "$host" > /dev/null 2>&1; then
status="ONLINE"
else
status="OFFLINE"
fi
printf "%s\t%s\t%s\n" "$host" "$role" "$status"
done
#+END_SRC
#+RESULTS:
| Host | Role | Status |
|-----------------+---------+--------|
| prod-app-01.lan | backend | ONLINE |
| prod-app-02.lan | backend | ONLINE |
| prod-db-01.lan | storage | ONLINE |
* Schritt 3: Asynchrone Dienstwartung durchführen
#+BEGIN_SRC bash :session *runbook* :async yes
echo "Initiating rolling cache purge across nodes..."
sleep 4
echo "Cache purge completed successfully."
#+END_SRC