Wer regelmäßig in der Bash arbeitet, kennt das Aha-Gefühl, wenn eine durchdachte Vervollständigung nicht bloß Zeit spart, sondern Arbeitsfluss und Zuversicht verändert. Ein einziges TAB kann eine Welt eröffnen: Dateinamen, Befehlsoptionen, Subkommandos – alles erscheint, bevor man den Cursor wieder loslässt. In vielen Systemen funktioniert das wie eine stille Intelligenz im Hintergrund, doch echte Power kommt erst, wenn man diese Intelligenz selbst gestaltet: Von einer simplen statischen Liste bis zu kontextsensitiven Vorschlägen, die sich an Verbformen oder Arbeitsabläufe knüpfen. Das Thema Bash Completion bietet genau diesen Spielraum.
Dieser Artikel führt von den Grundlagen zur eigenen Vervollständigung: Von der Unterscheidung zweier Vervollständigungsformen über zentrale Bash-Mechanismen bis hin zu einfachen Mustern, wie man eine -F-Funktion registriert und COMPREPLY füllt. Er zeigt, wie dynamische Generierung, Verzeichnisstrukturen und Überschreibungspfade ineinandergreifen, ohne das System zu destabilisieren. Leserinnen und Leser gewinnen so Schritt für Schritt das Werkzeug, um die Bash-UX ihrer Kommandozeilenwelt zu verfeinern – flexibel, robust und exakt auf den eigenen Workflow zugeschnitten.
Kernkonzepte und Architektur der Bash Completion
Bash Completion basiert auf einem zweistufigen Vervollständigungsmodell, das die Readline-Tastenkombination TAB als Grundfunktionalität mit einer programmierbaren, kontextabhängigen Vervollständigung verbindet. Die Readline-Variante liefert klassische Dateinamen- und Befehlsvervollständigung per TAB, während die programmierbare Completion durch das Kommando complete -F-Funktionen gesteuert wird. Dieser Aufbau ermöglicht sowohl generische Vervollständigungen als auch spezialisierte, sitzungs- bzw. befehlspezifische Vorschläge. Die Trennung von Readline-gestützter Vervollständigung und programmierbarer Completion ist ein zentraler Bestandteil der Architektur.

