Welcome!

I'm Martin Häcker, a software developer with more than 20 years of experience. I can assist with all aspects of professional software development.

My blog is about Software, Liquid Democracy, Go / Baduk / Weiqi, Kochen, choir singing, bouldering, billiard, paragliding, kiting, juggling and other things that interest me. Look around, use my code and subscribe to my feed to enjoy reading the blog in the comfort of your feed reader.

While my website is available in English and German, most of my blog will be single language. Mostly in German, but occasionally in English.

Newest Entries:

Die Freuden einer gut eingerichteten Shell: fzf

written by Martin Häcker on

fzf

Nachdem es bisher in der Serie um die grundlegende Einrichtung der Shell, einen guten Prompt und funktionierende autoomatische Vervollständigung ging, geht es jetzt eine Weile um Werkzeuge um mit der Shell effizient zu navigieren und Dateien und Inhalte zu finden.

Einleitung

Hier geht es mir darum das die Arbeit auf der Shell (auf dem eigenen Rechner vor allem) nur dann schnell und Effizient ist, wenn man schnel und einfach in die Ordner kommt in denen man arbeiten möchte, und die Dateien findet in denen etwas interessantes steht das man entweder lesen oder verändern möchte.

Und natürlich ist das Skillset auch auf beliebige Server transferierbar, weil man alle diese Werkzeuge (oder deren etwas primitivere Variante, dazu später mehr) auch auf einem Server, oder in einem Docker-Container, gerne auch auf einem Kubernetes-Cluster in Produktion einsetzen kann, wo man sonst halt nicht so viele Werkzeuge hat, und schon gar nicht seine IDE anschließen kann um zu versuchen dort Herr der Lage zu werden.

Dazu möchte ich euch die Tools zoxide, grep/ripgrep, fzf, less/cat/bat und direnv vorstellen.

Diese Tools erleichtern viele täglich oft wiederholte Arbeitsabläufe dramatisch, und sie ermöglichen viele Use-Cases, die viele grafischen IDEs und Text-Editoren gar nicht unterstützen. Los geht es mit dem flauschigsten der Tools, fzf.

Zackig die richtige Datei finden: fzf

fzf frei Eingezangendeutscht "der flauschige Kommandozeilen Finder", ist ein werkzeug um Dateien (aber auch beliebige andere Dinge) anhand von teilen des Namens oder Mustern wie jeder erste Buchstabe der Wörter ihres Namens zu finden. Dazu bietet dieses Werkzeug eine Oberfläche die interaktiv die Liste der Auswahlmöglichkeiten filtert während man tippt. Die meisten IDEs bieten so eine Funktion irgendwo mehr oder weniger gut versteckt an, und dieses Werkzeug portiert diese Funktionalität als generisches Werkzeug in die Shell.

Als beispiel: Ich möchte einen bestimmten UnitTest ausführen:

$ bin/run_tests_in_docker.sh $(fzf)

Mit diesem Kommando, wird zuerst fzf aufgerufen (wegen $(fzf)) was dann eine oberfläche präsentiert, mit der man interktiv die richtige oder die richtigen Dateien auswählen kann.

# ich verwende die fish shell, daher brauch ich das $ nicht
❯ bin/run_tests_in_docker.sh (fzf)
  src/models/dokumente/tests/document_distribution_test.py
  src/controller/process_distribution/tests/models_test.py
  src/models/dokumente/tests/dokumente_test.py
  src/integration/d3/api/test/models_test.py
▌ src/models/tests/kontaktdaten_test.py
  5/448 ────────────────────────────────
> models 'test.py

In dem Interface kann man auch mit den Pfeiltasten navigieren, oder einen Eintrag anklicken. Der von mir eingegebene Suchstring "models 'test.py" bedeutet, dass 'models' irgendwo in dem Treffer diese Buchstaben in dieser Reihenfolge vorkommen müssen, während "'test.py" erzwingt das der exakte String 'test.py' vorkommen muss.

