Fuzzy-Suche statt starrer Nummernmenüs
fzf verwandelt jede Liste, die ein Bash-Skript erzeugt, in ein durchsuchbares Auswahlmenü. Statt Nummern abzutippen, tippt der Anwender ein paar Buchstaben und bekommt sofort eine gefilterte Trefferliste. Dieser Artikel zeigt, wie fzf in eigene Skripte integriert wird, inklusive Preview-Fenster, Multi-Select und einem robusten Fallback für Umgebungen ohne fzf.
Inhaltsverzeichnis
- 1. Warum fzf in Bash-Skripten ein eigenes Muster verdient
- 2. Grundprinzip: fzf als Filter zwischen zwei Pipes
- 3. Erste Integration: Auswahl in eine Variable schreiben
- 4. Preview-Fenster: Kontext zur Auswahl anzeigen
- 5. Multi-Select mit -m und die Ergebnisse verarbeiten
- 6. Praxisbeispiel: Git-Branch- und Container-Switcher
- 7. Tastaturbindungen und Layout anpassen
- 8. Fallback: robust bleiben ohne fzf
- 9. fzf im Vergleich zu select, read und whiptail
- 10. Zusammenfassung
- 11. FAQ
1. Warum fzf in Bash-Skripten ein eigenes Muster verdient
fzf ist ein Kommandozeilen-Fuzzy-Finder, der eine Liste von Zeilen von stdin liest und interaktiv nach Teilstrings filtert, während der Benutzer tippt. Für sich genommen ist das schon nützlich, aber der eigentliche Gewinn entsteht, wenn fzf in eigene Bash-Skripte eingebettet wird. Statt ein Skript zu schreiben, das eine nummerierte Liste ausgibt und auf eine Zifferneingabe wartet, liefert man die Liste an fzf und bekommt die gewählte Zeile als Ergebnis zurück.
Der Unterschied wird bei langen Listen deutlich: Ein Menü mit 40 Docker-Containern oder 60 Git-Branches per Nummer zu bedienen, ist mühsam und fehleranfällig. Mit fzf tippt der Anwender einfach ein paar Buchstaben des gesuchten Namens, und die Liste filtert sich in Echtzeit. Das macht Skripte nicht nur schneller bedienbar, sondern auch für neue Teammitglieder zugänglicher, weil sie sich nicht die exakte Schreibweise merken müssen. In den folgenden Abschnitten wird gezeigt, wie fzf Schritt für Schritt zu einem vollwertigen Bestandteil eines Bash-Skripts wird.
2. Grundprinzip: fzf als Filter zwischen zwei Pipes
Das Grundprinzip von fzf ist denkbar einfach: Es liest Zeilen von stdin, zeigt sie in einem interaktiven Fenster an, und schreibt die vom Benutzer ausgewählte Zeile nach stdout. Damit fügt sich fzf nahtlos in die Unix-Pipe-Philosophie ein, die auch den Rest eines Bash-Skripts prägt. Ein einfacher Aufruf wie ls | fzf zeigt bereits, wie das Werkzeug funktioniert, ohne dass eine einzige Zeile eigener Code nötig ist.
Für den produktiven Einsatz in Skripten ist entscheidend, dass fzf selbst keine Aktion ausführt. Es liefert lediglich die Auswahl zurück, die anschließende Verarbeitung bleibt vollständig in der Verantwortung des Bash-Skripts. Diese Trennung macht fzf zu einem reinen Auswahlwerkzeug, das sich mit beliebiger Logik kombinieren lässt, von einem einfachen echo bis zu komplexen Deployment-Routinen.
#!/usr/bin/env bash
# minimal-fzf-example.sh — the simplest possible fzf pipeline
set -euo pipefail
# fzf reads lines from stdin, writes the chosen line to stdout
selected_file=$(find . -maxdepth 2 -type f -name "*.log" | fzf --prompt="Log auswaehlen> ")
if [[ -z "$selected_file" ]]; then
echo "Keine Auswahl getroffen, Abbruch." >&2
exit 1
fi
echo "Ausgewaehlt: $selected_file"
tail -n 50 "$selected_file"
3. Erste Integration: Auswahl in eine Variable schreiben
Der Standardweg, eine fzf-Auswahl in ein Bash-Skript einzubauen, ist die Command Substitution: variable=$(quelle | fzf). Bricht der Anwender die Auswahl mit Esc oder Strg-C ab, gibt fzf einen Nicht-Null-Exit-Code zurück und die Variable bleibt leer. Genau diesen Fall muss jedes robuste Skript explizit prüfen, sonst läuft die nachfolgende Logik mit einer leeren Variable weiter und produziert verwirrende Fehler.
Ein zweites wichtiges Detail betrifft set -e in Kombination mit fzf. Da ein Abbruch der Auswahl einen Fehlercode erzeugt, würde set -e das Skript sofort beenden, sobald der Anwender Esc drückt. In den meisten Fällen ist das sogar erwünscht, aber wer stattdessen eine eigene Fehlermeldung ausgeben möchte, fängt den Exit-Code explizit mit || true ab und prüft danach den Inhalt der Variable. So bleibt die Kontrolle über den Ablauf beim Skript, nicht bei einem impliziten Abbruch.
#!/usr/bin/env bash
set -euo pipefail
choose_branch() {
local branch
# Capture exit code without letting set -e kill the script on Esc/Ctrl-C
branch=$(git branch --format='%(refname:short)' | fzf --prompt="Branch> " --height=40%) || true
if [[ -z "$branch" ]]; then
echo "[INFO] Keine Auswahl getroffen." >&2
return 1
fi
echo "$branch"
}
if selected_branch=$(choose_branch); then
git checkout "$selected_branch"
else
echo "Abgebrochen." >&2
exit 1
fi
4. Preview-Fenster: Kontext zur Auswahl anzeigen
Ein reines Filtern von Zeilen ist bei einfachen Listen ausreichend, wird bei komplexeren Auswahlen aber schnell unübersichtlich. Die Option --preview löst dieses Problem, indem sie ein zweites Fenster öffnet, das für die aktuell markierte Zeile einen beliebigen Befehl ausführt und dessen Ausgabe live anzeigt. Bei einer Dateiliste kann das ein cat oder bat sein, bei einer Container-Liste ein docker inspect, bei einer Commit-Liste ein git show.
Der Platzhalter {} in der Preview-Definition wird von fzf automatisch durch die aktuell selektierte Zeile ersetzt. Damit lässt sich mit wenigen Zeichen ein Kontextfenster bauen, das dem Anwender sofort zeigt, ob die markierte Zeile tatsächlich die gesuchte ist, bevor er sie mit Enter bestätigt. Für Skripte, die zwischen ähnlich benannten Ressourcen unterscheiden müssen, etwa mehreren Umgebungen mit fast identischen Namen, ist das Preview-Fenster oft der entscheidende Unterschied zwischen einer riskanten und einer sicheren Auswahl.
#!/usr/bin/env bash
set -euo pipefail
# Preview shows the last 15 log lines of the selected container
container=$(docker ps --format '{{.Names}}' | \
fzf --prompt="Container> " \
--preview 'docker logs --tail 15 {} 2>&1' \
--preview-window=right:60%:wrap) || true
if [[ -z "$container" ]]; then
echo "Keine Auswahl getroffen." >&2
exit 1
fi
docker exec -it "$container" /bin/sh
5. Multi-Select mit -m und die Ergebnisse verarbeiten
Viele Skript-Aufgaben betreffen nicht nur eine, sondern mehrere Elemente gleichzeitig, etwa mehrere Log-Dateien archivieren oder mehrere Feature-Branches löschen. Die Option -m beziehungsweise --multi aktiviert die Mehrfachauswahl in fzf, markiert mit der Tab-Taste. Jede markierte Zeile wird beim Bestätigen mit Enter als eigene Zeile ausgegeben, getrennt durch Newlines.
Für die Verarbeitung in Bash bedeutet das: Die Ausgabe von fzf gehört in ein Array, nicht in eine einzelne String-Variable, damit auch Einträge mit Leerzeichen korrekt behandelt werden. Das bewährte Bash-Pattern dafür ist mapfile -t array < <(fzf-befehl), das jede Zeile der Ausgabe als eigenes Array-Element speichert. Anschließend iteriert man mit for item in "${array[@]}" über die Auswahl, exakt wie bei jedem anderen sicher befüllten Array.
#!/usr/bin/env bash
set -euo pipefail
# Multi-select: mark entries with Tab, confirm with Enter
declare -a selected_logs=()
mapfile -t selected_logs < <(
find /var/log/app -name "*.log" -mtime +14 | \
fzf -m --prompt="Logs (Tab = markieren)> " --height=60%
)
if [[ ${#selected_logs[@]} -eq 0 ]]; then
echo "Keine Logs ausgewaehlt." >&2
exit 0
fi
echo "Archiviere ${#selected_logs[@]} Dateien..."
for log in "${selected_logs[@]}"; do
gzip -9 "$log" && echo "[OK] $log"
done
6. Praxisbeispiel: Git-Branch- und Container-Switcher
Ein Git-Branch-Switcher gehört zu den beliebtesten fzf-Integrationen, weil er ein alltägliches Problem elegant löst: Statt git branch auszuführen, den Namen abzutippen und Tippfehler zu riskieren, wählt man den Branch aus einer gefilterten Liste. Kombiniert man das mit einem Preview-Fenster, das die letzten Commits des jeweiligen Branches zeigt, entsteht ein Werkzeug, das schneller ist als jede grafische Git-Oberfläche für diesen einen Anwendungsfall.
Dasselbe Muster funktioniert für Docker-Container, Kubernetes-Pods oder SSH-Hosts aus einer .ssh/config. Der gemeinsame Nenner ist immer derselbe: Eine Liste erzeugen, sie an fzf übergeben, das Ergebnis in Bash weiterverarbeiten. Wer dieses Muster einmal verinnerlicht hat, findet in nahezu jedem eigenen Skript eine Stelle, an der eine starre Liste durch eine durchsuchbare fzf-Auswahl ersetzt werden kann, ohne die zugrunde liegende Logik anzufassen.
7. Tastaturbindungen und Layout anpassen
Über --bind lassen sich eigene Tastenkombinationen definieren, etwa um mit ctrl-r die Ansicht neu zu laden oder mit ctrl-y die Auswahl direkt in die Zwischenablage zu kopieren. Für Skripte, die regelmäßig genutzt werden, lohnt es sich, eine kleine Kopfzeile mit --header einzublenden, die dem Anwender die verfügbaren Tastenkombinationen anzeigt, statt sie in der Dokumentation verstecken zu müssen.
Das Layout mit --layout=reverse zeigt die Eingabezeile oben statt unten an, was in Skripten mit langen Ausgaben oft übersichtlicher ist, weil der Cursor direkt unter der zuletzt ausgegebenen Zeile bleibt. Die Höhe mit --height=40% verhindert, dass fzf den gesamten Bildschirm einnimmt, und lässt Kontext des aufrufenden Skripts sichtbar. Diese kleinen Anpassungen entscheiden oft darüber, ob ein Skript sich wie ein durchdachtes Werkzeug anfühlt oder wie eine schnelle Bastellösung.
8. Fallback: robust bleiben ohne fzf
Nicht jede Zielumgebung hat fzf installiert, insbesondere minimal gehaltene Container-Images oder ältere Produktionsserver. Ein Skript, das fzf voraussetzt, ohne das zu prüfen, bricht dort mit einer kryptischen "command not found"-Meldung ab. Der robuste Ansatz prüft mit command -v fzf &>/dev/null, ob das Werkzeug verfügbar ist, und fällt andernfalls auf ein einfaches, aber funktionierendes select-Menü zurück.
Dieser Fallback muss keine identische Funktionalität bieten. Es reicht, wenn er dieselbe Aufgabe mit Bordmitteln löst, auch wenn der Bedienkomfort geringer ist. Diese Absicherung ist besonders wichtig für Skripte, die in unterschiedlichen Umgebungen laufen sollen: lokal mit fzf komfortabel, im CI-Container oder auf frisch aufgesetzten Servern trotzdem funktionsfähig ohne manuelle Nachinstallation.
#!/usr/bin/env bash
set -euo pipefail
choose_from_list() {
local -a items=("$@")
local choice
if command -v fzf &>/dev/null; then
choice=$(printf '%s\n' "${items[@]}" | fzf --prompt="Auswahl> ") || true
else
echo "[INFO] fzf nicht gefunden, Fallback auf select" >&2
PS3="Auswahl (Nummer)> "
select choice in "${items[@]}"; do
[[ -n "$choice" ]] && break
done
fi
echo "$choice"
}
targets=("staging" "production" "canary")
selected=$(choose_from_list "${targets[@]}")
echo "Deploye nach: $selected"
9. fzf im Vergleich zu select, read und whiptail
Für interaktive Auswahlmenüs in Bash stehen mehrere Werkzeuge zur Verfügung, die sich in Bedienkomfort, Abhängigkeiten und Einsatzgebiet unterscheiden. fzf punktet dort, wo Listen lang oder unübersichtlich sind, während das eingebaute select ohne externe Abhängigkeit auskommt und für kurze, feste Listen völlig ausreicht.
| Werkzeug | Abhängigkeit | Fuzzy-Suche | Bester Einsatzfall |
|---|---|---|---|
| fzf | Extern (Paketmanager) | Ja | Lange, dynamische Listen mit Preview |
| select (Builtin) | Keine | Nein | Kurze, feste Listen ohne Zusatzpakete |
| read -p | Keine | Nein | Einzelne Freitext- oder Ja/Nein-Eingaben |
| whiptail / gum | Extern (meist vorinstalliert) | Teilweise | Grafische TUI-Dialoge mit Rahmen |
In der Praxis schließen sich diese Werkzeuge nicht gegenseitig aus. Ein gut gebautes Skript prüft die Verfügbarkeit von fzf und nutzt es, wo es den größten Mehrwert bringt, während es für einfache Ja/Nein-Bestätigungen bei read -p bleibt. Diese pragmatische Kombination liefert den besten Bedienkomfort, ohne unnötige Abhängigkeiten für triviale Fälle einzuführen.
Mironsoft
Shell-Automatisierung und CLI-Tooling für Entwicklerteams
Skripte, die sich wie durchdachte Werkzeuge bedienen lassen?
Wir integrieren fzf und andere interaktive CLI-Werkzeuge in eure Bash-Skripte, mit Preview-Fenstern, Multi-Select und robusten Fallbacks für Umgebungen ohne fzf.
fzf-Integration
Auswahlmenüs mit Preview und Multi-Select für bestehende Skripte nachrüsten
CLI-UX-Review
Bestehende Skripte auf Bedienkomfort und Fallback-Robustheit prüfen
Deployment-Tooling
Interaktive Auswahl für Branches, Container und Umgebungen bauen
10. Zusammenfassung
fzf löst ein konkretes Problem in Bash-Skripten: starre, schwer bedienbare Listen werden zu durchsuchbaren, schnell filterbaren Auswahlmenüs. Die Command Substitution variable=$(quelle | fzf) ist der Grundbaustein jeder Integration, ergänzt um explizite Prüfung auf leere Ergebnisse für den Fall eines Abbruchs. Preview-Fenster mit --preview liefern Kontext zur aktuellen Auswahl, Multi-Select mit -m erlaubt das Markieren mehrerer Einträge für Batch-Operationen.
Der größte Hebel für produktive Skripte liegt in der Kombination aus fzf für komfortable Auswahl und einem funktionierenden Fallback für Umgebungen ohne das Werkzeug. So bleibt ein Skript überall lauffähig, während es lokal und interaktiv den größtmöglichen Bedienkomfort bietet. Wer fzf einmal in ein Skript integriert hat, findet meist schnell weitere Stellen im eigenen Tooling, an denen sich starre Listen durch dieselbe Technik ersetzen lassen.
fzf in Bash — Das Wichtigste auf einen Blick
Grundmuster
variable=$(quelle | fzf) plus explizite Prüfung auf leeres Ergebnis bei Abbruch mit Esc oder Strg-C.
Preview
--preview 'befehl {}' zeigt Kontext zur markierten Zeile, bevor der Anwender bestätigt.
Multi-Select
-m plus mapfile -t array < <(fzf-befehl) für sichere Verarbeitung mehrerer Auswahlen.
Fallback
command -v fzf prüfen und auf select zurückfallen, wenn fzf nicht installiert ist.