Zugleich handelt es sich um ein mehrschichtiges System: Zunächst die schnelle, generische Unterstützung durch Readline, ergänzt durch eine kontextsensitive Schicht, die durch spezialisierte Completion-Dateien und -Funktionen pro Befehl bereitgestellt wird. Die dynamische Lade-Strategie ermöglicht es, Completion-Definitionen on demand zu laden, woraus Startzeiten optimiert und Anpassungen gezielt vorgenommen werden können.
Zweischichtige Vervollständigung: Readline-basiert vs. programmierbare Completion
- Readline-basierte TAB-Vervollständigung sorgt unmittelbar für Vorschläge basierend auf dem aktuellen Token und dem Dateisystem. Sie lässt sich über Readline-Optionen steuern, beispielsweise ob bei Mehrdeutigkeit eine Liste angezeigt wird oder ob lange Listen paginiert werden.
- Programmierbare Completion greift erst dann ein, wenn eine spezifische Completer-Funktion per complete -F für ein bestimmtes Kommando registriert ist. Diese Ebene ermöglicht kontextabhängige Vorschläge: Optionen und Argumente eines Befehls, Subkommandos, spezielle Kontextregeln, dynamische Generierung aus Systemzuständen oder externen Quellen.
- Die beiden Ebenen arbeiten oft Hand in Hand: Die generische Readline-Vervollständigung liefert eine Basis, während die programmierbare Completion darüber hinaus feinere, kontextsensitive Ergebnisse beisteuert.
Zentrale Mechanismen: COMPREPLY, COMP_WORDS, COMP_CWORD und dreifaches Funktions-Setup
- COMPREPLY-Array: Das zentrale Speicherelement der Vervollständigung. Nach Ausführung einer Completion-Funktion enthält dieses Array die zu ersetzenden Vorschläge, die Bash dann in der Anzeige oder in der Interaktion präsentiert.
- COMP_WORDS: Das Array, das die einzelnen Wörter der aktuellen Befehlszeile enthält. Es bildet die Eingabebasis der Vervollständigung.
- COMP_CWORD: Der Index in COMP_WORDS, der angibt, welches Element gerade vervollständigt wird. Er ermöglicht es, die Funktion auf das richtige Wort ausgerichtet zu arbeiten.
- In der Praxis arbeitet man meist direkt mit diesen globalen Kontextvariablen (COMP_WORDS, COMP_CWORD) und liest daraus das aktuell zu vervollständigende Wort. Die Kernidee ist, COMPREPLY mit passenden Vorschlägen zu befüllen und dann return 0 zu setzen.
- Ergänzende Hilfslogiken nutzen interne Generatoren, Filterregeln und ziel-spezifische Besonderheiten, um aus Kurzwortlisten, Dateisystempfaden oder dynamisch erzeugten Daten Vorschläge zu liefern. Die Trennung von Eingabe, Kontext und Generierung bleibt hierbei zentral.
Historische Funktionen und der Unterschied generischer vs. kontextsensitiver Vervollständigung
- Historisch betrachtet dominierten generische Regeln: Dateinamen, Programmnamen und einfache Token-Verwaltung. Diese generischen Mechanismen arbeiten unabhängig vom konkreten Befehl und dienen als Basisfall.
- Kontext‑sensitive Completion führt über diese Basismuster hinaus: Sie kennt das Zielkommando, dessen Optionen und Argumente, sowie gegebenenfalls Subkommandos, Pfadstrukturen oder Befehlsabhängigkeiten. Dadurch entstehen spezialisierte Vorschläge, die über das bloße Dateisystem hinausgehen.
- Der modulare Aufbau erlaubt es, per Datei und Funktion zusätzliche Completion-Definitionen bereitzustellen, die sich an den jeweiligen Kontext anpassen. Auf diese Weise lässt sich eine Vielzahl an Programmen abdecken, ohne die generische Logik zu beeinträchtigen.
Wesentliche Hilfsfunktionen: _comp_compgen, _comp_expand und _comp_get_cword_at_cursor
- _comp_compgen: Zentrale Schnittstelle zur Generierung von Vorschlägen. Sie kapselt den Aufruf interner Generatoren sowie externer Befehle und sorgt dafür, dass COMPREPLY konsistent befüllt wird. Dadurch wird eine einheitliche Verarbeitung der unterschiedlichen Generatoren erreicht.
- _comp_expand: Hilft bei der Erweiterung von Eingaben – beispielsweise Globbing, Globbing-Filterung, Tilde-Erweiterung und andere Pfad- bzw. Wort-Erweiterungen. Diese Funktion sorgt dafür, dass eine realistische und konsistente Liste von Kandidaten entsteht, unabhängig davon, ob die Werte aus dem Dateisystem, aus Assembly-Liste(n) oder dynamisch generiert werden.
- _comp_get_cword_at_cursor: Berechnet das cursor-aktuelle Wort relativ zur Eingabe. Sie adressiert den Fall, dass der Cursor mitten in einem Wort steht und passt cur, cword sowie Kontext-Informationen entsprechend an. Dazu gehört auch das Left-Trimming oder Anpassen bei Tokengrenzen, um eine korrekte Filterung zu ermöglichen.
- Ergänzend gibt es weitere Hilfsfunktionen wie _comp_get_words, die die Auflösung von Wortgrenzen und Tokens unterstützt. Zusammen bilden sie das Fundament für robuste, kontextbewusste Generierung von Kandidaten.
Drei Ebenen der Standardvervollständigung
- Generische Dateinamen- und Pfadvervollständigung: Auf jeder Befehlsebene nutzbar. Diese Ebene deckt die Grundbedürfnisse ab und sorgt dafür, dass einfache Pfad- und Dateianforderungen funktionieren.
- Kommando-spezifische Vervollständigungen: Durch das bash-completion-Paket bereitgestellt oder durch eigene Dateien ergänzt. Diese Ebene versteht die Semantik einzelner Kommandos, kennt deren Optionen und typische Argumente und erzeugt kontextbezogene Vorschläge.
- Eigene Shell-Funktionen: Für fortgeschrittene Nutzungsszenarien lassen sich eigene Shellfunktionen registrieren, die eine maßgeschneiderte Completion-Logik liefern. Sie ermöglichen komplexe Abhängigkeiten, spezielle Abkürzungen oder dynamische Listen, die sich am konkreten Anwendungsfall orientieren.
- Die Gesamtkonstellation ergibt eine stabile, modulare Architektur: Generische Mechanismen liefern Basissicherheit, kontextsensitive Completion erhöht die Produktivität, und eigene Funktionen ermöglichen individuelle Workflows.
Fazit: Die Konstruktion von Bash Completion basiert auf einer klaren Trennung von Readline-basierter Grundvervollständigung und programmierbarer, kontextsensitiver Erweiterungen. Das Zusammenspiel aus COMPREPLY, COMP_WORDS, COMP_CWORD sowie der unterstützenden Hilfslogik ermöglicht es, sowohl einfache als auch hochkomplexe Vervollständigungs-Szenarien zuverlässig abzubilden. Die drei Ebenen der Standardvervollständigung liefern dabei eine bewährte Struktur, um generische Funktionalitäten mit konkreten Anwendungsfällen sinnvoll zu kombinieren.
Eine einfache eigene Bash-Vervollständigung schreiben
In dieser Sektion zeigen wir dir, wie du eine einfache, eigene Bash-Vervollständigung schreibst, die eine benutzerdefinierte -F-Funktion registriert und mit COMPREPLY eine statische Wortliste verwendet. Der Fokus liegt dabei auf den Kernmechanismen: Zugriff auf COMP_WORDS, COMP_CWORD und das aktuell zu vervollständigende Wort, sowie ein praktisches, minimales Beispiel mit einer festen Wortliste. Abschließend betrachten wir, wie eigene Vervollständigungen existierende System-Vervollständigungen überschreiben können.

