Mit complete und compgen Unterbefehle, Optionen und dynamische Werte vervollständigen
Ein selbstgeschriebenes CLI-Tool wirkt erst richtig fertig, wenn die Tab-Taste Unterbefehle, Optionen und Argumente vorschlägt, genau wie bei git oder docker. Das Bash-Builtin complete zusammen mit compgen macht das ohne externe Abhängigkeiten möglich, von einer einfachen Wortliste bis zu dynamisch aus einer Datenbank oder API geladenen Vorschlägen.
Inhaltsverzeichnis
- 1. Warum eigene Completion die Akzeptanz eines CLI-Tools erhöht
- 2. Grundlagen: das complete-Builtin und compgen
- 3. Einfache Wortlisten-Completion für ein eigenes Tool
- 4. Unterbefehle vervollständigen: Subcommand-Dispatch in der Completion-Funktion
- 5. Optionen und Flags kontextabhängig vervollständigen
- 6. Dynamische Werte vervollständigen: Dateien, IDs aus einer API
- 7. Completion-Skript strukturieren und lokal testen
- 8. Installation: wohin die Completion-Datei gehört
- 9. Zsh-Kompatibilität und Grenzen der Bash-Completion
- 10. Zusammenfassung
- 11. FAQ
1. Warum eigene Completion die Akzeptanz eines CLI-Tools erhöht
Ein CLI-Tool ohne Tab-Vervollständigung zwingt Nutzer, sich jeden Unterbefehl und jede Option aus dem Gedächtnis oder aus der --help-Ausgabe zu holen, während Werkzeuge wie git, docker oder kubectl per Tab-Taste sofort zeigen, was als Nächstes möglich ist. Diese kleine Investition in eine Completion-Funktion senkt die Einstiegshürde für neue Nutzer eines eigenen Tools spürbar und reduziert Tippfehler bei langen Subcommand-Namen oder Optionsflags.
Gerade bei internen Tools, die von einem ganzen Team genutzt werden, zahlt sich die einmalige Investition in eine Completion-Datei schnell aus, weil jede eingesparte Nachfrage im Chat oder jeder vermiedene Blick in die Dokumentation Zeit spart. Bash-Completion ist dabei kein exotisches Feature, sondern ein Standardmechanismus, den fast jede Linux-Distribution bereits mitbringt und aktiviert hat.
2. Grundlagen: das complete-Builtin und compgen
Das Builtin complete registriert eine Completion-Funktion für einen bestimmten Befehlsnamen, sodass Bash bei jedem Drücken der Tab-Taste innerhalb dieses Befehls die registrierte Funktion aufruft, statt die Standard-Dateinamen-Vervollständigung zu verwenden. Die Funktion selbst befüllt ein spezielles Array namens COMPREPLY mit den Vorschlägen, die Bash danach anzeigt oder bei eindeutigem Treffer direkt einsetzt.
compgen ist das Werkzeug, das innerhalb der Completion-Funktion eine Liste von Kandidaten anhand des bisher eingegebenen Wortfragments filtert. Der Aufruf compgen -W "liste von woertern" "$cur", getrennt durch zwei Bindestriche als Optionsende-Markierung, gibt aus der übergebenen Wortliste nur die Einträge zurück, die mit dem aktuellen Präfix beginnen, und ist damit der Baustein, aus dem sich fast jede Completion-Funktion zusammensetzt.
3. Einfache Wortlisten-Completion für ein eigenes Tool
Der einfachste Einstieg ist eine statische Liste erlaubter Werte, etwa die Namen aller Unterbefehle eines eigenen Tools namens mstool. Die Completion-Funktion liest das aktuell eingegebene Wort aus COMP_WORDS an der Position COMP_CWORD, übergibt es an compgen -W zusammen mit der Wortliste und speichert das Ergebnis in COMPREPLY.
Diese Grundstruktur, bestehend aus dem Auslesen des aktuellen Wortes, dem Filtern per compgen und dem Zuweisen an COMPREPLY, taucht in praktisch jeder Bash-Completion-Funktion wieder auf, unabhängig davon, wie komplex die eigentliche Vorschlagslogik später wird. Wer diese drei Schritte einmal verinnerlicht hat, kann sie auf beliebig viele eigene Tools anwenden.
#!/usr/bin/env bash
# mstool-completion.bash -- einfache Wortlisten-Completion
_mstool_complete() {
local cur
cur="${COMP_WORDS[COMP_CWORD]}"
COMPREPLY=($(compgen -W "deploy status logs rollback" -- "$cur"))
}
complete -F _mstool_complete mstool
4. Unterbefehle vervollständigen: Subcommand-Dispatch in der Completion-Funktion
Sobald ein Tool eigene Argumente pro Unterbefehl anbietet, etwa mstool deploy staging oder mstool logs backend, muss die Completion-Funktion selbst erkennen, welcher Unterbefehl bereits eingegeben wurde, um die passenden Folgevorschläge zu liefern. Dazu wird COMP_WORDS[1] ausgelesen, das bei einem Aufruf wie mstool deploy <TAB> bereits deploy enthält.
Eine case-Anweisung über diesen Unterbefehl verzweigt dann in die jeweils passende Vorschlagsliste, genau wie das Tool selbst intern zwischen seinen Unterbefehlen unterscheidet. Diese Struktur skaliert gut, weil jeder neue Unterbefehl im eigenen Tool nur einen zusätzlichen case-Zweig in der Completion-Funktion braucht, statt die gesamte Logik neu zu schreiben.
#!/usr/bin/env bash
# mstool-completion.bash -- Subcommand-Dispatch
_mstool_complete() {
local cur subcommand
cur="${COMP_WORDS[COMP_CWORD]}"
subcommand="${COMP_WORDS[1]}"
if [[ "$COMP_CWORD" -eq 1 ]]; then
COMPREPLY=($(compgen -W "deploy status logs rollback" -- "$cur"))
return
fi
case "$subcommand" in
deploy)
COMPREPLY=($(compgen -W "staging production" -- "$cur"))
;;
logs)
COMPREPLY=($(compgen -W "backend frontend worker" -- "$cur"))
;;
esac
}
complete -F _mstool_complete mstool
5. Optionen und Flags kontextabhängig vervollständigen
Optionen wie --env oder --verbose lassen sich derselben Case-Verzweigung hinzufügen, wobei ein zusätzlicher Check auf den vorherigen Wortfragment sinnvoll ist, um nach --env gezielt Umgebungsnamen statt beliebiger Wörter vorzuschlagen. Der vorherige Wort-Token steckt in COMP_WORDS[COMP_CWORD-1] und lässt sich mit einer zusätzlichen case-Verzweigung auswerten.
Wichtig ist, das aktuelle Wortfragment immer mit einem führenden Bindestrich zu prüfen, um zu erkennen, ob gerade eine Option oder ein normales Argument vervollständigt wird. Beginnt $cur mit -, liefert die Funktion die Liste der verfügbaren Flags, andernfalls die regulären Positions-Argumente wie Umgebungsnamen oder Service-Namen.
#!/usr/bin/env bash
# mstool-completion.bash -- Optionen kontextabhaengig vorschlagen
_mstool_complete() {
local cur prev
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
if [[ "$prev" == "--env" ]]; then
COMPREPLY=($(compgen -W "staging production local" -- "$cur"))
return
fi
if [[ "$cur" == -* ]]; then
COMPREPLY=($(compgen -W "--env --verbose --dry-run --help" -- "$cur"))
return
fi
COMPREPLY=($(compgen -W "deploy status logs rollback" -- "$cur"))
}
complete -F _mstool_complete mstool
6. Dynamische Werte vervollständigen: Dateien, IDs aus einer API
Statische Wortlisten reichen nicht mehr aus, sobald Vorschläge von Laufzeitdaten abhängen, etwa gültige Deployment-IDs aus einer Datenbank oder Servicenamen aus einer laufenden Konfiguration. In diesem Fall ruft die Completion-Funktion das eigentliche Tool oder ein Hilfsskript im Hintergrund auf, um die aktuelle Liste zu holen, statt eine feste Liste im Skript zu pflegen.
Solche Aufrufe müssen unbedingt schnell sein, weil sie bei jedem Tab-Druck blockierend ausgeführt werden und eine langsame Netzwerkabfrage die Nutzung des Tools spürbar verlangsamt. Ein kurzes Timeout und, wenn möglich, ein lokaler Cache der letzten Abfrage verhindern, dass eine hängende API die Completion unbenutzbar macht.
#!/usr/bin/env bash
# mstool-completion.bash -- dynamische Werte per Hilfsbefehl
_mstool_complete() {
local cur subcommand
cur="${COMP_WORDS[COMP_CWORD]}"
subcommand="${COMP_WORDS[1]}"
if [[ "$subcommand" == "rollback" && "$COMP_CWORD" -eq 2 ]]; then
local ids
ids=$(timeout 1 mstool internal-list-deployment-ids 2>/dev/null)
COMPREPLY=($(compgen -W "$ids" -- "$cur"))
return
fi
}
complete -F _mstool_complete mstool
7. Completion-Skript strukturieren und lokal testen
Eine Completion-Datei sollte ausschließlich die _toolname_complete-Funktion und den abschließenden complete-Aufruf enthalten, ohne Seiteneffekte beim bloßen Einlesen der Datei, weil sie potenziell bei jedem neuen Shell-Start automatisch geladen wird. Variablen innerhalb der Funktion gehören konsequent mit local deklariert, damit sie nicht versehentlich die interaktive Shell-Umgebung des Nutzers verschmutzen.
Zum Testen reicht es, die Datei mit source in der aktuellen Shell zu laden und anschließend den Befehlsnamen gefolgt von Tab auszuprobieren, ohne die Datei fest zu installieren. Diese schnelle Iteration direkt in der Shell ist deutlich effizienter als jedes Mal eine neue Shell zu öffnen, um eine Änderung an der Completion-Logik zu testen.
#!/usr/bin/env bash
# Lokal testen, ohne die Datei fest zu installieren
source ./mstool-completion.bash
# Danach im Terminal ausprobieren:
# mstool dep<TAB> -> deploy
# mstool deploy <TAB> -> staging production
# mstool --e<TAB> -> --env
8. Installation: wohin die Completion-Datei gehört
Für dauerhafte Nutzung gehört die Completion-Datei nach /etc/bash_completion.d/ oder, auf moderneren Distributionen, nach /usr/share/bash-completion/completions/, benannt exakt wie der Befehl selbst, also mstool ohne Dateiendung. Beide Verzeichnisse werden von der zentralen bash-completion-Infrastruktur automatisch beim Start einer interaktiven Shell eingelesen, ohne dass jeder Nutzer die Datei manuell in seiner .bashrc einbinden muss.
Fehlt das bash-completion-Paket auf dem System, etwa auf minimalen Server-Images, lässt sich die Datei alternativ direkt aus der .bashrc mit source einbinden. Für die Verteilung über ein Paketmanagement-System, etwa via .deb oder Homebrew-Formula, gehört die Completion-Datei mit ins Installationspaket, damit Tab-Vervollständigung direkt nach der Installation funktioniert.
#!/usr/bin/env bash
set -euo pipefail
readonly DEST="/usr/share/bash-completion/completions/mstool"
sudo install -m 644 mstool-completion.bash "$DEST"
echo "Installiert nach $DEST. Neue Shell oeffnen oder 'source $DEST' ausfuehren."
9. Zsh-Kompatibilität und Grenzen der Bash-Completion
Eine mit complete und compgen geschriebene Completion-Funktion läuft ausschließlich in Bash. Zsh-Nutzer, die bashcompinit aktiviert haben, können Bash-Completion-Skripte zwar meist unverändert laden, aber für ein natives Zsh-Erlebnis mit Beschreibungstexten neben jedem Vorschlag ist ein eigenes Zsh-Completion-Skript im _toolname-Format nötig, das eine komplett andere Syntax verwendet.
Für ein internes Tool, das ausschließlich in einer Bash-geprägten Umgebung wie einem Docker-Container oder einer Standard-Server-Distribution läuft, reicht die Bash-Completion allein meist aus. Für ein öffentlich verteiltes CLI-Tool lohnt sich dagegen, von Anfang an sowohl eine Bash- als auch eine Zsh-Completion-Datei zu pflegen, weil ein spürbarer Teil der macOS-Nutzer standardmäßig Zsh verwendet.
| Ansatz | Aufwand | Dynamische Werte | Installationsort |
|---|---|---|---|
| Statische Wortliste mit compgen -W | Gering | Nein | /usr/share/bash-completion/completions/ |
| Subcommand-Dispatch mit case | Mittel | Nein | /usr/share/bash-completion/completions/ |
| Dynamische Werte per Hilfsbefehl | Höher, Performance beachten | Ja | Wie oben, mit Timeout absichern |
| Manuelles source in .bashrc | Gering | Je nach Funktion | Persönliche .bashrc, nicht systemweit |
| Zsh-natives _toolname-Skript | Hoch, eigene Syntax | Ja, mit Beschreibungstexten | Zsh fpath-Verzeichnis |
Mironsoft
Shell-Automatisierung, DevOps-Tooling und Deployment-Infrastruktur
Shell-Skripte, die in der Produktion zuverlässig laufen?
Wir analysieren bestehende Bash-Skripte, erkennen fragile Muster und ersetzen sie durch robuste Bash-Patterns: mit vollständiger Fehlerbehandlung, Logging und sicherer Parallelisierung für euren Deployment-Stack.
Code-Review
ShellCheck-Analyse und manuelle Prüfung auf kritische Bash-Pattern-Verstöße.
Refactoring
Fehlerbehandlung, Logging und sichere Dateioperationen nachrüsten.
CI-Integration
ShellCheck und BATS in Pipelines integrieren und Regressionstests aufbauen.
10. Zusammenfassung
Bash-Completion-Skripte für eigene CLI-Tools: Das Wichtigste auf einen Blick
Grundprinzip
complete registriert eine Funktion pro Befehl, die das COMPREPLY-Array befüllt. compgen -W filtert Kandidaten nach dem aktuellen Wortpräfix.
Subcommands
COMP_WORDS[1] liefert den bereits eingegebenen Unterbefehl, eine case-Anweisung liefert passende Folgevorschläge.
Dynamische Werte
Bei Werten aus API oder Datenbank das Hilfsskript mit kurzem Timeout aufrufen, um die Completion nicht zu blockieren.
Installation
Datei nach /usr/share/bash-completion/completions/toolname legen, benannt exakt wie der Befehl, ohne Dateiendung.