Wenn man die fzf-Integration mit der eigenen Shell aktiviert, kriegt man viele weitere Integrationen in die Shell dazu. Zwei Beispiele:

  • ⌃-T sucht (mit Vorschau!) nach Dateien unterhalb des aktuellen Verzeichnisses. Das ist immer dann Praktisch wenn man für ein Kommando eine Datei aus dem aktuellen Projekt als Argument übergeben muss, und spart das tippen von $(fzf). Klar, mit Auto-Vervollständigung kommt man auch ans Ziel, aber das ist soo viel schneller. Insbesondere wenn man nicht genau im Kopf hat wo die Datei liegt, aber noch weiß was in Ihrem Namen oder Pfad vorkommen muss. Das verwende ich die ganze Zeit.

  • ⌃-R sucht mit fzf in der Shell-Historie. Das funktioniert viel besser als die Standard-Suche, die nur nach direkt zusammenhängenden Buchstaben suchen kann. Ein Beispiel: Das Wenn ich das Kommando helm template extensions ./k8s/extensions/ --values ./k8s/extensions/values.dev.yaml | yq aus meiner historie suchen möchte, müsste ich ohne fzf den exakten Text schreiben der in dem Kommando vorkommt.

~
Search History> helmtemplateexten
  76/32637 (0)
  02-17 18:07:03 │ helm template extensions ./k8s/extensions/ --values ./k8s/extensions/values.dev.yaml
  02-17 18:06:10 │ helm template extensions ./k8s/extensions/ --values ./k8s/extensions/values.dev.yaml | yq
  02-17 17:59:53 │ helm template extensions ./k8s/extensions/ --values ./k8s/extensions/values-dev.yaml
  02-17 20:22:18 │ helm template  extensions ./k8s/extensions/ --values ./k8s/extensions/values.dev.yaml
  02-17 18:15:27 │ helm template --debug extensions ./k8s/extensions/ --values ./k8s/extensions/values.dev.yaml
  02-17 17:59:42 │ helm template --dry-run --debug extensions ./k8s/extensions/ --values ./k8s/extensions/values-dev.yaml
▌ 02-17 17:59:29 │ helm template --dry-run --debug  ./k8s/extensions/ --values ./k8s/extensions/values-dev.yaml
  02-17 17:59:36 │ helm template --dry-run --debug foo ./k8s/extensions/ --values ./k8s/extensions/values-dev.yaml
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
│ helm template --dry-run --debug ./k8s/extensions/ --values ./k8s/extensions/values-dev.yaml  │
╰──────────────────────────────────────────────────────────────────────────────────────────────╯

Wenn ich oft, wenn ich ein neues Terminal öffne in die gleichen Projekte navigiere, dann geht das prima über die Shell-History:

# ctrl-r für history suche
Search History> cdmkkapi
  352/32638 (0) ──────────────────────────
  08-12 11:56:19 │ cd mkk/api
  08-24 19:05:13 │ cd ../mkk/api
▌ 05-26 08:39:19 │ cd Code/Projekte/mkk/api
  07-29 17:02:48 │ cd Code/Projekte/mkk/api_infra/
  02-15 08:37:01 │ cd Code/Projekte/mkk/api_infra/monitoring/
╭──────────────────────────╮
│ cd Code/Projekte/mkk/api │
╰──────────────────────────╯

Mit Zoxide geht das noch besser, aber dazu später mehr.

So habe ich meine fzf Integration konfiguriert:

# configure key-bindings for fzf-fish
# ctrl-f directory search
# ctrl-r history search
# ctlr-v variables search
# ctrl-l git log search
# ctrl-s git status search
# ctrl-p processes pid search
fzf_configure_bindings --git_log=\f --directory=\cF --git_status=\cS --processes=\cP