Registrierung einer -F-Funktion und Nutzung von COMPREPLY
- Registrierung: Um eine eigene Vervollständigung für ein Kommando zu definieren, registrierst du eine Funktion mit dem Bash-Builtin complete. Typischer Fall:
- complete -F _mycommand_complete mycommand
- Dabei ist _mycommand_complete der Funktionsname, der unter der Shell zur Generierung der Vorschläge aufgerufen wird, und mycommand das Zielkommando, für das vervollständigt wird.
- Aufbau der Funktion: In der Praxis greift die Funktion üblicherweise auf COMP_WORDS und COMP_CWORD zu, um den Kontext zu bestimmen. Die Kernidee ist, COMPREPLY mit passenden Vorschlägen zu befüllen und dann return 0 zu setzen.
- Eine typische Minimal-Funktion sieht so aus:
- _mycommand_complete {
- local cur="${COMP_WORDS[COMP_CWORD]}"
- local -a OPTS=("start" "stop" "restart" "status")
- COMPREPLY=( $(compgen -W "${OPTS[@]}" -- "$cur") )
- return 0
- }
- Die Zeilen definieren:
- cur: das aktuell zu vervollständigende Wort, abgerufen aus COMP_WORDS[COMP_CWORD].
- OPTS: eine lokale Array-Liste der möglichen Vervollständigungen.
- COMPREPLY: das Bash-Array, das die Vorschläge sammelt. Hier kommt der Generator-Mechanismus von compgen zum Einsatz.
- Diese Struktur erlaubt einfache, kontextsensitive Vervollständigungen, die sofort funktionieren, sobald du Tab drückst.
Zugriff auf COMP_WORDS, COMP_CWORD und das aktuelle Wort
- COMP_WORDS ist das Array der bisherigen Eingaben in der aktuellen Befehlszeile; COMP_CWORD ist der Index des aktuellen Worts innerhalb dieses Arrays.
- Um das Wort zu bekommen, das gerade vervollständigt wird, greifst du direkt darauf zu:
- local cur="${COMP_WORDS[COMP_CWORD]}"
- Beispielhafte Erklärungen:
- COMP_WORDS enthält alle Wörter der aktuellen Zeile, z. B. für „mycommand sta[TAB]“ befinden sich im Array die Tokens ["mycommand", "sta"].
- COMP_CWORD gibt an, welches Element von COMP_WORDS gerade vervollständigt wird (in diesem Fall der Index des Tokens „sta“).
- Mit dieser Information programmierst du deine Logik so, dass du die passenden Vervollständigungen für das gerade eingegebene Teildatum liefern kannst. Die einfache Variante nutzt nur den aktuellen Wortwert (cur) und eine vordefinierte Liste von Optionen.
Statische Wortliste mit Optionen; Nutzung von compgen -W zur Generierung
- Der einfachste Fall ist eine feste Wortliste, aus der Bash Vorschläge generiert.
- Beispiel-Implementierung (fortlaufend, inline beschrieben):
- _mycommand_complete {
- local cur="${COMP_WORDS[COMP_CWORD]}"
- local -a OPTS=("start" "stop" "restart" "status")
- COMPREPLY=( $(compgen -W "${OPTS[@]}" -- "$cur") )
- return 0
- }
- Erklärungen:
- OPTS enthält die möglichen Optionen.
- compgen -W erzeugt eine Liste von Vorschlägen aus OPTS, gefiltert nach dem aktuellen Wort cur.
- COMPREPLY wird mit den gefilterten Vorschlägen gefüllt, wodurch Tab-Vervollständigung möglich wird.
- Vorteil dieser Variante: schnell implementierbar, robust und direkt testbar. Nachteile: Erweiterungen müssen manuell gepflegt werden, und kontextsensitive Abhängigkeiten (z. B. unterschiedliche Vervollständigungen je nach Verb) benötigen zusätzliche Logik.
Überschreiben existierender System-Vervollständigungen durch eigene Dateien in benannten Verzeichnissen
- Grundidee: Zusätzlich zur systemweiten Vervollständigung kannst du eigene Dateien in speziellen Verzeichnissen ablegen, um deine Vervollständigung override-ähnlich zu verwenden.
- Vorgehen in der Praxis:
- Lege eine Datei mit dem Namen deines Befehls in einem benannten Verzeichnis ab, das für benutzerdefinierte Vervollständigungen vorgesehen ist (oft ein completions-Verzeichnis, z. B. ~/.local/share/bash-completion/completions/).
- In dieser Datei definierst du die -F-Funktion und registrierst die Vervollständigung erneut, z. B. durch:
- _mycommand_complete { ... }
- complete -F _mycommand_complete mycommand
- Wirkung:
- Die benutzerdefinierte Datei wird bei Bedarf geladen, bevor oder statt der systemweiten Standard-Vervollständigung. Dadurch kannst du deine eigene Logik gezielt für dein Kommando nutzen.
- Wenn sowohl die systemweite als auch die benutzerdefinierte Vervollständigung vorhanden sind, priorisiert Bash in der Regel die lokaleren, benutzerdefinierten Dateien, wodurch deine Überschreibung wirksam wird.
- Vorteile:
- Einfaches Überschreiben von bestehenden Vervollständigungen, ohne Eingriffe in Systempakete.
- Organisation über benannte Verzeichnisse erleichtert Wartung und Verteilung von eigenen Snippets.
- Praktische Tipps:
- Verwende klare Dateinamen, die dem Zielkommando entsprechen (z. B.
- Halte die Logik in der Modulfunktion kompakt, damit du sie gezielt anpassen kannst, ohne eine komplexe Gesamtmlogik neu schreiben zu müssen.
- Nutze, falls nötig, zusätzliche Hilfsfunktionen innerhalb derselben Datei, um schrittweise Erweiterungen vorzunehmen.
Registrierung konkret: Abschlussbeispiele
- Um deine Vervollständigung fest zu registrieren, verwendest du die Abschlusszeile:
- complete -F _mycommand_complete mycommand
- Diese Zeile bindet die Funktionslogik an das Zielkommando. Von nun an wird bei jedem Tab der Funktionsaufruf von _mycommand_complete getriggert, der wiederum COMPREPLY mit passenden Vorschlägen befüllt.
- Wenn du deine Überschreibung in einer Datei unter einem benannten Verzeichnis platziert hast, wird diese Datei typischerweise beim ersten Aufruf von mycommand geladen und angewendet, bevor Bash weiter mit System-Vervollständigungen arbeitet.
Fazit und Praxis-Check
- Mit einer einfachen -F-Funktion lässt sich eine zuverlässige, eigene Bash-Vervollständigung implementieren, die das aktuelle Wort aus COMP_WORDS[COMP_CWORD] nutzt und über COMPREPLY passende Optionen liefert.
- Der minimale Aufbau mit einer statischen Wortliste ist leicht nachzuvollziehen und eignet sich für erste Experimente.
- Durch das Ablegen eigener Dateien in benannten Verzeichnissen lassen sich existierende System-Vervollständigungen gezielt überschreiben oder ergänzen, ohne das System paketweise zu verändern.
- Die abschließende Registrierung erfolgt über complete -F _mycommand_complete mycommand, wodurch die eigene Logik nahtlos in die Bash-Vervollständigungspipeline integriert wird.
Damit hast du eine robuste, nachvollziehbare Grundlage geschaffen, um eigene Bash-Vervollständigungen schrittweise zu erweitern – von einer reinen, statischen Wortliste bis hin zu kontextabhängigen Vorschlägen, die sich an Verbformen oder Unterbefehle anpassen.
Fortgeschrittene Muster: dynamische Generierung und Kontext
- Dynamische Generierung mit compgen: Der Bash-Builtin-Mechanismus erzeugt Vorschläge zur Laufzeit über gezielte Generatoren. Wesentliche Optionen betreffen den Zugriff auf Dateien und Verzeichnisse sowie die granulare Steuerung von Dateitypen. In komplexeren Fällen wird oft zwischen Verzeichnis- und Dateivervollständigungen unterschieden, um kontextgerechte Vorschläge zu liefern.
Dynamische Generierung mit compgen
- Dynamische Dateigenerierung (Dateien vs. Verzeichnisse): Verwenden Sie je nach Bedarf unterschiedliche Generatoren, um passende Kandidaten zu erzeugen. Typische Muster:
- Verzeichnisse: COMPREPLY=( $(compgen -d -- "$cur_orig") )
- Dateien: COMPREPLY=( $(compgen -f -- "$cur_orig") )
- Allgemeine Dateien (inkl. Verzeichnissen): COMPREPLY=( $(compgen -A file -- "$cur_orig") )
- Der Unterschied: -d wählt gezielt Verzeichnisse aus, -f liefert Dateien (und ggf. Verzeichnisse) als Kandidaten. -A file ermöglicht eine flexible Generierung von Dateinamen je nach Kontext.
- -W-Optionen und -O-Filtern: Mit -W definieren Sie eine eigene Wortliste, die durchsucht wird; -A file bzw. -d/-f steuern die Typen-Filterung. Kombiniert man diese Mechanismen, ergibt sich eine präzise Vorschlagslogik, die Verzeichnisse und Dateien sinnvoll trennt.
- Filtern und Escapen: In dynamischen Pfaden sollten Surrogat- oder Escape-Verhalten berücksichtigt werden, damit Sonderzeichen in Dateinamen korrekt behandelt werden. Besondere Beachtung verdienen Unit-Namen, Pfade mit Leerzeichen oder Zeichen, die in Patterns eine besondere Bedeutung haben.
- Beispielhafte Anwendungsfälle: Für Befehle, die Pfade als Argument erwarten, generieren Sie standardmäßig Dateinamen und Pfade; in anderen Kontexten schlagen Sie nur Verzeichnisse vor, z. B. bei Shell-Kommandos, die ein Verzeichnis als Ziel erwarten.
Kontextabhängige Unteroptionen
- Kontextsensitives Vorgehen bedeutet, dass die aktuelle Eingabe und der vorhergehende Token die nächsten Vorschläge beeinflussen. Ein gängiges Muster ist die prev-Verarbeitung, beispielsweise für systemctl oder ähnliche Befehle:
- Ermitteln Sie cur (das gerade zu vervollständigende Wort) und prev (das vorhergehende Wort) aus COMP_WORDS.
- Basierend auf prev bestimmen Sie, welche Unteroptionen oder Gruppierungen passend sind (z. B. prev == "start" oder prev == "restart" unterschiedliche Sichten von Einheiten).
- Verschachtelte Verarbeitungen: Ein spezialisiertes Untermenü könnte verb-übergreifend unterschiedliche Gruppen liefern (z. B. ALL_UNITS, ENABLED_UNITS, DISABLED_UNITS, STARTABLE_UNITS, STOPPABLE_UNITS, RELOADABLE_UNITS). In einem Completion-Block lässt sich so eine logische Verzweigung implementieren, die abhängig vom zuletzt eingegebenen Verb weitere Prioritäten setzt.
- Modalansichten als Untermenüs: Für bestimmte Befehle kann man modale Ansichten anbieten, z. B. JOBS-, FILE-, TARGETS-Module, die nur aktiv werden, wenn der Kontext passt. So entstehen konsistente, thematische Vorschläge, ohne den Benutzer mit unnötigen Optionen zu überfluten.
Verwendung von assoziativen Arrays und komplexeren Strukturen
- Assoziative Arrays ermöglichen das Gruppieren von Optionen nach Kategorien oder Kontexten. Beispiele:
- declare -A OPT_GROUPS
- OPT_GROUPS[unit_types]="service socket mount target"
- OPT_GROUPS[verbs]="start stop restart reload status"
- Sie können verschachtelte Strukturen abbilden, indem Sie pro Kontext weitere Arrays oder Strings erzeugen, die dynamisch in COMPREPLY einfließen.
- Durch Verben-zu-Unit-Typ-Zuordnung lässt sich eine konsistente Gruppierung der Vorschläge realisieren, sodass bei einem Typwechsel automatisch passende UNIT-Namen (z. B. service, socket, mount) präsentiert werden.
- Kombinationen mit Previous-Listen erlauben es, pro Gruppe spezifische Unterlisten zu erzeugen (z. B. ENABLED_UNITS vs DISABLED_UNITS) und diese gezielt in COMPREPLY zu integrieren.
Systemctl-Beispiele mit gruppierten UNIT-Typen und modalen Ansichten
- Systemctl eignet sich als Paradebeispiel, wie man gruppierte UNIT-Typen und modulare Ansichten realisiert:
- Gruppierte UNIT-Typen: Sammeln Sie Typen wie SERVICES, SOCKETS, MOUNTs, TARGETs in einem assoziativen Array und präsentieren Sie bei der passenden Verbphase nur die relevanten Gruppen.
- Modalansichten JOBS, FILE, TARGETS: Je nach Kontext wechseln Sie zu einer anderen Anzeigeebene, die speziell diese Gruppen auflistet (z. B. Jobs, Unit-Dateien oder Zielzustände).
- Die finale Vervollständigung erfolgt in der Regel über COMPREPLY, erzeugt durch eine Mischung aus direktem Zeichenabgleich, dynamischer Generierung aus Systemdaten und Fallback-Fällen.
- Fallspezifika wie UNIT-Namen mit Sonderzeichen Escape-Handling erfordern, ist eine sorgfältige Vorverarbeitung sinnvoll.
- Wichtige Ergänzungen: Vor dem Override sollten Sie sicherstellen, dass die Basiskomplettierung geladen ist, damit Ihre Überschreibung sinnvoll darauf aufbauen kann.
Fallstricke: Escapen, -o nospace und -o filenames
- Escapen von UNIT-Namen: UNIT-Namen können Bindestriche oder andere Sonderzeichen enthalten; beim Aufbau von Mustervergleichen oder beim Übergang zu weiteren Tokens ist eine sorgfältige Escape-Behandlung nötig, damit Bash Muster nicht versehentlich interpretiert.
- -o nospace: Der Modus nospace verhindert, dass Bash nach der Vervollständigung ein Leerzeichen setzt. Das ist besonders nützlich, wenn die nächste Eingabe direkt an ein Argument gebunden ist (z. B. komplexe UNIT-Namen oder zusammengesetzte Parameter).
- -o filenames: Mit dieser Option lässt sich sicherstellen, dass Dateinamen als solche behandelt werden, sodass eine korrekte Weiterverarbeitung von Dateinamen in der Vervollständigung möglich bleibt.
- Fehlerrobuste Praxis: Definieren Sie fallback-Pfade, damit die Vervollständigung auch dann sinnvoll bleibt, wenn eine Gruppe oder ein Kontext keine Treffer liefert.
Fazit und Ausblick
- Fortgeschrittene Muster nutzen dynamische Generatoren, kontextabhängige Logik und strukturierte Datentypen, um Completion präzise und intuitiv zu gestalten.
- Die Kombination aus compgen-basierter Generierung, prev-/Kontext-Verarbeitung, assoziativen Arrays und modulare Sichten (wie bei systemctl) ermöglicht leistungsfähige, wartbare Completion-Skripte.
- Durch sorgfältiges Escapen, gezielte Optionen wie nospace und filenames sowie robuste Fallback-Pfade wird die Benutzbarkeit auch in anspruchsvollen Befehlswelten erhöht.
Laden, Überschreiben und Verzeichnisse
Basissatz: bash_completion-Dateien werden geladen, danach können eigene Funktionen überschrieben werden. Dadurch lassen sich die mitgelieferten Vervollständigungen durch benutzerdefinierte Implementierungen ersetzen oder ergänzen, ohne die ursprüngliche Logik zu verändern.
Pfadkonzepte und Verzeichnisse
- Per-Benutzer- und Systempfade: Es existieren mehrere zentrale Verzeichnisse, über die Bash-Completion gefunden und geladen wird. Die wichtigsten sind completionsdir, startupdir, helpersdir und compatdir. Diese Strukturen ermöglichen es, On-Demand- oder eager-loading-Verhalten zu modellieren und System- von Benutzerebene zu trennen.
- XDG-basierte Pfade: Zusätzlich können XDG-Datenpfade genutzt werden. Sind Variablen wie XDG_DATA_HOME oder XDG_DATA_DIRS gesetzt, liegen bash-completion-bezogene Dateien in den entsprechenden Unterverzeichnissen. Dadurch entstehen standardisierte Pfade wie Benutzerdatenbereiche, in denen Ergänzungen abgelegt werden können.
- Was steckt in den Verzeichnissen?
- completionsdir dient der Ablage von completions-Dateien pro Befehl. Die Datei trägt den Namen des Befehls, damit sie automatisch geladen wird, wenn der Command aufgerufen wird.
- startupdir enthält Dateien, die beim Start von bash_completion geladen werden, um globale oder benutzerspezifische Startlogik bereitzustellen.
- helpersdir ist vorgesehen für Hilfs-Skripte, die von Completion-Dateien genutzt werden können.
- compatdir sammelt Backwards-Kompatibilitätspfade; für neue Entwicklungen ist dies nicht der empfohlene Weg.
- Empfohlene Nutzung: Systemweite completions- und Hilfsdateien werden üblicherweise in den vom Paketmanagement definierten Verzeichnissen platziert. Benutzerdefinierte Ergänzungen können in den entsprechenden Benutzerverzeichnissen abgelegt werden; XDG-Datenpfade gelten als Standardweg.
Suchreihenfolge ab Bash-Completion >= 2.18
- 1. BASH_COMPLETION_USER_DIR (User-Direktorien): Zuerst durchsucht Bash die vom Benutzer bereitgestellten Verzeichnisse in der Reihenfolge der Einträge, um eine passende completions-Datei zu finden.
- 2. Standort der Haupt-bash_completion-Datei: Danach gelten die Verzeichnisse der Hauptdatei bash_completion als Suchorte.
- 3. Ort des Zielbefehls: Liegt das Zielkommando in einem bekannten Prefix wie
/bin oder /sbin, wird in entsprechenden share-/bash-completion-Verzeichnissen danach gesucht, um eine passende Datei zu finden. - 4. XDG-DIRS (XDG-basierte Pfade): Schließlich werden XDG_DATA_DIRS berücksichtigt. In jedem Pfad wird das bash_completion-Verzeichnis durchsucht, insbesondere nach Dateien, die
.bash oder betreffen. - Suchmuster innerhalb der Verzeichnisse: In den genannten Verzeichnissen suchen Bash-Completion-Dateien nach Dateien namens
.bash oder im Unterverzeichnis completions; der erste Treffer zählt. Falls nichts Passendes gefunden wird, greifen systemweite Core-/Fallback-Verzeichnisse als zusätzliche Stufen. - Diese Reihenfolge priorisiert Benutzervorlieben vor Systemstandards und ermöglicht gezielte Overrides.
Startup-Dateien: Lade-Reihenfolge kontrollieren
- Dateien mit numerischen Präfixen: Zur Steuerung der Lade-Reihenfolge können Startup-Dateien eine klare Sortierung per Namensschema erhalten. Typisch ist ein dreistelliger Präfix aus Ziffern (z. B. 001_, 042_ etc.), der die Reihenfolge festlegt.
- Ziel: Durch die numerische Benennung wird sichergestellt, dass spezifische Startkonfigurationen vor oder nach anderen geladen werden.
- Benutzerseitige Überschreibungen: Falls ein Benutzer eine Startup-Datei mit demselben Namen wie eine systemweite Startup-Datei bereitstellt, wird in der Praxis die benutzerdefinierte Version zuerst geladen, wodurch sich System-Dateien effektiv überschreiben lassen, sofern die Struktur dies vorsieht.
Praxis: Überschreibung durch Benutzerdaten
- Praxisfall: Eine eigene Datei
.bash im Benutzerverzeichnis kann System-Versionsdateien überholen. Dadurch wird die benutzerspezifische Vervollständigung bevorzugt, solange sie im entsprechenden Verzeichnis gefunden wird. - Namenskonventionen und Ort: Um eine Per-Command-Überschreibung zu ermöglichen, platziert man die benutzerdefinierte Datei idealerweise in dem completions-Verzeichnis, das dem konkreten Kommando zugeordnet ist, oder nutzt Startup-Dateien mit passenden Präfixen, die in der richtigen Reihenfolge geladen werden.
- Kehrseite: Überschreibungen sollten vorsichtig eingesetzt werden. Wenn man systemweite Funktionen gezielt anpassen möchte, ist die Benennung und Platzierung so zu wählen, dass die gewünschte Priorität erfüllt wird, ohne unvorhergesehene Seiteneffekte in anderen Kommandos zu verursachen.
- Namens- und Pfadregeln beachten: Die Dateibenennung muss konsistent erfolgen, damit bash-completion die Dateien zuverlässig lädt. Dabei helfen die festgelegten Verzeichnisse und die von der Distribution bereitgestellten pkg-config-Variablen, Pfade eindeutig zu bestimmen.
- Richtige Reihenfolge sicherstellen: Durch Startup-Prefixe und die 2.18-Suchreihenfolge haben benutzerdefinierte Dateien Vorrang vor Systemdateien; das schafft eine stabile Grundlage für individuelle Anpassungen, ohne das Gesamtsystem zu gefährden.
Zusammenfassend ermöglichen diese Mechanismen eine klare Trennung von systemweiten Vervollständigungen und benutzerspezifischen Anpassungen, gesteuert durch definierte Verzeichnisse, XDG-Pfade und eine deterministische Lade-Reihenfolge. Die Praxis zeigt, dass durch gezielte Platzierung von
Debugging, Troubleshooting und Best Practices
- In der Praxis beim Schreiben eigener Bash-Completion ist es essenziell, Fehler gezielt zu isolieren und klare, reproduzierbare Abläufe zu etablieren. Die folgenden Anleitungen helfen, Debugging-Schritte zu strukturieren, dynamische Ladepfade zuverlässig zu testen und typische Stolpersteine zu vermeiden.
Debugging-Techniken
- Vor Tests Debug-Flag setzen: Um Fehlverhalten der Completion nachvollziehbar zu machen, aktivieren Sie vor dem Testen das Debugging. Praktisch bedeutet das: Vor dem relevanten Codebereich setzen Sie set -x (oder set -v) und beobachten Zeile für Zeile die ausgeführten Befehle. Nach dem Testen wieder abschalten mit set +x bzw. set +v.
- Trace-Ausgaben sammeln: Sammeln Sie die Trace-Ausgaben in eine Datei, um später detailliert nachzuvollziehen, welcher Pfad im Skript durchlaufen wurde. Beispielsweise: bash -x your_completion.sh > dbg.log 2>&1.
- PS4-Determinanten nutzen: Setzen Sie PS4='+ $BASH_SOURCE:$LINENO:${FUNCNAME[0]}: ', um bei jeder Trace-Zeile Kontextinformationen (Datei, Zeilennummer, Funktionsname) zu erhalten.
- Spezifische Debug-Variablen beachten: Falls Ihre Umgebung Debug-Mechanismen für Bash-Completion bietet, aktivieren Sie sie gezielt, um zusätzliches Logging oder Prüfpfade zu erhalten.
- Fehlerberichte bündeln: Wenn Sie ein Issue öffnen, kopieren Sie die generierte Debug-Ausgabe in eine separate Anhang-Datei und überarbeiten sensible Daten ggf. vor dem Upload.
Dynamisches Laden testen
- Neue Shell-Sitzung verwenden: Testen Sie dynamisch geladene Funktionen in einer frischen interaktiven Shell (z. B. einer neuen Bash-Instanz) und lösen Sie dort gezielt eine Completion aus. So vermeiden Sie Abhängigkeiten von Variablen aus Ihrer aktuellen Session.
- Ladepfade prüfen: Verifizieren Sie, ob die dynamisch geladenen Dateien in den erwarteten Verzeichnissen liegen (z. B. completions-Verzeichnis, Startup-Ordner oder comps-Core/compat-Alternativen). Prüfen Sie, ob Umgebungsvariablen wie BASH_COMPLETION_USER_DIR, PATH-basierte Ladepfade oder pkg-config-Variablen tatsächlich auf existierende Dateien zeigen.
- Fallbacks sicherstellen: Stellen Sie sicher, dass im Falle eines fehlenden benutzerseitigen Completers ein Minimal-Fallback vorhanden ist, damit eine grundlegende Vervollständigung funktioniert. Dokumentieren Sie, welche Pfade priorisiert werden, und testen Sie explizit den Fall, dass kein spezifischer Eintrag gefunden wird.
Häufige Problemfelder
- GNU-Utilities-Abhängigkeiten: Viele Completion-Funktionen setzen GNU-Utilities wie grep, sed oder awk voraus. Auf Systemen mit Busybox-Varianten oder anderen Implementierungen können Unterschiede auftreten. Prüfen Sie systemweit die Verfügbarkeit dieser Tools, oder schreiben Sie Fallbacks, die auch POSIX-kompatible Werkzeuge nutzen.
- Langsame rpm-Completion (-q): Die rpm-Vervollständigung kann langsam sein, da die Abfrage der Paketdatenbank viel RAM beansprucht. Beschleunigen lässt sich das durch das Generieren einer Paketliste in eine Textdatei (z. B. rpm -qa --qf '…' | sort > /var/log/rpmpkgs). Wenn sich das RPM-Datenbank-Layout ändert, kann die Completion wieder zur exakten Abfrage wechseln.
- Konflikte mit command_not_found_handle: Wenn eine benutzerdefinierte command_not_found_handle-Funktion existiert, kann sie mit der programmablen Completion in Konflikt geraten. COMP_POINT (oder READLINE_POINT) sorgfältig zu prüfen und gegebenenfalls frühzeitig aus der Fehlerfall-Ersetzung auszusteigen.
- Performance-Überlegungen: Vermeiden Sie umfangreiche Berechnungen innerhalb der Completion. Beziehen Sie statische Listen vor oder kapseln Sie teure Operationen in vorgerechnete Variablen. Nutzen Sie sinnvolles Caching.
- Versions- und Distribution-Schwankungen: Unterschiede zwischen GNU-Bash-Versionen, Bash-Distributionen und deren bash_completion-Paketen können das Verhalten beeinflussen. Führen Sie Tests explizit mit der Ziel-Bash-Version und -Distribution durch.
Colon-bezogene Token-Probleme
- Tokenisierung durch Doppelpunkte beachten: Bash behandelt Doppelpunkte oft als Start eines neuen Token-Bereichs in bestimmten Completion-Kontexten (typisch bei PATH-ähnlichen Inhalten). Wird innerhalb eines Tokens mit Doppelpunkten vervollständigt, kann das Token-Parsing stören.
- Escape oder kontextbezogene Behandlung: Um Kolonsignale innerhalb eines Tokens korrekt zu behandeln, escapen Sie Doppelpunkte (z. B. mit Backslash) oder verwenden Sie eine kontextsensitive Logik, die Tokens entsprechend trennt und erst dann passende Vorschläge erzeugt.
- Kontext-taugliche Token-Behandlung: Falls Ihre Completion kontextabhängige Tokens berührt (z. B. man File::B), implementieren Sie eine Kontext-Erkennung und trennen Sie Token-Logik sauber von der eigentlichen Vorschlagsliste. So verhindern Sie, dass Token-Bedingungen COMPREPLY verzerren.
Best Practices
- Klare Benennungen: Verwenden Sie konsistente, klare Funktions- und Dateinamen per Kommando, z. B. _cmdname_complete oder cmdname_completion. Vermeiden Sie generische Namen, die zu Überschneidungen führen könnten.
- Explizite COMPREPLY-Generierung: Generieren Sie COMPREPLY explizit aus einer sauber vorbereiteten Wortliste. Vermeiden Sie mehrstufige oder indirekte Generierungswege, die schwer nachvollziehbar sind.
- Minimaler Einsatz von -o und -C: Nutzen Sie -o und -C nur dort, wo es sinnvoll ist. Zu viel Spezifikation erhöht die Komplexität, erschwert Wartung und kann zu unerwartetem Verhalten führen. Halten Sie sich an ein klares, dokumentiertes Default-Verhalten.
- Isoliertes Testen: Führen Sie Completion-Tests isoliert von anderen Bash-Funktionen aus, idealerweise in einer eigenen Shell-Session oder mit separaten Skripten, die nichts anderes als die zu testende Completion laden.
- Doku und Kommentare: Kommentieren Sie komplexe Logikpfade, insbesondere Token-Handling, Ladepfade und Fehlerfälle. Dokumentieren Sie, welche Dateien priorisiert werden und welche Fallbacks greifen.
- Performance und Sicherheit: Vermeiden Sie lang laufende Prozesse in der Completion. Nutzen Sie Vor-Caching oder vordefinierte Listen. Achten Sie darauf, dass Completion-Skripte sicher gegen Eingaben und Umgebungsvariablen sind.
- Ladeordnung beachten: Stellen Sie sicher, dass das Basis-Paket bash-completion geladen wird, bevor Sie eigene Überschreibungen verwenden. Prüfen Sie das Verhalten durch erneutes Laden der Shell oder durch explizites Sourcen der Basiskomponenten.
- Portabilität berücksichtigen: Schreiben Sie Completionen so, dass sie in verschiedenen Bash-Versionen und auf Systemen mit leicht abweichenden Tools robust funktionieren. Führen Sie Fallbacks ein, die bei Nichtverfügbarkeit bestimmter Tools greifen.
- Richtiger Einsatz von Tests: Beginnen Sie mit kleinen, gut reproduzierbaren Beispielen und erweitern Sie schrittweise. Dokumentieren Sie jeden Testfall und die erwartete Ausgabe.
- Mit diesen Prinzipien lassen sich Debugging, Troubleshooting und Best Practices beim Schreiben eigener Bash-Completion systematisch handhaben: Fehlerquellen werden früh erkannt, dynamische Ladepfade zuverlässig validiert, problematische Tokens sicher gehandhabt und die Codebasis bleibt wartbar, portabel und performancebewusst.
Fazit
Am Ende des Artikels steht nicht eine einzige Lösung, sondern ein Baukasten-Prinzip: Bash Completion lebt von der klaren Trennung zwischen Readline-Grundvervollständigung und der programmierbaren, kontextsensitiven Erweiterung. Wir haben gesehen, wie COMPREPLY, COMP_WORDS und COMP_CWORD die Eckpfeiler bilden, wie eine einfache -F-Funktion mit einer statischen Wortliste beginnt und wie sich daraus dynamische Generierung, Verzeichnisstrukturen und Überschreibungspfad-Strategien entwickeln lassen. Durch die drei Ebenen – generisch, kontextsensitiv, eigene Funktionen – lässt sich eine robuste, flexible Completion erzeugen, die exakt zum eigenen Workflow passt.
Der Schluss ist kein Abschied, sondern der Startpunkt für individualisierte Bash-Erlebnisse. Mit sorgfältigem Debugging, sinnvollen Ladepfaden und verantwortungsvollem Überschreiben kannst du komplexe Befehle sauber ergänzen, ohne das System zu destabilisieren. Nutze die Architektur, plane schrittweise Erweiterungen und halte Ressourcen- und Performance-Faktoren im Blick. So entsteht eine Benutzeroberfläche der Bash, die intuitiv bleibt, selbst wenn die Kommandos wachsen – und du steuerst dieses Wachstum mit eigener, maßgeschneiderter Completion.