12 Punkte von GN⁺ 2025-08-13 | 1 Kommentare | Auf WhatsApp teilen
  • Einführung, wie sich in Bash und Zsh eine Tab-Vervollständigung implementieren lässt, die auch für bereits vollständig eingegebene Wörter Beschreibungen anzeigt
  • Bash und Zsh verwenden unterschiedliche APIs für die Tab-Vervollständigung, und nur Zsh bietet standardmäßig die Funktion Beschreibungen in der Vervollständigung anzuzeigen
  • Es wird eine Struktur implementiert, die Kandidaten mit _generate_foo_completions erzeugt und in Bash über COMPREPLY, in Zsh über compadd zurückgibt
  • Um die Beschreibungsfunktion von Zsh auch in Bash umzusetzen, werden Beschreibungen in die Kandidaten-Strings eingebettet und nur bei einem einzelnen Kandidaten wieder entfernt
  • Selbst bei einem einzelnen Kandidaten wird die Funktion verbessert, indem absichtlich Mehrdeutigkeit hinzugefügt wird, damit bei Eingabe von <TAB> eine Beschreibung angezeigt wird
  • Das finale Skript bietet in beiden Shells dieselbe User Experience und unterstützt Teil-Vervollständigung, vollständige Vervollständigung und die Anzeige von Kandidaten-Beschreibungen

Hintergrund des Problems

  • Tab-Vervollständigung (tab-completion) ist nützlich, um Befehle oder Flags zu erkunden, und hilft besonders dann, wenn man eine API oder ein CLI-Tool zum ersten Mal nutzt
  • Zsh zeigt standardmäßig nur dann Beschreibungen an, wenn es mehrere Kandidaten gibt, Bash kann das nur über zusätzliche Konfiguration
  • Für bereits vollständig eingegebene Wörter zeigen jedoch beide Shells keine Beschreibungen an
  • Dadurch müssen Nutzer, um eine Beschreibung zu sehen, den folgenden umständlichen Ablauf durchlaufen
    • Einige Zeichen löschen, sodass mehrere Kandidaten passen
    • Die Taste <TAB> drücken, um die Kandidatenliste anzuzeigen
    • Die gewünschte Beschreibung visuell suchen
    • Die gelöschten Zeichen erneut eingeben, um den Befehl auszuführen

Überblick über den Lösungsansatz

  • Auch bei einem einzelnen Kandidaten wird eine Dummy-Vervollständigung (dummy completion) hinzugefügt, um Mehrdeutigkeit zu erzeugen
  • Dadurch geben sowohl Bash als auch Zsh die Kandidatenliste samt Beschreibung aus
  • Dabei muss jedoch darauf geachtet werden, dass der Beschreibungstext nicht in den eigentlichen Befehl eingefügt wird

Grundkonzept

  • Tab-Vervollständigung arbeitet so, dass bei Eingabe von <TAB> das aktuelle Wort und die Cursorposition übernommen und anschließend mögliche Kandidatenlisten zurückgegeben werden
  • Bash: Kandidaten werden dem Array COMPREPLY zugewiesen
  • Zsh: Kandidaten werden mit dem Befehl compadd registriert
  • Die Funktion _generate_foo_completions gibt Kandidaten-Strings aus; in der Praxis können diese dynamisch auf Basis des CLI-Status erzeugt werden

Bash und Zsh gleichzeitig unterstützen

  • Implementierung passend für jede Shell über die Funktionen _complete_foo_bash und _complete_foo_zsh
  • Unterscheidung über if [ -n "${ZSH_VERSION:-}" ]; then ... elif [ -n "${BASH_VERSION:-}" ]; then ... fi
  • Nutzer registrieren das Skript in .bashrc oder .zshrc und wenden es anschließend an

Anzeige von Beschreibungen in Zsh

  • Für Kandidaten-Strings wird das Format Name: Beschreibung verwendet
  • Zsh: Übergibt Name und Beschreibung als parallele Arrays mit compadd -d raw -- $trimmed
  • Bash: Übergibt nur Kandidaten an COMPREPLY, bei denen der Beschreibungsteil entfernt wurde (standardmäßig ohne Beschreibungsunterstützung)

Beschreibungen in Bash implementieren

  • Bei mehreren Kandidaten werden Strings mit enthaltener Beschreibung unverändert angezeigt
  • Nur bei einem einzelnen Kandidaten wird die Beschreibung entfernt
  • Dabei wird das Verhalten der Bash-Vervollständigung genutzt, die nur das gemeinsame Präfix einfügt, sodass die Beschreibung nicht in die tatsächliche Eingabe übernommen wird

Beschreibungen auch bei einem einzelnen Kandidaten anzeigen

  • Damit auch bei <TAB> auf einem vollständig vervollständigten Wort eine Beschreibung erscheint, wird ein Dummy-Kandidat ergänzt, um Mehrdeutigkeit zu erzwingen
  • Zsh: Fügt den Dummy-Kandidaten sowohl zu den beiden parallelen Arrays raw und trimmed hinzu
  • Bash: Fügt bei einem einzelnen Kandidaten nur den Namen zu trimmed hinzu

Endergebnis

  • Bei mehreren Kandidaten werden Name und Beschreibung vollständig angezeigt
  • Auch bei einem einzelnen Kandidaten lässt sich die Beschreibung per <TAB> anzeigen
  • Bash und Zsh bieten beide dieselbe User Experience
  • Anwendungsbeispiel:
    $ foo <TAB>  
    apple: a common fruit banana: starchy and high in potassium  
    apricot: sour fruit... cherry: small and sweet...  
    