Das geniale an fzf ist, dass es sich so wunderbar in andere Tools integrieren lässt. Hat man es installiert wird es z.B. von KubeCTX verwendet um in kubectx die liste der verbundenen Kubernetes Cluster zu filtern. Oder von kubens um die Liste der Namespaces. Tatsächlich verwenden viele Werkzeuge intern fzf wenn es instaliert ist. Für mich immer wieder eine schöne Überrachung, wenn ein weiteres Werkzeug das ich gerne einsetze fzf verwendet.

Die Freuden einer gut eingerichteten Shell: Autocomplete

written by Martin Häcker on

Was ist schlechte Vervollständigung?

Um zu verstehen was ich mit guter Auto-Completion für Shells meine, brauchen wir erst einmal eine Baseline wie eine schlechte Completion aussieht. Das lässt sich sehr gut mit Docker demonstrieren: 

docker run --rm -it --hostname shell-completion-demo debian

Erstes Experiment: ls⇥⇥ (kein Leerzeichen vor den Tabs!)

Automatische Vervollständigung von Kommandos - unkonfiguriert

Zeigt alle Kommandos die mit ls anfangen Zweites Experiment: ls ⇥⇥ Das zeigt bei mir:

Automatische Vervollständigung von Dateien - unkonfiguriert

Schon mal gut, denn hier werden die Dateien im aktuellen Ordner vervollständigt.

Nächste Schwierigkeitsstufe - kurze und lange Optionen: ls -⇥⇥ (Minus vor dem Tab) Das zeigt hier nichts, genauso für lange Optionen ls --⇥⇥ (zwei mal Minus vor dem Tab)

Keine Ausgabe. ls ist eigentlich so ungefähr das einfachste Programm das jeder Shell beiliegt. Wenn automatische Vervollständigung also irgend etwas kann, dann sollte ls gut funktionieren.

Was ist gute Vervollständigung?

Dagegen mal ein Beispiel von meinem System:

ls⇥ zeigt die Kommandos die mit ls anfangen, mit einer Kurzbeschreibung was diese Kommandos tun.

Vervollständigung von Kommandos mit der Fish-Shell

Schon mit einem Tab sehe ich die Dateien, und zusätzlich sehe ich als Vorschlag den letzten Befehl den ich mit ls abgesetzt habe und kann diesen mit ⌃→ im ganzen, oder mit wortweise akzeptieren kann.

Automatische Vervollständigung von Dateien mit der Fish Shell

