Die 2. Auflage meines Scripting-Buchs ist frisch aus der Druckerei und ab Anfang August im Buchhandel erhältlich. Wie schon die erste Auflage dreht sich alles um die drei Scripting-Sprachen Bash, PowerShell und Python. Für die Neuauflage habe ich das Buch vollständig überarbeitet und aktualisiert. Neu dazugekommen sind unter anderem:
Terminal Tuning (»Pimp my Terminal«): Nerd Fonts, Prompt Frameworks, moderne CLI-Tools
Python-Modulverwaltung mit uv
Ein neues Kapitel zum Erstellen und Zeichnen von Diagrammen
Ein neues Kapitel zum E-Mail-Versand per Script
Ein neues Kapitel zur Nutzung von KI-APIs in eigenen Scripts
Viele neue Praxisbeispiele: Claude-Code-Sessions exportieren, Web Scraping mit Playwright, GPS-Tracks aus Fotos extrahieren, WordPress-Bilder nachträglich mit Alt-Beschreibungen versehen usw.
Gerade jetzt ist Scripting-Wissen wichtiger denn je. KI-Tools wie Claude, ChatGPT oder Gemini erzeugen auf Zuruf Scripts – und führen sie oft gleich selbst aus. Das funktioniert erstaunlich gut, solange jemand am Ruder sitzt, der den resultierenden Code auch beurteilen kann: Welche Sprache eignet sich für die Aufgabe? Was macht dieses PowerShell-Cmdlet eigentlich? Ist der generierte Bash-Einzeiler sicher, oder löscht er versehentlich das falsche Verzeichnis? Ohne dieses Grundwissen bleibt nur blindes Vertrauen in den KI-Output – und das ist bei Code, der auf Servern oder Produktivsystemen läuft, keine gute Idee.
Das Buch vermittelt das Fundament, um Scripts selbst oder mit KI-Hilfe zu erstellen: Crashkurse zu den drei Sprachen, das nötige Handwerkszeug (SSH, Git, VS Code, reguläre Ausdrücke, JSON/XML-Verarbeitung) und zahlreiche Praxisbeispiele aus Backup, Bildverarbeitung, Web Scraping, Datenbank- und Cloud-Administration. Wer diese Grundlagen beherrscht, kann KI-generierte Scripts nicht nur einsetzen, sondern auch verstehen, anpassen und im Zweifel selbst debuggen.
Alle Infos zum Buch, eine Leseprobe und die Bestell-Links finden Sie auf der Buchseite.
Für die Neuauflage meines Scripting-Buchs habe ich mich zuletzt mit dem Senden und Empfangen von E-Mails per SMTP und IMAP beschäftigt. Dieser Blog-Beitrag stellt das Standardmodul imaplib vor. Damit können Sie per IMAP Postfächer abrufen und verwalten. Die Anwendung ist ein wenig umständlich, aber dafür können Sie wirklich praktisch alles machen, was das Protokoll IMAP vorsieht.
Kurz erklärt: IMAP und Authentifizierung
IMAP (Internet Message Access Protocol) ist das Standardprotokoll zum Abrufen und Verwalten von E-Mails auf einem Mailserver. Die Verbindung erfolgt normalerweise über Port 993 mit SSL/TLS. Im Unterschied zu POP3 verbleiben die Nachrichten auf dem Server und lassen sich in Ordnern organisieren – IMAP eignet sich daher besonders gut für die automatisierte Verarbeitung.
Klassische Unix/Linux-Mailserver erlauben die Anmeldung mit Benutzername und Passwort. Bei Google-Konten ist das anders: Gmail erfordert entweder App-Passwörter (16-stellige Codes, die Sie nach Aktivierung der Zwei-Faktor-Authentifizierung unter myaccount.google.com/apppasswords erstellen) oder OAuth2. App-Passwörter sind dabei die einfachere Variante für Scripts.
Passwörter sollten nie direkt im Scriptcode stehen. Die Beispiele hier lesen die Zugangsdaten aus Umgebungsvariablen, die wiederum aus einer Datei credentials.env geladen werden:
# credentials.env (chmod 600; in .gitignore aufnehmen!)
IMAP_HOST=mail.example.com
IMAP_USER=username
IMAP_PASS=topSecret
In Python laden Sie die Datei am einfachsten mit python-dotenv:
from dotenv import load_dotenv
load_dotenv("credentials.env")
Das Modul installieren Sie mit pip install python-dotenv oder – wenn Sie uv verwenden – mit uv add python-dotenv.
imaplib: Verbinden, suchen und herunterladen
imaplib ist seit Python 2 Bestandteil der Standardbibliothek und deckt den vollständigen IMAP-Befehlssatz ab. Das folgende Beispiel-Script stellt die Verbindung zur Inbox her, lädt die zehn neuesten E-Mails herunter und zeigt die wichtigsten Metadaten tabellarisch an – mit * als Indikator für noch ungelesene Nachrichten:
* 13-05 17:53 papa.xxx@xxx... AW: Halbjahresabrechnungen
12-05 20:48 kundenxxx@xxx... Terminanfrage Sendungszustel...
12-05 04:02 info@weltxxx... Japan, Korsika oder Sansibar ...
11-05 20:51 noreply@xxx... OpenAI Dev News: Realtime 2.0...
05-05 18:58 cron@webxxx Invitation updated: iprot dev
04-05 08:28 externexxx@fhxxx... Vertrag bereit zur Abbrechnung
imaplib.IMAP4_SSL stellt die Verbindung zum IMAP-Server auf Port 993 her. select öffnet einen Ordner, search liefert eine Liste von Nachrichten-IDs als leerzeichen-getrennte Byte-Zeichenkette, fetch lädt die Rohdaten einer einzelnen oder mehrerer Nachrichten.
Der Parameter readonly=True bei select stellt sicher, dass das Abrufen der Nachrichten keine Nebeneffekte hat – insbesondere werden keine Nachrichten als gelesen markiert. Das ist wichtig, weil IMAP-Server den Status einer Nachricht bereits beim Öffnen verändern können.
search(None, "ALL") liefert eine aufsteigend sortierte Liste aller Nachrichten-IDs im Postfach. Mit split()[-10:] extrahieren Sie die zehn neuesten. Diese IDs werden, getrennt durch Kommas, zu einem einzigen fetch-Aufruf zusammengefasst. Als Fetch-Attribute übergeben Sie FLAGS für den Lesestatus sowie BODY.PEEK[HEADER.FIELDS (...)], um nur die benötigten Header-Felder abzurufen. PEEK verhindert dabei, dass der Server die Nachrichten automatisch als gelesen kennzeichnet.
Jede Zeile der Server-Antwort ist ein Tupel aus Metadaten und rohen Header-Bytes. ParseFlags wertet die Metadaten aus und gibt die IMAP-Flags als Tupel zurück. Wenn \Seen fehlt, gilt die Nachricht als ungelesen. message_from_bytes wandelt die Header-Bytes in ein auswertbares Objekt um.
parsedate_to_datetime macht aus einem RFC-2822-Datum ein datetime-Objekt, das dann mit strftime nach eigenen Vorstellungen formatiert werden kann. parseaddr zerlegt das From-Feld in ein (Name, Adresse)-Tupel. MIME-kodierte Header-Zeichenketten wie =?utf-8?q?...?= entschlüsselt decode_header aus dem Modul email.header.
Die Ergebnisse werden zunächst in einer Liste gesammelt, da die Reihenfolge der Server-Antwort nicht zwingend der Reihenfolge der angefragten IDs entspricht. reversed dreht die Liste beim Ausgeben um, sodass die neueste Nachricht zuerst erscheint.
# Beispieldatei fetch-imap.py
import os, email, imaplib
from email.header import decode_header, make_header
from email.utils import parsedate_to_datetime, parseaddr
from dotenv import load_dotenv
# IMAP-Konfigurationsparameter lesen
load_dotenv("credentials.env")
HOST = os.environ["IMAP_HOST"]
USER = os.environ["IMAP_USER"]
PASS = os.environ["IMAP_PASS"]
# RFC-2047-Header (=?utf-8?q?...?=) dekodieren
def decode_mime(value: str | None) -> str:
return str(make_header(decode_header(value or "")))
# Zeichenkette auf max_len verkürzen
def truncate(text: str, n: int) -> str:
return text if len(text) <= n else text[: n - 3] + "..."
# Verbindung zum IMAP-Server
with imaplib.IMAP4_SSL(HOST) as imap:
imap.login(USER, PASS)
imap.select("INBOX", readonly=True) # nichts ändern!
# IDs aller Nachrichten ermitteln -> Byte-String für fetch()
_, data = imap.search(None, "ALL")
id_set = b",".join(data[0].split()[-10:]) # max. 10
# IDs der ungelesenen Nachrichten
_, unseen_data = imap.search(None, "UNSEEN")
unseen = set(unseen_data[0].split())
# relevante Spalten abfragen (IMAP-Syntax); PEEK, damit
# das Seen-Flag nicht gesetzt wird
fields = "(FLAGS BODY.PEEK[HEADER.FIELDS (DATE FROM SUBJECT)])"
_, rows = imap.fetch(id_set, fields)
# Daten/Spalten dekodieren und formatieren
messages = []
for row in rows:
if not isinstance(row, tuple):
continue
meta, raw_headers = row
# Sequence-Nummer am Beginn der Metadaten (b'4 (FLAGS ...')
seq = meta.split()[0]
is_new = seq in unseen
msg = email.message_from_bytes(raw_headers)
dt = parsedate_to_datetime(msg["Date"])
date = dt.strftime("%d-%m %H:%M")
addr = parseaddr(decode_mime(msg.get("From", "")))[1]
sender = truncate(addr, 20)
raw_sub = decode_mime(msg.get("Subject", "(no subject)"))
subject = truncate(raw_sub, 40)
messages.append((is_new, date, sender, subject))
# in umgekehrter Reihenfolge ausgeben, die neueste Mail zuerst
for is_new, date, sender, subject in reversed(messages):
flag = "*" if is_new else " "
print(f"{flag} {date} {sender:<20} {subject}")
Was imaplib sonst noch kann
Das obige Beispiel kratzt nur an der Oberfläche. imaplib deckt den vollständigen IMAP-Befehlssatz ab:
Vollständige Nachrichten laden: Mit fetch können Sie nicht nur Header, sondern auch den kompletten Nachrichtentext inklusive Anhänge abrufen.
Filtern:search akzeptiert IMAP-Suchkriterien wie FROM, SUBJECT, SINCE oder UNSEEN, um gezielt nach bestimmten Nachrichten zu suchen.
Flags setzen:store setzt oder entfernt Flags wie \Seen, \Flagged oder \Deleted.
Löschen:expunge löscht die als \Deleted markierten Nachrichten endgültig.
Ordner verwalten:copy verschiebt Nachrichten zwischen Ordnern, list gibt alle verfügbaren Mailboxen zurück, create und delete legen neue Ordner an oder entfernen sie.
Kurz gesagt: Von der einfachen Posteingangsübersicht bis zur vollautomatisierten Mailbox-Verwaltung ist alles drin.
Alternativen zu imaplib
Wer die etwas sperrige API von imaplib als mühsam empfindet, kann auf IMAPClient ausweichen. Die Bibliothek ist ein High-Level-Wrapper um imaplib, der insbesondere den Umgang mit Nachrichten-IDs, Flags und Ordnern deutlich angenehmer macht. Das folgende Mini-Beispiel zeigt die Verbindung und das Abrufen der Betreffzeilen aller ungelesenen Nachrichten:
from imapclient import IMAPClient
with IMAPClient(HOST) as client:
client.login(USER, PASS)
client.select_folder("INBOX", readonly=True)
messages = client.search("UNSEEN")
for uid, data in client.fetch(messages, "ENVELOPE").items():
print(data[b"ENVELOPE"].subject.decode())
IMAPClient installieren Sie mit pip install imapclient bzw. uv add imapclient.
Wer IMAP in einem asyncio-Kontext benötigt, greift zu aioimaplib – einer vollständig async-fähigen Bibliothek, da imaplib synchron blockiert. Und imbox ist eine sehr einsteigerfreundliche Abstraktion, die E-Mails als Python-Objekte mit direkt zugänglichen Attributen wie .subject oder .attachments liefert. Der Nachteil: weniger Kontrolle bei komplexen Operationen.
Vor gut zweieinhalb Jahren habe ich hier über pip-Probleme berichtet, die unter aktuellen Ubuntu- und Debian-Systemen aufgetreten sind: externally-managed-environment (PEP 668) verhinderte lokale pip-Installationen wegen Konflikten zwischen System-Paketen. Die damals beschriebenen Lösungen, insbesondere das Einrichten eines virtuellen Environments, funktionieren weiterhin.
Aber inzwischen gibt es eine bessere Option: uv ist ein schneller, moderner Paketmanager für Python, der pip, venv, pipx und noch einige andere Tools auf einmal ersetzt. Wer regelmäßig Python-Module installiert, sollte sich die paar Minuten nehmen, uv kennenzulernen. Es lohnt sich.
Update 8.6.2026: uv und Cron
Update 31.7.2026: uv 0.12 mit neuem Init-Verhalten (src-Verzeichnis)
Das pip-Problem
Zuerst eine kurze Wiederholung meines alten Artikels: Bei immer mehr Linux-Distributionen verhindert PEP 668, dass pip install systemweit Pakete installiert. Stattdessen erscheint die Fehlermeldung externally-managed-environment. Es gibt zwei Auswege: entweder installieren Sie das Python-Modul als Distributionspaket (falls verfügbar), oder sie richten ein Virtual Environment, also ein projektspezifisches Verzeichnis mit eigenen, isolierten Modul-Installationen.
Das Einrichten eines Virtual Environments mit python3 -m venv .venv ist nicht schwierig, aber umständlich. Man muss es bei jedem neuen Terminal-Fenster aktivieren (source .venv/bin/activate oder eine Variante dieses Kommandos für Zsh, Fish oder PowerShell), eine requirements.txt pflegen und bei der Weitergabe des Projekts dem Empfänger erklären, was er zu tun hat. uv macht das alles einfacher.
Was ist uv?
uv ist in der Programmiersprache Rust entwickelt worden und stammt von der Firma Astral. Im März 2026 hat OpenAI Astral übernommen; das Team arbeitet seitdem in der Codex-Abteilung. Ob und wie das die Weiterentwicklung von uv beeinflusst, ist noch nicht absehbar. Das Projekt bleibt aber Open Source (https://github.com/astral-sh/uv).
Was uv von pip unterscheidet:
Es ersetzt pip, venv, pip-tools, pipx und pyenv in einem einzigen Binary.
Es ist laut eigenen Benchmarks 10- bis 100-mal schneller als pip.
Es verwaltet Abhängigkeiten in pyproject.toml und einer Lock-Datei. Das manuelle Pflegen von requirements.txt ist damit vorbei.
Allein im Februar 2026 wurde uv über 126 Millionen Mal heruntergeladen. Es hat sich als De-facto-Standard für neue Python-Projekte etabliert. Auch bei meinen eigenen Python-Projekten ist uv inzwischen eine Selbstverständlichkeit geworden. Das Kommando ist einfach zu bedienen und funktioniert gut.
uv installieren
uv wird unabhängig von Python installiert. Unter macOS gelingt das am einfachsten mit Homebrew:
brew install uv
Bei manchen Linux-Distributionen steht uv als Paket zur Verfügung (apt install uv, dnf install uv). Falls nicht, funktioniert dieses Vorgehen:
curl -LsSf https://astral.sh/uv/install.sh -o uv_install.sh
less uv_install.sh # kurze Kontrolle
sh uv_install.sh
Das Script gibt am Ende Hinweise, wie .local/bin zur PATH-Variablen hinzuzufügen ist. Unter Windows führen Sie folgende PowerShell-Kommandos aus:
Nach der Installation überzeugen Sie sich kurz davon, dass alles geklappt hat:
uv --version
uv 0.11.19 (...)
Updates führen Sie mit brew upgrade uv (macOS) bzw. durch Wiederholung des Installations-Kommandos durch.
Weitere Installationstipps finden Sie auf der GitHub-Seite.
Neues Projekt einrichten
Nehmen wir an, Sie entwickeln ein Script, das die Module requests und beautifulsoup4 benötigt. Die folgenden Kommandos richten das Projekt ein:
uv init my-webscraper # erzeugt das Projektverzeichnis
cd my-webscraper # dorthin wechseln
uv add requests beautifulsoup4 # Module installieren
Falls es schon ein Projektverzeichnis gibt, wechseln Sie mit cd dorthin und führen uv init . aus. uv init erzeugt ein Projektverzeichnis mit folgender Struktur:
uv init kümmert sich also um das Virtual Environment im Verzeichnis .venv. uv add installiert das gewünschte Modul und trägt es in pyproject.toml ein.
Hinweis: Die Datei .gitignore wird nicht erzeugt bzw. verändert, wenn das aktuelle Verzeichnis bereits unter Git-Kontrolle steht. In diesem Fall sollten Sie selbst in .gitignore eine Regel einbauen, die das Verzeichnis .venv ignoriert.
Ihr Script führen Sie mit uv run aus:
uv run main.py
uv run sorgt dafür, dass die richtige Python-Version und das Virtual Environment aktiv sind. Sie müssen sich um nichts weiter kümmern.
Weitere Module fügen Sie mit uv add hinzu, überflüssige entfernen Sie mit uv remove. Normalerweise verwendet uv einfach die gerade installierte Python-Version. Wenn Sie ausnahmsweise für ein Projekt eine andere Version benötigen, installieren und fixieren Sie diese:
uv python install 3.12
uv python pin 3.12
Projektweitergabe
uv pflegt mehrere Dateien für die Projektverwaltung:
pyproject.toml enthält die Abhängigkeiten des Projekts.
uv.lock enthält die exakten Versionsnummern aller installierten Module.
.python-version enthält die gewünschte Python-Version.
Wenn Sie Ihr Projekt weitergeben oder auf einem anderen Rechner einsetzen möchten, geben Sie neben dem Quellcode die ersten beiden Dateien weiter, falls die Python-Version wichtig ist, auch die dritte. Die Empfängerin kann das Projekt dann sofort ausführen:
uv run main.py
uv erkennt pyproject.toml und uv.lock, richtet das Virtual Environment ein und installiert alle erforderlichen Module — vollautomatisch, ohne weitere Anweisungen.
Ältere Projekte mit requirements.txt
Bei bestehenden Projekten, die noch requirements.txt verwenden, bietet uv ebenfalls Unterstützung:
Wer ein vorhandenes Projekt von pip auf uv umstellen möchte, beginnt mit uv init --bare im Projektverzeichnis. Das Flag --bare verhindert, dass Beispieldateien erzeugt oder vorhandene Dateien überschrieben werden. Als Ergebnis erscheint eine minimale pyproject.toml. Im nächsten Schritt importieren Sie die bisherigen Abhängigkeiten aus requirements.txt:
cd my-project
uv init --bare
uv add -r requirements.txt
uv add -r requirements.txt trägt alle Module in pyproject.toml ein und erzeugt gleichzeitig uv.lock mit den exakten Versionsnummern. Mit uv sync installiert uv anschließend alle Module in einem neuen .venv-Verzeichnis — ohne manuelles Aktivieren der Umgebung. Ab jetzt führen Sie Ihr Script mit uv run aus.
Nach erfolgreicher Migration geben Sie künftig pyproject.toml und uv.lock weiter — requirements.txt können Sie löschen. Falls CI-Systeme oder Docker-Builds weiterhin eine requirements.txt benötigen, lässt sie sich jederzeit automatisch aus der Lock-Datei erzeugen:
uv ersetzt auch pipx: Mit uv tool install installieren Sie in Python entwickelte Kommandozeilen-Tools direkt auf Ihrem Rechner, außerhalb eines Projektverzeichnisses. Die Tools landen in .local/share/uv/tools, Links dazu werden in .local/bin angelegt:
uv tool install httpie
httpie --version
3.2.4
Noch praktischer ist uvx: Damit führen Sie ein Tool aus, ohne es permanent zu installieren. uv lädt die erforderlichen Dateien in ein Cache-Verzeichnis und führt das Tool sofort aus — ideal für die gelegentliche Nutzung:
uvx ruff check . # überprüft alle Python-Dateien im Verzeichnis
Mit uv cache clean löschen Sie bei Bedarf die zwischengespeicherten Dateien.
uv und Cron
Wer ein Python-Script per Cron automatisch ausführen möchte, stößt auf zwei Probleme: Erstens ist uv häufig lokal installiert, meist in .local/bin/uv. Cron verwendet eine minimale PATH-Variable und findet das Kommando uv daher nicht. Zweitens setzt uv run voraus, dass das aktuelle Verzeichnis das Projektverzeichnis ist — das ist beim Aufruf per Cron ebenfalls nicht der Fall.
Theoretisch könnten Sie beide Probleme durch die Angabe absoluter Pfaden in der crontab-Datei lösen. Eleganter ist es aber, ein kleines Wrapper-Script zu schreiben:
# Datei /home/user/myproject/run.sh
cd /home/user/myproject
/home/user/.local/bin/uv run main.py
Dieses Script rufen Sie dann aus Cron auf:
# /etc/crontab
0 8 * * * user /home/user/myproject/run.sh
user ersetzen Sie jeweils durch Ihren Benutzernamen. Vergessen Sie nicht, das Script ausführbar zu machen (chmod +x run.sh).
uv 0.12 mit geändertem Init-Verhalten
Mit Version 0.12 hat sich das Verhalten von uv init geändert. Bisher hat uv init einfach main.py eingerichtet. Jetzt ist die Default-Verzeichnisstruktur verschachtelt:
Wenn Sie also uv init my-project aufrufen, erzeugt uv das Verzeichnis src/my-project und speichert dort __init__.py. Den resultierenden Start-Code in __init__.py können Sie nun mit uv run my-project aufrufen. Die Verzeichnisstruktur entspricht den Python-Empfehlungen für Module.
Die src-Verschachtelung mag für große Projekte zweckmäßig sein. Wenn Sie mit uv ein kleines Script einrichten, ist die Verschachtelung aber unnötig. Es hindert Sie niemand daran, weiterhin direkt im Projektverzeichnis my-script.py einzurichten und dieses mit uv run my-script.py auszuführen.
Glücklicherweise muss ich nicht allzu oft unter Windows arbeiten. Aber hin und wieder — aktuell für die Überarbeitung meines Scripting-Buchs — lässt es sich nicht vermeiden. Wenn schon Windows, dann wenigstens so komfortabel wie möglich! Und so habe ich in den vergangenen Wochen mein Terminal/PowerShell-Setup optimiert:
Nerdfont installiert
informativen Prompt eingerichtet (Oh My Posh)
bessere Tastaturunterstützung im Terminal (mit Emacs-Tastenkürzeln!)
Editor für den Textmodus installiert (je nach Geschmack: Edit, nano, Emacs, NeoVim)
sudo aktiviert
Update 27.5.2026: less installiert
Dieser Artikel liefert dazu ein paar Details. Der Text beweist gleichzeitig, dass man selbst unter Windows mit relativ wenig Mühe ein produktives Setup einrichten kann. Das erforderliche Fundament liefert Microsoft direkt aus: das Windows Terminal mit vielen High-end-Funktionen inklusive GPU-Rendering, die PowerShell sowie das Paketverwaltungskommando winget.
PowerShell in einem Windows Terminal mit den JetBrains Nerd Font und »Oh My Posh«
Nerdfonts
Moderne CLI-Tools stellen im Terminal alle erdenklichen Zeichen und Symbole dar, um auf Dateitypen, den Git-Status oder Fehlerursachen hinzuweisen. In »gewöhnlichen« Fonts fehlen diese Zeichen; im Terminal wird dann ein Rechteck, ein Fragezeichen oder ein anderes Ersatzzeichen angezeigt. Das lässt den Charme moderner Kommandos und Prompt-Frameworks ins Leere laufen. Abhilfe schafft die Installation eines Fonts, der einen Coding-Zeichensatz um Tausende Symbole und Sonderzeichen ergänzt.
Auf der Seite https://nerdfonts.com stehen ca. 100 geeignete Fonts zum freien Download zur Auswahl. Aber welcher Font ist der beste? Wenn Sie sich nicht entscheiden können, ist der beliebte JetBrainsMono Nerd Font eine gute Wahl für erste Experimente. Er basiert auf dem freien Mono-Font der Firma JetBrains (IntelliJ, PyCharm etc.). Dieser Font hat noch einen Vorteil: Er lässt sich im Handumdrehen mit winget installieren. Sie sollten die Installation in einem Terminal mit Admin-Rechten durchführen, damit die Fonts auch dann zur Verfügung stehen, wenn Sie in einem Admin-Terminal arbeiten.
# in einem Admin-Terminal
winget install -e --id DEVCOM.JetBrainsMonoNerdFont
Oh My Posh
Die Fish oder die Zsh mit der Erweiterung »Oh My Zsh« zeigen im Prompt alle erdenklichen Kontextinformationen an: den Hostnamen, den Verzeichnisnamen, den Git-Zweig und -Status etc. Genau das kann auch Oh My Posh, eine Plattform- und Shell-unabhängiges Prompt-Framework. Die Installation gelingt unter Windows am schnellsten mit winget:
winget install JanDeDobbeleer.OhMyPosh -s winget
Damit Oh My Posh in interaktiven PowerShell-Sessions aktiviert wird, bauen Sie die folgenden Anweisungen in die Profile-Datei ein (notepad $Profile, wobei Sie notepad durch Ihren Lieblingseditor ersetzen):
# Datei Documents/PowerShell/Microsoft.PowerShell_profile.ps1
# Oh My Posh nur in interaktiven PowerShell-Sessions verwenden
if (-not [Console]::IsInputRedirected -and
(Get-Module -Name PSReadLine -ErrorAction SilentlyContinue))
{
oh-my-posh init pwsh | Invoke-Expression
}
Wenn Sie jetzt ein neues PowerShell-Tab öffnen, wird Oh My Posh erstmals aktiv. Sie werden von einem informativen und mehrfarbigen Default-Prompt begrüßt. Unter https://ohmyposh.dev/docs/themes stehen über 100 weitere Prompt-Themen zur Wahl. Zur Aktivierung bauen Sie den Themennamen in das oh-my-posh-Init-Kommando in der Profile-Datei ein, z.B. so:
Um die neue Konfiguration zu aktivieren, lesen Sie die Profile-Datei neu ein:
. $PROFILE
Starship Eine Alternative zu Oh My Posh ist das Framework Starship. Es wurde in Rust entwickelt und ist schneller/effizienter als Oh My Posh. Dafür gibt es aber weniger vordefinierte Themen; generell ist die Konfiguration sperriger. Ich habe beide Frameworks ausprobiert, bin dann aber bei Oh My Posh geblieben.
Tastenkürzel in der PowerShell
In der PowerShell unterstützt Sie das Modul PSReadLine bei der Kommandoeingabe (siehe auch die Dokumentation zu Set-PSReadLineOption). Standardmäßig schlägt PSReadLine das letzte Kommando mit den selben Anfangsbuchstaben zur Vervollständigung durch Cursor rechts vor. Tab bewirkt, dass begonnenen Dateinamen oder Schlüsselwörter komplettiert werden.
Das Verhalten von PSReadLine kann durch Optionen in der Profile-Datei beeinflusst werden. Diese Datei öffnen Sie am bequemsten mit notepad $Profile, wobei Sie notepad durch Ihren Lieblingseditor ersetzen. Damit die Änderungen wirksam werden, laden Sie die Datei mit . $Profile neu.
Das folgende Listing schlägt einige Änderungen/Verbesserungen vor. Gleich das erste Kommando bewirkt den größten Unterschied: Nach der Eingabe der ersten Buchstaben haben Sie die Wahl zwischen mehreren ähnlichen zuletzt ausgeführten Kommandos, die Sie mit den Cursortasten aus einer Liste wählen. Mit F2 können Sie zwischen der Listenansicht und dem Defaultverhalten (InlineView) umschalten.
Auswahl aus zuletzt ausgeführten Kommandos, die die Buchstaben »ed« enthalten
Falls Sie bei InlineView bleiben wollen, sollten Sie zumindest die beiden HistorySearch-Kommandos in Erwägung ziehen. Normalerweise blättern Cursor auf und Cursor ab durch alle bisherigen Kommandos. Mit den hier vorgeschlagenen Einstellungen können Sie dagegen git eingeben und dann durch die bisherigen git-Kommandos blättern.
Emacs- und Vi-Fans werden begeistert sein, dass die PowerShell per EditMode die vertrauten Tastenkürzel akzeptiert. Die if-Abfrage im folgenden Listing stellt sicher, dass die Einstellungen nur in interaktiven Sessions gelten, aber z.B. nicht, wenn die PowerShell ein einzelnes Kommando via SSH ausführt.
# Ergänzungen in der Profile-Datei
if (-not [Console]::IsInputRedirected -and
(Get-Module -Name PSReadLine -ErrorAction SilentlyContinue))
{
# zeigt Vervollständigungsliste an, Auswahl per Cursortasten
Set-PSReadLineOption -PredictionViewStyle ListView
# Cursor auf/ab berücksichtigen die bisherige Eingabe
Set-PSReadLineKeyHandler -Key UpArrow `
-Function HistorySearchBackward
Set-PSReadLineKeyHandler -Key DownArrow `
-Function HistorySearchForward
# Emacs- oder Vi-Tastenkürzel (per Default: Windows-Tastenkürzel)
Set-PSReadLineOption -EditMode Emacs
Set-PSReadLineOption -EditMode Vi
# besser sichtbare Farbe für Inline-Vervollständigung
Set-PSReadLineOption -Colors @{ InlinePrediction = '#884488' }
# keine Duplikate in der Kommando-History speichern
Set-PSReadLineOption -HistoryNoDuplicates
}
Terminal-Editoren
An GUI-Editoren herrscht unter Windows kein Mangel — die Palette reicht von notepad.exe über Notepad++ bis hin zu VS Code und anderen KI-tauglichen Programmen/IDEs. Aber oft wollen Sie einfach nur ein paar Zeilen Text ändern, eine Konfigurationsdatei vervollständigen etc. — und zwar, ohne das Terminal zu verlassen. (Das gilt insbesondere, wenn Sie via SSH remote arbeiten!) Dazu brauchen Sie einen Editor, der im Terminal ausgeführt werden kann.
edit: Durchaus nicht die schlechteste Wahl ist edit. Mitte 2025 hat Microsoft diesen Mini-Editor vorgestellt — als GitHub-Projekt in der Programmiersprache Rust! Damit liegt Microsoft voll im Zeitgeist. Zur Installation führen Sie winget install microsoft.edit aus. In der Folge lädt edit <file> die gewünschte Datei.
Bemerkenswert an edit ist die intuitive, einfache Bedienung. Text wird mit den Cursortasten markiert, mit Strg+C und Strg+V kopiert und wieder eingefügt etc. Die Cursorposition kann mit der Maus verändert werden, auch das lokalisierte Menü lässt sich per Maus bedienen und gibt IT-Veteranen ein wenig Turbo-Pascal-Vibes. Fortgeschrittene Funktionen fehlen allerdings: kein Syntaxhighlighting, keine Code-Vervollständigung, keine Einstellungen …
Der relativ neue CLI-Editor »Edit«
nano: In der Linux-Welt ist nano das Gegenstück zu edit. Der Editor hat zwar nur relativ wenige Funktionen, ist dafür aber einfach zu bedienen. Praktischerweise zeigt das Programm alle erforderlichen Tastenkürzel gleich in der Statusleiste an. Die Installation gelingt unkompliziert mit winget install -e --id GNU.Nano.
vi/NeoVim: Die einen lieben ihn, andere hassen ihn — das Editor-Urgestein vi. Vi-Fans verwenden unter Windows am besten die Variante NeoVim (siehe https://neovim.io). NeoVim ist aber nur die Basis: Damit das Programm sein ganzes Potential ausschöpfen kann, brauchen Sie diverse Erweiterungen (Git, LSP, Fuzzy Finding usw.) und Konfigurationseinstellungen. Das Setup gelingt am schnellsten mit Frameworks wie LazyVim oder AstroNvim.
Emacs: Mich hat der Vi nie überzeugen können, ich bin im Emacs-Lager. Unter Windows ist das allerdings ein Abenteuer. Von abgespeckten Emacs-Klonen wie mg, zile oder jmacs gibt es keine Windows-Ports, die im Terminal funktionieren. Also muss es die Vollversion sein: winget install -e --id GNU.Emacs. winget kümmert sich leider nicht darum, das Emacs-Installationsverzeichnis zum Path hinzuzufügen. Sie müssen sich selbst um diesen Schritt kümmern. Die ausführbare Datei befindet sich üblicherweise in C:\Program Files\Emacs\emacs-<n.n>\bin.
Beim Start des Editors müssen Sie an die Option -nw denken (no window), sonst erscheint der Emacs in einem eigenen Fenster statt im Terminal. Noch eine Besonderheit betrifft die Konfigurationsdatei ~/.emacs. Die Windows-Version des Emacs liest diese Datei normalerweise (abhängig von der HOME-Umgebungsvariablen) nicht aus C:\Users\name\.emacs, sondern aus C:\Users\name\AppData\Roaming\.emacs. Wenn Emacs Unicode-Zeichen fehlerhaft anzeigt, bauen Sie die folgenden Anweisungen in .emacs ein:
Um unter Windows ein Kommando mit Administratorrechten auszuführen, müssen Sie zuerst umständlich ein Terminal mit Admin-Rechten öffnen. Unter Linux und macOS klappt das mit sudo viel unkomplizierter.
Ab Version 11 / 24H2 gibt es sudo auch unter Windows. Microsoft hat das Kommando komplett neu implementiert und nur den Namen übernommen. Die Funktionsweise und Optionen sind anders als unter Linux oder macOS. Insbesondere gibt es keine (Passwort-)Authentifizierung; stattdessen erscheint vor sudo-Aktivitäten nur der UAC-Bestätigungsdialog (User Account Control).
sudo muss zuerst aktiviert werden. Sie finden die Option in den Einstellungen unter System / Erweitert / Terminal.
sudo unter Windows aktivieren
Es gibt drei Arten, wie sudo-Kommandos ausgeführt werden können: in einem neuen Fenster (gilt per Default, forceNewWindow), mit deaktivierter Eingabe (disableInput, die Standardeingabe wird blockiert) oder inline (normal, also wie unter Linux mit der Möglichkeit, direkt im Terminal mit dem ausgeführten Kommando zu interagieren). Statt in den Einstellungen können Sie sudo auch in einem Terminal mit Admin-Rechten aktivieren:
Sobald sudo zur Verfügung steht, können Sie das Kommando wie in den folgenden Beispielen anwenden. (Das erste Kommando setzt voraus, dass das Programm edit installiert ist.)
Beachten Sie, dass sudo Restart-Service -Name Spoolernicht funktioniert! sudo kann nur »echte« Kommandos (Executables) ausführen, keine CmdLets. Für CmdLets müssen Sie den Umweg über eine neue PowerShell-Instanz nehmen.
Der Windows-Implementierung von sudo fehlt auch die Option -s, um eine neue Shell zu starten. Stattdessen führt sudo pwsh zum Ziel.
Sicherheitsbedenken: Microsoft warnt davor, sudo ohne unmittelbare Notwendigkeit zu aktivieren. Die Warnung bezieht sich insbesondere auf die Inline-Variante. In der sudo-Implementierung von Linux wurden über den Verlauf von Jahrzehnten immer neue Sicherheitsprobleme gefunden und behoben. Vor diesem Hintergrund rate ich dazu, die Warnungen Microsofts ernst zu nehmen. sudo ist eine vergleichsweise neue, bislang eher selten genutzte Funktion.
gsudo: Eine Alternative sudo ist das schon länger verfügbare Kommando gsudo. Dieses Open-Source-Projekt bietet mehr Features als die Microsoft-Implementierung.
less
Wie man als Entwickler/Admin ohne less leben kann ist mir schleiferhaft. Unzähligen Windows-Admins scheint dies aber zu gelingen. Ich habe dazu aber keine Lust. less ist nur ein winget-Kommando entfernt!
Heute kann angeblich jeder programmieren. Sagen zumindest Forscher und CEOs aus der KI-Industrie. »Vibe Coding« ist das Wort des Jahres 2025. Sinngemäß meint das müheloses Programmieren dank KI-Unterstützung. Und tatsächlich ist jede der folgenden Aussagen grundsätzlich richtig, auf jeden Fall nicht ganz falsch:
2023, Andrej Karpathy (KI-Forscher, u.a. bei Tesla und OpenAI): The hottest new programming language is English.
2024, Jensen Huang (NVIDIA): It is our job to create computing technology such that nobody has to program. (…) Everybody in the world is now a programmer.
2025, Mark Zuckerberg (Meta): Probably in 2025, we (…) are going to have an AI that can effectively be a sort of mid-level engineer that you have at your company that can write code.
2026, Boris Cherny (Claude-Code-Erfinder): Coding is largely solved
Aber wie in jedem guten Krimi ist nichts so einfach, wie es scheint.
Traum …
Coding is largely solved ist ein wenig optimistisch. Tatsächlich können Coding-Tools, also IDEs wie Antigravity, Cursor oder VS Code sowie CLI-Tools wie Claude Code oder Codex in Kombination mit einem guten, kommerziellen Sprachmodell von z.B. Anthropic, Google oder OpenAI richtig gut programmieren. Der folgende Prompt führt bei den meisten Systemen innerhalb von Sekunden zum Ziel:
Prompt: Please provide a minimal implementation of ‚brick out‘ for the web browser. Use node. Keep the features to a minimum and the code concise.
Für Nicht-Programmierer besteht die größte Hürde darin, Node.js zu installieren, das Programm auszuführen und das Browserfenster richtig zu öffnen (also z.B. http://localhost:3000).
Agentic Tools funktionieren aber auch dann großartig, wenn es darum geht, sich in eine komplexe, existierende Code-Basis einzuarbeiten.
Prompt: Get me an overview about this project.
In der Folge ist es empfehlenswert, im Projektverzeichnis in AGENTS.md oder CLAUDE.md eine Zusammenfassung der wichtigsten Projektdaten zu speichern. Claude Code erstellt sich diese Datei mit /init gleich selbst, etwas Nacharbeit ist aber zweckmäßig. Der Sinn dieser Datei: Das KI-Tool beginnt nicht jede Session bei Null. Vielmehr weiß es, welche Regeln für Ihr Projekt gelten: Build-Tools, Unit-Tests, Datenbank- und Netzwerkzugriff, Bibliotheken, Schreibweisen usw.
Unter diesen Voraussetzungen können Sie dem KI-Tool Ihrer Wahl jetzt Aufträge geben, welche neuen Features es programmieren soll. Wird der Code anschließend funktionieren? Die Chancen stehen mittlerweile nicht schlecht. Eventuell müssen Sie bei Problemen mit ein paar weiteren Prompts nachhelfen. Idealerweise geben Sie dem Agentic-Coding-Tool die Erlaubnis, den Code selbst zu testen. Dann kann das Tool auftretenden Fehler oft selbst lösen.
… und Wirklichkeit
Jedes Projekt ist anders. KI-Tools brillieren, wenn populäre Programmiersprachen für alltägliche Probleme zur Anwendung kommen: also beim Python-Script zur Auswertung von CSV-Dateien, bei der Node.js-Anwendung für eine Web-UI oder beim Datenbank-Backend samt REST-API.
Bei richtig großen Projekten mit Hunderten, ja Tausenden von Dateien, bei ganz neuen Compiler-Versionen oder Bibliotheken, bei exotischen Programmiersprachen mit wenig Trainingsmaterial im Internet sinkt die Erfolgsquote deutlich. KI-Tools sind weiterhin eine Hilfe, aber nicht im gleichen Ausmaß. Hinweise im Prompt auf zusätzliche Dokumentation, auf interne Projektabhängigkeiten oder sonstige Besonderheiten machen plötzlich einen riesigen Unterschied.
Wer kann die Tools bedienen?
Mit dieser Frage kommen wir zum Kern des Problems: Die Eingangszitate stammen von Personen, die entweder ein kommerzielles Interesse daran haben, dass Coding mit KI funktioniert, oder von IT-Profis, die ein riesiges IT-Grundwissen und eine Menge praktischer Erfahrung mit KI-Tools haben. Das sind genau die Leute, bei denen Coding mit KI tatsächlich richtig gut funktioniert. Die wissen, wie der Prompt richtig formuliert wird, erkennen offensichtlich fehlgeleitete Antworten und greifen korrigierend ein, bevor sich das KI-Tool in eine Sackgasse manövriert.
Wenn Sie regelmäßig den Blog von Simon Willison oder Texte von vergleichbaren Entwicklern lesen, also von Leuten, die täglich programmieren und ständig die neuesten KI-Tools ausprobieren, dann gewinnen Sie den Eindruck: Coding mit KI ist kinderleicht. Drei, vier längere Prompts, schon ist ein neues Feature fertig. Jeder moderne Software Developer arbeitet so.
Tatsächlich ist es aber gerade umgekehrt! Wenn ich in meinem privaten und beruflichen Umfeld über KI spreche, stoße ich auf viel Zurückhaltung. Jeder hat schon KI-Tools ausprobiert, allerdings hat auch jeder schon negative Erfahrungen gemacht. Nur wenige kennen die gerade aktuellen Tools oder Sprachmodelle. Nur wenigen ist klar, wie gut diese Tools mittlerweile sind. (Das ist verständlich: Professionelle Entwickler stehen unter Zeitdruck, sollen Features liefern, Bugs beheben, Sicherheitslücken stopfen. Da bleibt wenig Zeit, um ständig neue KI-Tools auszuprobieren.)
Was heißt programmieren?
Jeder kann programmieren! Vielleicht, aber welche Programme? Für ein kleines Tool oder Spiel, das nur lokal/privat genutzt werden soll, gelten ganz andere Regeln als für professionelle Software. Ja, Vibe Coding macht Spaß. Aber wollen Sie in einem Auto sitzen (oder diesem Auto begegnen), dessen Software so erstellt wurde?
Prompt: Entwickle die Steuerungs-Software für die Lenkung und Bremse eines Autos. Wenn das Lenkrad nach links bzw. rechts gedreht wird, ändere den Einstellwinkel der Räder entsprechend. Wenn das Bremspedal gedrückt wird, aktiviere die Bremsen der vier Räder. Je mehr das Pedal gedrückt wird, desto stärker soll das Auto abgebremst werden. Falls die Räder blockieren, während sich das Fahrzeug noch bewegt, aktiviere das Antiblockiersystem.
Was kann schon schief gehen?
Erfahrungen aus vier Jahren »Scripting«-Unterricht
Die vergangenen vier Jahre habe ich auf der FH JOANNEUM in Kapfenberg das Fach »Scripting« unterrichtet. Die StudentInnen mussten in Zweier- oder Dreiergruppen eine Projektarbeit durchführen. In allen vier Jahren durften sie dabei — ganz offiziell! — KI-Tools zu Hilfe nehmen.
Im Verlauf des ersten Durchlaufs im Wintersemester 2022/23 gab OpenAI die erste Version von ChatGPT frei. Alle waren beeindruckt (auch ich), dass reguläre Ausdrücke jetzt mit KI-Hilfe zusammengestellt werden konnten. Manchmal funktionierten sie sogar. KI-Tools waren bei der Projektarbeit eine gewisse Hilfe, aber keine große.
Bis zum vierten Durchlauf (WS 2025/26) machten KI-Tools gleich mehrere Quantensprünge. Agentic Coding wurde zur Selbstverständlichkeit, zumindest für einen Teil der Teilnehmer. Einige Teams lieferten großartige Projektarbeiten, die 2022/23 aufgrund des Zeitaufwands vollkommen undenkbar gewesen wären. Umgekehrt gilt: Den Code etlicher Arbeiten aus dem Jahr 2022/23 würden heutige KI-Tools mit zwei, drei Prompts direkt liefern. Fertig ist das Projekt! Der Quantensprung in der Qualität von KI-Tools führte also — nicht ganz überraschend — zu einem Quantensprung auch bei den Projektarbeiten. Jeder, jede kann jetzt Programmieren, oder?
Ich will hier aber auf einen anderen Punkt hinaus. Das Vorwissen meiner StudentInnen variiert stark. Manche programmieren seit Jahren, hatten bereits eine solide IT-Ausbildung. Andere sind praktisch neu in der IT. (Die Lehrveranstaltung findet im 3. Semester statt.)
Obwohl alle Teams KI-Tools verwenden dürfen, spiegelt sich das Vorwissen dramatisch in den Projektarbeiten wider. Auch wenn das Niveau der heurigen Projektarbeiten im Durchschnitt viel höher war als drei Jahre zuvor, blieb die Spannbreite unverändert, wurde womöglich noch größer. Teams mit viel IT-Vorwissen bedienten die KI-Tools intelligenter, zielgerichteter und lieferten viel bessere Ergebnisse. Teams, deren Teilnehmer weniger IT-Erfahrung hatten, halfen auch die KI-Tools nur in begrenztem Ausmaß. Obwohl die Ausgangslage für alle gleich war, bleibt es dabei: Besseres Vorwissen, bessere Ergebnisse. Die KI ändert daran nichts, verstärkt eher die Unterschiede.
Fazit
KI macht Software-Entwicklung schneller, effizienter und, wie ich finde, angenehmer. KI nimmt das lästige Formulieren von Schleifen, Methoden und Klassen ab. Es ist nicht mehr so wichtig, ob Sie alle Syntax-Details auswendig kennen — die KI kümmert sich schon darum.
Davon abgesehen ändert sich aber überraschend wenig: Die vernünftige Anwendung von KI-Tools setzt weiterhin ein großes IT-Wissen voraus. Wer mehr Erfahrung hat, mehr Grundlagen kennt, eine solide IT-Ausbildung hat, der/die wird bessere Ergebnisse erzielen, qualitativ guten, sicheren, wartbaren Code produzieren. Das gilt mit oder ohne KI-Tools. Aber mit KI sind Sie in den meisten Fällen schneller fertig.
KI-Tools ändern nichts daran, dass Sie intelligente Prompts formulieren und den resultierenden Code verstehen müssen. Auch in Zukunft müssen Sie im professionellen Segment die Verantwortung für Ihren Code übernehmen. Sie können sich nicht drauf ausreden, dass die KI eben einen Fehler gemacht hat. Es bleibt Ihr Fehler!
Kurz und gut: Professionelle Software-Entwicklung kann eben doch nicht jeder! Vibe Coding ist in diesem Segment nicht zielführend.
PS: Zuletzt die obligatorische Werbeeinschaltung (obwohl diese Website ja eigentlich werbefrei ist, auf jeden Fall frei von externer Werbung): Die vergangenen vier Monate haben Bernd Öggl, Sebastian Springer und ich unser Buch »Coding mit KI« komplett überarbeitet. In meiner fast 40-jährigen Autorenkarriere ist es noch nie vorgekommen, dass ich ein Buch nach nur 18 Monaten so umfassend ändern musste! Wenn Sie sich für KI-assistierte Software-Entwicklung interessieren, werfen Sie einen Blick in das Buch. Es erscheint Anfang Mai.
Seit über 30 Jahren nutze ich Linux, und knapp 25 Jahre davon war die bash meine Shell. Ein eigener Prompt, der das aktuelle Verzeichnis farbig anzeigte, was das Maß der Dinge :-)
Mein Umstieg auf die zsh hatte mit Git zu tun: Die zsh in Kombination mit der Erweiterung Oh my zsh gibt im Prompt direktes Feedback über den Zustand des Repositories (aktiver Zweig, offene Änderungen). Außerdem agiert die zsh in vielen Details »intelligenter« (ein viel strapazierter Begriff, ich weiß) als die bash. Es macht ein wenig Arbeit, bis alles so funktioniert wie es soll, aber ich war glücklich mit meinem Setup.
Seit ein paar Monaten habe ich die Default-Shell meiner wichtigsten Linux-Installationen neuerlich gewechselt. Ich gehöre jetzt zum rasch wachsenden Lager der fish-Fans. fish steht für Friendly Interactive Shell, und die Shell wird diesem Anspruch wirklich gerecht. fish bietet von Grund auf eine Menge Features, die zsh plus diverse Plugins inklusive Oh my zsh erst nach einer relativ mühsamen Konfiguration beherrschen. Die Inbetriebnahme der fish dauert bei den meisten Distributionen weniger als eine Minute — und die Defaultkonfiguration ist so gut, dass weitere Anpassungen oft gar nicht notwendig sind. Und sollte das doch der Fall sein, öffnet fish_config einen komfortablen Konfigurationsdialog im Webbrowser (außer Sie arbeiten in einer SSH-Session).
Die Stärken der fish im Vergleich zu bash und zsh haben aus meiner Sicht wenig mit der Funktionalität zu tun; einige Features der fish lassen sich auch mit bash-Hacks erreichen, fast alle mit zsh-Plugins. Der entscheidende Vorteil ist vielmehr, dass die fish out of the box zufriedenstellend funktioniert. Für mich ist das deswegen entscheidend, weil ich viele Linux-Installationen verwende und keine Zeit dafür habe, mich jedesmal mit dem Shell-Setup zu ärgern. Deswegen hatte ich in der Vergangenheit auf meinen wichtigsten Installationen zsh samt einer maßgeschneiderten Konfiguration, auf allen anderen aber der Einfachheit halber die bash oder eine unkonfigurierte zsh-Installation.
Auf den ersten Blick sieht die »fish« aus wie jede andere Shell
Installation
Die Installation ist schnell erledigt. Alle gängigen Distributionen stellen fish als Paket zur Verfügung. Also apt/dnf install fish, danach:
chsh -s $(which fish)
Aus- und neu einloggen, fertig.
Falls Ihnen die fish doch nicht zusagt, ist die bisherige Shell ebenso schnell mit chsh -s $(which bash) oder chsh -s $(which zsh) reaktiviert.
Features
Im Prinzip verhält sich die fish wie jede andere Shell. Insbesondere gelten die üblichen Mechanismen zum Start von Kommandos, zur Ein- und Ausgabeumleitung mit < und >, zur Bildung von Pipes mit | sowie zur Verarbeitung von Kommandoergebnissen mit $(cmd). Was ist also neu?
Während der Eingabe verwendet die fish Farben, um verschiedene Bestandteile Ihres Kommandos (z.B. Zeichenketten) zu kennzeichnen. Das sieht nett aus, der entscheidende Vorteil ist aber, dass Sie oft Tippfehler erkennen, bevor Sie Return drücken: Kommandos, die es gar nicht gibt, werden rot hervorgehoben, ebenso nicht geschlossene Zeichenketten. (Die Farben sind vom aktiven Farbschema abhängig.)
Die Vervollständigung von Kommandos, Optionen, Datei- und Variablennamen mit der Tabulator-Taste ist noch »intelligenter« als bei bash und zsh. fish greift dazu auf über 1000 *.fish-Dateien im Verzeichnis /usr/share/fish/completions zurück, die Regeln für alle erdenklichen Fälle enthalten und mit jeder fish-Version erweitert werden. Die fish zeigt sogar kurze Hilfetexte an (siehe die folgende Abbildung). Wenn es viele mögliche Vervollständigungen gibt, zeigt fish diese in mehreren Spalten an. Sie können mit den Cursortasten das gewünschte Element auswählen.
Bei der Eingabe von Kommandos durchsucht die fish die History, also eine Datei, in der alle zuletzt ausgeführten Kommandos gespeichert wurden. In etwas blasserer Schrift schlägt es das passendste Kommando vor. Die fish berücksichtigt dabei auch den Kontext (welches Verzeichnis ist aktiv, welche Kommandos wurden vorher ausgeführt) und schlägt oft — fast schon ein wenig unheimlich — das richtige Kommando vor. Wenn Sie dieses Kommando ausführen möchten, vervollständigen Sie die Eingabe mit Cursor rechts (nicht Tabulator!) und drücken dann Return. Durch ähnliche Kommandos können Sie mit den Cursortasten blättern.
Alternativ können Sie auch mit Strg+R suchmuster nach früher ausgeführten Kommandos suchen. Die fish sucht nach dem Muster nicht nur in den Anfangsbuchstaben, sondern in den gesamten Zeichenketten der History.
Wenn das aktuelle Verzeichnis Teil eines Git-Repositories ist, zeigt fish den Namen des aktuellen Zweigs in Klammern an. (Wenn Sie mehr Git-Infos sehen wollen, ändern Sie die Prompt-Konfiguration.)
Die »fish« zeigt Hilfetexte zu allen »mysql«-Optionen an, die mit »–default« beginnen.
Globbing-Eigenheiten
In Shells wird die Umwandlung von *.txt in die Liste passender Dateinamen als »Globbing« bezeichnet. Die fish verhält sich dabei fast gleich wie die bash — aber mit einem kleinen Unterschied: Wenn es keine passenden Dateien gibt (z.B. keine einzige Datei mit der Endung .txt), löst die fish einen Fehler aus. Die bash übergibt dagegen das Muster — also *.txt — an das Kommando und überlässt diesem die Auswertung. In der Regel tritt der Fehler dann dort auf. Also kein großer Unterschied?
Es gibt Sonderfälle, in denen das Verhalten der bash günstiger ist. Stellen Sie sich vor, Sie wollen mit scp alle *.png-Dateien von einem externen Rechner auf Ihren lokalen Rechner übertragen:
scp externalhost:*.png .
In der bash funktioniert das wie gewünscht. Die fish kann aber mit externalhost:*.png nichts anfangen und löst einen Fehler aus. Abhilfe: Sie müssen das Globbing-Muster in Anführungszeichen stellen, also:
scp "externalhost:*.png" .
Analoge Probleme können auch beim Aufruf von Paketkommandos auftreten. apt install php8-* funktioniert nicht, wohl aber apt install "php8-*". Hintergründe zum Globbing-Verhalten können Sie hier nachlesen:
Tastenkürzel
Grundsätzlich gelten in der fish dieselben Tastenkürzel wie in der bash. In der fish gibt es darüberhinaus weitere Kürzel, von denen ich die wichtigsten hier zusammengestellt habe. bind oder fish_config (Dialogblatt bindings) liefert eine wesentlich längerer Liste aller Tastenkürzel. Beachten Sie, dass es vom Desktopsystem und vom Terminal abhängt, ob die Alt-Tastenkürzel wirklich funktionieren. Wenn die Kürzel vom Terminal oder dem Desktopsystem verarbeitet werden, erreichen Sie die fish nicht.
Kürzel Bedeutung
------------------ -------------------------------------------------------
Alt+Cursor links führt zurück ins vorige Verzeichnis (prevd)
Alt+Cursor rechts macht die obige Aktion rückgängig (nextd)
Alt+E öffnet den Dateinamen mit $EDITOR
Alt+H oder F1 zeigt die man-Seite zum eingegebenen Kommando an (Help)
Alt+L führt ls aus
Alt+P fügt der Eingabe &| less hinzu (Pager)
Alt+S fügt sudo am Beginn der Eingabe ein
Alt+W zeigt Aliasse und eine Beschreibung des Kommandos (What is?)
Noch eine Anmerkung zu Alt+S: In meiner Praxis kommt es ständig vor, dass ich sudo vergesse. Ich führen also dnf install xy aus und erhalte die Fehlermeldung, dass meine Rechte nicht ausreichen. Jetzt drücke ich einfach Alt+S und Return. Die fish stellt sudo dem vorigen, fehlgeschlagenen Kommando voran und führt es aus.
Konfiguration
Das Kommando fish_config öffnet einen Konfigurationsdialog im Webbrowser. Falls Ihr Webbrowser gerade minimiert ist, müssen Sie das Fenster selbst in den Vordergrund bringen. Im Browser können Sie nun ein Farbenschema auswählen, noch mehr Informationen in den Prompt integrieren, die Tastenkürzel nachlesen etc.
In SSH-Sessions scheitert der Start eines Webbrowsers. In diesem Fall können Sie mit fish_config prompt bzw. fish_config theme das Promptaussehen und das Farbschema direkt im Textmodus verändern.
fish-Konfiguration im Webbrowser
Wenn Sie Änderungen durchführen, werden diese im Terminal mit set -U fish_xxx newvalue ausgeführt und in Konfigurationsdateien in .config/fish gespeichert, insbesondere in:
Das Gegenstück zu .bashrc oder .zshrc ist die Datei .config/fish/config.fish. Das ist der richtige Ort, um eigene Abkürzungen zu definieren, den PATH zu erweitern etc. config.fish enthält einen vordefinierten if-Block für Einstellungen, die nur für interaktive fish-Sessions relevant sind. Alle anderen Einstellungen, die z.B. in Scripts gelten sollen, führen Sie außerhalb durch. Das folgende Listing zeigt ein paar typische Einstellungen:
# Datei .config/fish/config.fish
...
# PATH ändern
fish_add_path ~/bin
fish_add_path ~/.local/bin
# keine fish-Welcome-Nachricht
set -U fish_greeting ""
# Einstellungen nur für die interaktive Nutzung
if status is-interactive
# abr statt alias
abbr -a ls eza
abbr -a ll 'eza -la'
abbr -a gc 'git commit'
# Lieblingseditor
set -gx EDITOR /usr/bin/jmacs
end
Das obige Listing zeigt schon, das die fish gängige Einstellungen anders handhabt als bash und zsh:
Abkürzungen: Anstelle von alias sieht die fish das Kommando abbr vor. alias steht auch zur Verfügung, von seinem Einsatz wird aber abgeraten. abbr unterscheidet sich durch ein paar Details von alias: Die Expansion in das Kommando erfolgt bereits, wenn Sie Return drücken. Sie sehen daher, welches Kommando wirklich ausgeführt wird, und dieses Kommando (nicht die Abkürzung) wird in der History gespeichert.
PATH-Änderungen: Sie müssen die PATH-Variable nicht direkt verändern, sondern können stattdessen fish_add_path aufrufen. Ihr Pfad wird am Ende hinzugefügt, wobei die Funktion sicherstellt, dass es keine Doppelgänger gibt.
Variablen (set): Die Optionen des set-Kommandos zur Einstellung von Variablen funktionieren anders als in der bash:
-g: Die Variable ist in der gesamten fish-Session zugänglich (Global Scope), nicht nur in einer Funktion oder einem Block.
-x: Die Variable wird an Subprozesse weitergegeben (Export).
-U: Die Variable wird dauerhaft in .config/fish/fish_variables gespeichert und gilt daher auch für künftige fish-Sessions (Universal). Sie wird aber nicht exportiert, es sei denn, Sie verwenden -Ux.
-l: Definiert eine lokale Variable, z.B. innerhalb einer Funktion.
Zusätzliche eingebaute Kommandos
Jede Shell hat eine Menge integrierter Kommandos wie cd, if oder set. In der fish können Sie mit builtin -n alle derartigen Kommandos auflisten. Die meisten Kommandos entsprechen exakt den bash- und zsh-Vorgaben. In der fish gibt es aber einige originelle Erweiterungen: math führt einfache Berechnungen aus, random produziert ganzzahlige Zufallszahlen, string manupuliert Zeichenketten ohne die umständliche Parametersubstitution, path extrahiert Komponenten aus einem zusammengesetzten Dateinamen, count zählt Objekte (vergleichbar mit wc -l etc. Das folgende Listing zeigt die Anwendung dieser Kommandos:
math "2.5 * 3.8"
9.5
string split " " "lorem ipsum dolor est"
lorem
ipsum
dolor
est
string replace ".png" ".jpg" file1.png file2.png file3.png
file1.jpg
file2.jpg
file3.jpg
string sub -s 4 -e 8 "abcdefghijkl" # Start und Ende inklusive
defgh
path basename /home/kofler/images/img_234.png
img_234.png
path dirname /home/kofler/images/img_234.png
/home/kofler/images
path extension /home/kofler/images/img_234.png
.png
random 1 100
13
random choice a b c
c
count * # das aktuelle Verzeichnis hat
# 32 Dateien/Verzeichnisse
32
ps ax | count # gerade laufen 264 Prozesse
264
Programmierung
Die Bezeichnung Friendly Interactive Shell weist schon darauf hin: Die fish ist für die interaktive Nutzung optimiert, nicht für die Programmierung. Die fish unterstützt aber sehr wohl auch die Script-Programmierung. Diese ist insofern attraktiv, weil die fish-Entwickler auf maximale Kompatibilität verzichtet haben und die schlimmsten Syntaxungereimtheiten der bash behoben haben. fish-Scripts sind daher ungleich leichter zu verstehen als bash-Scripts. Umgekehrt heißt das leider: fish-Scripts sind inkompatibel zu bash und zsh und können nur ausgeführt werden, wo die fish zur Verfügung steht. Für mich ist das zumeist ein Ausschlusskriterium.
Anstelle einer systematischen Einführung will ich Ihnen hier anhand eines Beispiels die Vorteile der fish beim Programmieren nahebringen. Das Script ermittelt die Anzahl der Zeilen für alle *.txt-Dateien im aktuellen Verzeichnis. (Ich weiß, wc -l *.txt wäre einfacher; es geht hier nur darum, diverse Syntaxeigenheiten in wenig Zeilen Code zu verpacken.) Die bash-Variante könnte so aussehen:
#!/bin/bash
files=(*.txt)
if [ ${#files[@]} -eq 0 ]; then
echo "No .txt files found"
exit 1
fi
for file in "${files[@]}"; do
if [ -f "$file" ]; then
lines=$(wc -l < "$file")
echo "$file: $lines lines"
fi
done
Das äquivalente fish-Script ist deutlich besser lesbar:
#!/usr/bin/env fish
set files *.txt
if not count $files > /dev/null
echo "No .txt files found"
exit 1
end
for file in $files
if test -f $file
echo "$file: "(count < $file)" lines"
end
end
Auf ein paar Details möchte ich hinweisen:
Kontrollstrukturen werden generell mit end abgeschlossen, nicht mit fi für if oder mit esac für case.
Bedingungen für if, for etc. müssen weder in eckige Klammern gestellt noch mit einem Strichpunkt abgeschlossen werden.
Die fish verarbeitet Variablen korrekt selbst wenn sie Dateinamen mit Leerzeichen enthalten. Es ist nicht notwendig, sie in Anführungszeichen zu stellen (wie bei "$file" im bash-Script).
Wenn Sie in eigenen Scripts Optionen und andere Parameter verarbeiten möchten, hilft Ihnen dabei das Builtin-Kommando argparse. Eine gute Zusammenstellung aller Syntaxunterschiede zwischen bash und fish gibt die fish-Dokumentation.
Paketmanager fisher
Das Versprechen von fish ist ja, dass fast alles out-of-the-box funktioniert, dass die Installation von Zusatzfunktionen und deren Konfiguration ein Thema der Vergangenheit ist. Aber in der Praxis tauchen trotzdem immer Zusatzwünsche auf. Mit dem Paketmanager fisher können Zusatzmodule installiert werden. Eine Sammlung geeigneter Plugins finden Sie hier.
Die Geschichte von fish
Die fish ist erst in den letzten Jahren so richtig populär geworden. Das zeigt, dass es auch in der Linux-Welt Modetrends gibt. fish ist nämlich alles andere als neu. Die erste Version erschien bereits 2005.
fish wurde ursprünglich in C entwickelt, dann nach C++ und schließlich nach Rust portiert. Erst seit Version 4.0 (erschienen im Februar 2025) besteht fish ausschließlich aus Rust-Code sowie in fish selbst geschriebenen Erweiterungen.
Fazit
Die fish punktet durch die gut durchdachte Grundkonfiguration und die leichte Zugänglichkeit (Konfiguration und Hilfe im Webbrowser). Es gibt nicht das eine Feature, mit dem sich die fish von anderen Shells abhebt, es ist vielmehr die Summe vieler, gut durchdachter Kleinigkeiten und Detailverbesserungen. Das Arbeiten in der fish ist intuitiver als bei anderen Shells und macht mehr Spaß. Probieren Sie es aus!
Bei der Programmierung ist die fish inkompatibel zu anderen Shells und insofern kein Ersatz (auch wenn die fish-eigenen Features durchaus spannend sind). Zur Ausführung traditioneller Shell-Scripts brauchen Sie weiterhin eine traditionelle Shell, am besten die bash.