1 Kommentare

 
GN⁺ 2025-08-13
Hacker-News-Kommentare
  • In fish reicht es, einfach fish_update_completions auszuführen, wenn ein interessantes Programm eine man-Seite bereitstellt
    • Dieser Befehl parst alle man-Seiten des Systems und erzeugt Autovervollständigungsdateien unter ~/.cache/fish/generated_completions/
    • Wenn die man-Seite lückenhaft ist oder fehlt, kann man sie selbst schreiben und upstream beitragen
    • Das Format von fish ist simpel, daher reicht die offizielle Dokumentation völlig aus
    • Beispiel: Bei curl bedeutet -L „Follow redirects“, -O „Write output to file named as remote file“
    • Beim Screensharing denken die Leute oft, ich würde zsh und Plugins benutzen, tatsächlich nutze ich aber nur ein hübsches fish mit den Standardeinstellungen
    • Es wäre aber schön, wenn die Nicht-Präfix-Autovervollständigung verbessert würde, durch die car TAB zu blkdiscard erweitert wird und dadurch auch dann Fehlverhalten entsteht, wenn cargo gar nicht im PATH ist
    • Bei Programmen ohne man-Seite, die nur --help anbieten, frage ich mich, ob fish etwas Ähnliches wie _gnu_generic in zsh oder complete -F _longopt in bash hat
    • Auch für zsh gibt es ein Skript, das Autovervollständigungen auf Basis von man-Seiten erzeugt → zsh-manpage-completion-generator
    • Als ich unter OpenSUSE zypper search fish-completion eingegeben habe, kamen über 200 Pakete heraus, was mir irgendwie verdächtig vorkam
  • Es ist lästig, dass die bash-Autovervollständigung immer „smarter“ wird und Datei-/Verzeichnisvervollständigung blockiert, wenn sie entscheidet, dass ein Dateiname an der aktuellen Cursorposition nicht passend sei
    • Ich finde, sie sollte lieber immer auf Dateinamenvervollständigung zurückfallen
    • Deshalb habe ich schon darüber nachgedacht, die Vervollständigungsskripte komplett abzuschalten
    • In bash gibt es Funktionen wie complete-filename, gebunden an M-/, die den Kontext ignorieren und nur Dateinamen vervollständigen
    • Bei manchen Befehlen ist die Dateivervollständigung komplett kaputt, sodass ich erst mit ls vervollständige und danach den Befehl ändere
    • Wenn ein Dateiname existiert, aber nicht ausführbar ist, wäre eine Meldung wie „file foo.exe exists but it isn't executable“ weniger verwirrend
    • Dass nach complete -r alles wie gewünscht funktioniert, deutet darauf hin, dass das Problem bei den bash-completion-Skripten liegt
    • Das erinnert an die frustrierende UX von Frontend-Validierung in Webformularen, wenn schon nach dem ersten Buchstaben einer E-Mail-Adresse „Invalid email!“ angezeigt wird
  • Das ist ein Artikel von mir, und ich hoffe, dass andere ihn ebenfalls interessant fanden
    • Wenn man zsh-Vervollständigungsskripte statt bei jedem Start neu zu laden in $fpath installiert, werden sie gecacht und die Startgeschwindigkeit steigt
    • Bei der Verteilung über Homebrew lassen sich completions automatisch installieren
    • zsh-Vervollständigung ist umfangreich und daher schwer zu erlernen, aber ich feile in der Firma nach und nach daran, indem ich unserem Ansible-Wrapper-Skript Optionen pro Playbook, Tag-Autovervollständigung usw. hinzufüge
  • In letzter Zeit habe ich bei der CLI-Entwicklung begonnen, die Bibliothek usage zu verwenden → integriert sich mit clap und kann completions, argparse und man-Seiten erzeugen
    • Ich überlege noch, ob es sich lohnt, damit die argparse-Blöcke in fish-Skripten zu ersetzen, aber besser als optparse ist es auf jeden Fall
    • Ich baue ebenfalls ein ähnliches Tool zur Definition von CLI-Spezifikationen und habe gerade Unterstützung für fish hinzugefügt
  • Hinweis auf fx.wtf, das in bash/zsh die Autovervollständigung von JSON-Feldern unterstützt
    • Es ist schlanker als ijq, kann aber je nach Situation nützlich sein
  • Tutorial zum Erstellen eines Completers mit eingebauten zsh-Funktionen → zsh-completions-howto
  • Ich frage mich, ob es standardisierte Flags gibt, damit Programme keine bash-Skripte schreiben müssen
    • In zsh ermöglicht _gnu_generic eine einfache Vervollständigung auf Basis von --help
    • Es gibt Beispiele wie clap_complete in Rust, die Option --generate in ripgrep und die Laufzeit-Erzeugung von Vervollständigungen in PHP Symfony
    • Schön wäre ein Standard wie --completion, den gemeinsame Parser-Bibliotheken automatisch implementieren
  • In ksh kann man einfache Vervollständigungen allein durch die Definition von Arrays umsetzen
    • Beispiel: Wenn man im Array complete_kill_1 Signalnamen einträgt, bekommt der erste Parameter des Befehls kill eine passende Vervollständigung
    • Ich frage mich, ob das ksh93-Syntax ist oder nach oksh zurückportiert wurde
  • Ein Beispiel für ein einfaches zsh-Vervollständigungssnippet, das ich erstellt habe: In der Funktion set-java-home wird mit _describe die Liste ~/apps/java/* nach Versionen vervollständigt
    • Die Struktur ist extrem einfach, fast schon ein One-Liner
  • Vielleicht weil die Hauptfunktion von LLMs die automatische Textvervollständigung ist, erledigt GitHub Copilot in der vscode-Terminalumgebung bash- und zsh-Vervollständigung ziemlich gut