Ein ls -⇥ergibt sofort eine Optionsliste - kurz und lang - mit einer Kurzbeschreibung was dieses Schalter tun. Ein zweites Minus und Tabls --⇥` zeigt nur noch die langen Optionen an:

Vervollständigung von Optionen mit der Fish-Shell Vervollständigung von langen Optionen mit der Fish-Shell

Natürlich kann ich mit den Pfeiltasten oder mit Tab eine der Optionen auswählen - natürlich mit ordentlichem Highlighting. So macht arbeiten auf der Shell Spaß!

Falls Ihr verwirrt seid das mein ls andere Optionen anbietet als eures, dann liegt das daran das ich ls durch exa ersetzt habe.

Wie könnt Ihr das bei euch nutzen?

Ich nutze die Fish-Shell, da diese von Haus aus eine sehr gute Autocompletion anbietet. Das ist aber nicht für jede, denn die Syntax der Fish Shell ist etwas anders als bei Bash/ZSH - eben nicht posix kompatibel. Ich mag das Weil es logischer und Kürzer ist, aber ich komme auch nicht durcheinander mit den verschiedenen Shell-Syntaxen da ich sie schon so lange verwende.

Fast alle Shell Konfigrurations-Frameworks wie oh-my-zsh oder Prezto bieten zumindest etwas an das diesem Nahe kommen. Alle automatische Konfiguration stößt aber irgendwann an Ihre Grenzen wenn es um die Kommandos geht, die wir täglich benutzen. docker vervollständigt dann nicht compose und oder kennt die Unterkommandos davon nicht oder nur unvollständig, kubectl und helm sind notorische Kandidaten für die man sich selber kümmern muss.

Jetzt könnte man natürlich versuchen automatisch aus der Hilfsausgabe dieser Kommandos etwas zu generieren (das macht z.B. die Fish shell von sich aus) oder man schreibt selber etwas (argh).

Oder man wendet sich vertrauensvoll an das tool carapace, mit dem man die Completion für Programme komfortabel für alle Shells nachrüsten kann. Als Beispiel um die die Autocompletions für kubectl nachzurüsten, einfach source <(carapace kubectl zsh) oder carapace kubectl fish | source (je nach shell) eingeben und ausprobieren ob es gefällt, und wenn ja, diese Zeile in die User-Konfiguration deiner shell eintragen und viel glücklicher sein.

Obacht: Man kann mit so einem Snippet

# ~/.zshrc 
export CARAPACE_BRIDGES='zsh,fish,bash,inshellisense' # optional
zstyle ':completion:*' format $'\e[2;37mCompleting %d\e[m'
source <(carapace _carapace)

in seiner Shell-Konfiguration alle completer des Carapace Projekts aktivieren. Das hat mir allerdings nicht gefallen,, da ich manche der eingebauten Completer der Fish-Shell noch etwas besser finde als das was Carapace bereit stellt. Aber um Lücken zu ergänzen? Perfekt!

Meine Shell-Completion Konfiguration (fish!) sieht daher so aus:

# enable shell completions
set --global --export CARAPACE_BRIDGES 'zsh,fish,bash,inshellisense'
# I didn't have much luck enabling all carapace completions, but I do like some of them - especially if there is no built in fish completion for them
# carapace _carapace | source
carapace fd | source
carapace bat | source
carapace brew | source
carapace rg | source
carapace docker | source
uv generate-shell-completion fish | source
yq completion fish | source

Die Freuden einer gut eingerichteten Shell: Der Prompt

written by Martin Häcker on

Terminal

Es fängt natürlich mit der Frage an: welche Shell?

Das ist sehr einfach zu beantworten. Auf MacOS ist die zsh die Standard-Shell - und daher sollte man die auch benutzen. Artikel Fertig, vielen Dank fürs Lesen! 😅

Windows hat die PowerShell - wer damit Arbeiten (muss) sollte sie lernen!

Die nackte Shell ist ziemlich schlecht konfiguriert, da die Möglichkeiten der Shell gar nicht ausgenutzt werden. Auf Linux-Distributionen ist es Standard, dass die Shell von der Distribution eingerichtet wird, daher kann man damit meistens schon gut arbeiten.

Aber für MacOS (und Windows) gibt es viel zu tun.

So sieht die zsh unkonfiguriert aus:

dwt@NB1321 ~ % cd Library/Favorites
dwt@NB1321 Favorites %

Das ist schon gar nicht schlecht, denn man sieht einiges:

  • dwt: Welcher Nutzer man gerade ist. Wichtig, wenn man viel mit sudo arbeitet, damit man nicht versehentlich Kommandos als root mit zu vielen Rechten ausführt.
  • NB1321: Auf welchem Rechner man gerade eingeloggt ist. Wichtig wenn man auf vielen Rechnern arbeitet und nicht
  • Den aktuellen Ordner in dem man ist (Favorites). Wichtig damit man z.B. nicht versehentlich die falschen Dateien löscht.
  • Der Prompt % trennt die Meta-Informationen von dem was man selbst eingibt.

Da geht aber noch wesentlich mehr. Bei mir z.B. sieht der Prompt so aus:

~/C/P/m/api ❄️ 🧪 nix-shell-env   🐍 venv ⛵️ mkk-itsc-dev (api) 🌱 main $!  4s
❯

Da ist enthalten:

  • ~/C/P/m/api - nicht nur der aktuelle Ordner, sondern auch jeweils der Anfangsbuchstabe der darüber liegenden Ordner. Das liefert platzsparend viel mehr Kontext, wo man gerade ist. Super wenn man in vielen Projekten gleichnamige Ordner wie z.B. etc, src oder dist hat.
  • ❄️ 🧪 nix-shell-env Zeigt an, dass gerade eine 'dreckige' Nix-Shell aktiv ist. Das verwende ich um Projek-spezifische Abhängigkeiten (Shell-Tools, bestimmte Vesionen von Python etc.) zu installieren die ich deklarativ im Projekt tracke. Gut vergleichbar mit Python-Virtual-Envs - aber eben für alles, nicht nur Python-Pakete. Dreckig ('impure') ist Sie, da nicht nur die Shell-Tools aus der Nix-Shell sichtbar sind, sondern auch alles andere was ich im Betriebsystem installiert habe.
  • 🐍 venv - zeigt an, dass gerade ein Python-Virtual-Env aktiv ist. Da ich hauptsächlich mit Pyton entwickle ist das natürlich entscheidend weil es bedeutet das ich die Projekt-Werkzeuge direkt verwenden kann, da sie im $PATH und $PYTHONPATH sind.
  • ⛵️ mkk-itsc-dev (api) zeigt an, dass ich mit dem mkk-itsc-dev Kubernetes Cluster verbunden bin, und den Namespace api aktiviert habe. Das ist mir sehr wichtig, damit ich nicht versehentlich mit kubectl Befehlen versehentlich den Produktiv-Cluster zerstöre. (Rate wieso mir das so wichtig ist…)
  • 🌱 main $! zeigt an, dass ich in einem Git-Repository bin, auf dem main branch, das ich dinge ge-'stash't habe (die vergisst man sonst sehr leicht) und das ich Änderungen habe die ich noch nicht commited habe. Hier gibt es auch noch mehr informationen wenn das Repo den entsprechenden Zustand hat.
  • Zuletzt sehe ich da das letzte Kommando 4 Sekunden gedauert hat. Das wird nur angezeigt wenn das letzte Programm mehr als 3 Sekunden gedauert hat und ist immer wiede ein hilfreiche Information um bei langlaufenden Prozesen zu verstehen ob es sich lohnt da etwas zu optimieren.

Natürlich könnte man hier noch viel mehr anzeigen, je nachdem an was man gerade arbeitet. Ich habe hier eben die Informationen aufgenommen die ich am meisten benötige.

Jetzt kommt der Clou: Früher habe ich das alles von hand Konfiguriert. Das war ein ewiges und mühsammes gefummel um $PS1 und konsorten so hinzufummeln das das alles funktioniert hat, und hat nie Spaß gemacht. Der Standard ist derzeit, das man einen Shell-Konfigurations-Framework wie Oh-My-Zsh oder Prezto verwendet. Und die sind auch gut. Aber auch langsam, und komplex und kompliziert. Wenn man da mal etwas anders haben will dann gott bewahre wenn das nicht schon vorbereitet ist oder man darf sich nicht nur mit den Komplexitäten von $PS1 auseinandersetzen, sondern auch noch damit wie genau dieser Framework das handhabt.

Inzwischen bin ich weiser geworden, und verwende Starship. Damit kann ich meinen Prompt deklarativ in einer .toml-Datei konfigurieren und diesen auch mitnehmen wenn ich mal die Shell wechsle. (Was häufiger vorkommt als mir lieb ist). Und: Starship funktioniert auch mit der PowerShell.

So sieht meine Konfiguration aus

Wie richtet man das ein?

  1. brew install starship (oder mit nix)
  2. nano ~/.zshrc und am Ende eval "$(starship init zsh)" einfügen.
  3. Konfigurationsdatei erstellen: nano ~/.config/starship.toml und z.b. den Inhalt aus der Datei oben einfügen.
  4. Shell neu starten oder neues Terminal öffnen.

Danke sagen für die grandiose Verbesserung eures Shell-Lebens bei mir nicht vergessen. 😅

Weitere Beiträge…