2 Punkte von GN⁺ 2024-02-07 | 1 Kommentare | Auf WhatsApp teilen
  • Dieser Leitfaden ist eine Open-Source-Designrichtlinie, die die traditionellen UNIX-Prinzipien modernisiert, um CLIs zu entwickeln, die für Menschen gut nutzbar und zugleich robust für die Automatisierung sind
  • Eine gute CLI arbeitet menschenzentriert, sollte aber Konventionen wie stdout/stderr, Exit-Codes, Pipes und JSON einhalten, damit sie sich natürlich mit anderen Programmen kombinieren lässt
  • Hilfe, Dokumentation, Fehlermeldungen und Ausgaben sollten Nutzerinnen und Nutzern den nächsten Schritt erkennen lassen; --help, Beispiele, Vorschläge, Fortschrittsanzeigen und Statusbeschreibungen spielen dabei eine zentrale Rolle
  • Argumente, Flags, Interaktion, Konfiguration und Umgebungsvariablen sollten sowohl Skripte als auch die Terminalnutzung berücksichtigen; geheime Werte nicht direkt über Flags oder Umgebungsvariablen entgegenzunehmen ist sicherer
  • Weder Name, Distribution noch Analyseerfassung einer CLI sollten das Kontrollgefühl der Nutzenden oder die langfristige Kompatibilität beeinträchtigen; auch beim Brechen von Konventionen sollte der Zweck klar sein

Grundphilosophie des CLI-Designs

  • Dieser Leitfaden ist ein Open-Source-Dokument, das auf traditionellen UNIX-Prinzipien basiert und moderne Entwurfsprinzipien sowie konkrete Richtlinien für Kommandozeilenprogramme bietet
  • Früher war die Kommandozeile eher eine maschinenorientierte Umgebung, nahe an einem REPL auf einer Skripting-Plattform; heutige CLIs haben als textbasierte UI für den Zugriff auf verschiedene Tools, Systeme und Plattformen einen stark menschenorientierten Charakter
  • Die Kommandozeile hat alte Beschränkungen und eigenartige Gewohnheiten, ist aber auf fast jedem Laptop verfügbar, eignet sich sowohl für interaktive Nutzung als auch für Automatisierung und verändert sich langsamer als die meisten anderen Systemkomponenten
  • Dieser Leitfaden behandelt keine Vollbild-Terminalprogramme wie emacs oder vim und ist auch nicht an eine bestimmte Programmiersprache oder Toolchain gebunden

Menschenzentrierte und kombinierbare CLIs

  • Wenn eine CLI in erster Linie ein Werkzeug für Menschen ist, sollte man zuerst an Menschen denken
    • Viele CLI-Programme richten sich an menschliche Nutzer und übernehmen dennoch unverändert vergangene, maschinenzentrierte Interaktionsmuster
  • Die UNIX-Philosophie, dass kleine Programme mit sauberen Schnittstellen zusammenarbeiten, ist weiterhin wichtig
    • Konventionen wie Standardeingabe, Standardausgabe, Standardfehler, Signale und Exit-Codes ermöglichen die Kombination von Programmen
    • Zeilenbasierter Klartext lässt sich leicht durch Pipes leiten, und JSON ist nützlich, wenn stärker strukturierte Daten benötigt werden
  • Konsistenz ist der Schlüssel, um die Lernkosten einer CLI in langfristige Effizienz zu verwandeln
    • Wer bestehenden Mustern folgt, macht es Nutzenden leichter, Befehle und Optionen korrekt zu erraten
    • Wenn Konventionen die Nutzbarkeit verschlechtern, dürfen sie auch mit Bedacht gebrochen werden
  • Eine gute CLI sollte weder zu wortkarg sein noch zu viel ausgeben, sondern genau so viele Informationen liefern, wie die Nutzenden brauchen
  • CLIs gelten oft als weniger leicht zu entdecken als GUIs, aber durch umfassende Hilfe, Beispiele, Vorschläge für den nächsten Befehl und Anleitungen zur Fehlerbehebung lässt sich die Erlernbarkeit erhöhen

Interaktive Interaktion und Robustheit

  • Die Nutzung der Kommandozeile ist eher ein Gespräch aus wiederholten Versuchen und Korrekturen als eine einmalige Ausführung
    • Bei fehlerhafter Eingabe kann man nach Möglichkeit Korrekturvorschläge anbieten
    • Zwischenstände in mehrstufigen Abläufen können klar angezeigt werden
    • Vor riskanten Aktionen kann eine Bestätigung eingeholt werden
  • Eine CLI muss tatsächlich robust sein, und zugleich sollten Nutzende sie auch als robust wahrnehmen
    • Unerwartete Eingaben sollten elegant behandelt werden
    • Wenn möglich, sollten Operationen idempotent sein
    • Einschüchternd wirkende Stack-Traces sollten nicht in der Standardausgabe erscheinen
  • Empathie ist wichtig, damit Nutzende das Gefühl haben, dass die Software auf ihrer Seite ist
    • Es geht nicht darum, Emojis zu verwenden oder alles wie ein Spiel wirken zu lassen, sondern darum, sorgfältig so zu gestalten, dass Nutzende erfolgreich sein können
  • Im Terminal-Ökosystem gibt es viele Inkonsistenzen und Verwirrung, aber die geringen Einschränkungen haben auch neue Erfindungen ermöglicht
    • Bestehenden Mustern sollte man folgen, doch Standards, die Produktivität oder Zufriedenheit beeinträchtigen, können bewusst verworfen werden

Grundregeln, die unbedingt eingehalten werden sollten

  • Wenn möglich, sollte eine Bibliothek zum Parsen von Kommandozeilenargumenten verwendet werden
  • Der Exit-Code sollte bei Erfolg 0 sein und bei Fehlern einen von 0 verschiedenen Wert zurückgeben
    • Skripte beurteilen Erfolg oder Misserfolg eines Programms anhand des Exit-Codes
  • Die Hauptausgabe eines Befehls sollte an stdout gesendet werden
    • Auch maschinenlesbare Ausgabe sollte grundsätzlich an stdout gehen, damit Pipes korrekt funktionieren
  • Meldungen für Nutzende wie Log-Nachrichten oder Fehler sollten an stderr gesendet werden
    • Beim Weiterleiten per Pipe vermischen sich diese Meldungen dann nicht mit der Eingabe des nächsten Befehls

Hilfedesign

  • Wenn -h oder --help übergeben wird, sollte ausreichend Hilfe angezeigt werden
    • Auch Unterbefehle können eigene Hilfe haben
    • -h sollte nicht mit einer anderen Bedeutung überladen werden
  • Wenn ein Befehl Argumente benötigt und ohne Argumente ausgeführt wird, sollte eine knappe Hilfe angezeigt werden
    • Programmbeschreibung
    • Ein oder zwei Beispielaufrufe
    • Beschreibung der Flags
    • Hinweis, dass für ausführlichere Informationen --help verwendet werden soll
  • Es ist sinnvoll, in die Hilfe einen Support-Kanal aufzunehmen
    • Eine Website oder ein GitHub-Link ist üblich
  • Wenn es Webdokumentation gibt, sollte die Hilfe dorthin verlinken
    • Wenn es für Unterbefehle bestimmte Seiten oder Anker gibt, ist eine direkte Verlinkung besonders nützlich
  • Da Nutzende eher zuerst Beispiele als Dokumentation heranziehen, ist es sinnvoll, Beispiele weit oben in der Hilfe zu platzieren
    • Zuerst können komplexe, aber häufige Anwendungsfälle gezeigt werden
    • Wenn es viele Beispiele gibt, sind ein eigener Cheat-Sheet-Befehl oder eine separate Webseite angemessen
  • Hilfe kann so formatiert werden, dass sie sich leicht überfliegen lässt
    • Fette Überschriften verbessern die Lesbarkeit
    • Die Darstellung sollte terminalunabhängig erfolgen, damit Escape-Sequenzen nicht unverarbeitet sichtbar werden
  • Wenn Nutzende etwas falsch eingegeben haben und sich die Absicht erraten lässt, sollte ein Vorschlag gemacht werden
    • brew update jq könnte etwa auf brew upgrade jq hinweisen
    • Man kann fragen, ob der vorgeschlagene Befehl ausgeführt werden soll, sollte ihn aber nicht ungefragt ausführen
  • Wenn ein Befehl Eingaben über stdin erwartet, stdin aber ein interaktives Terminal ist, sollte sofort Hilfe angezeigt und das Programm beendet oder eine Meldung auf stderr ausgegeben werden

Dokumentation

  • Hilfe bietet eine sofortige Zusammenfassung und Anleitung für häufige Aufgaben, während die Dokumentation Zweck, Nicht-Zweck, Funktionsweise und die vollständige Nutzung des Werkzeugs ausführlich behandelt
  • Es sollte webbasierte Dokumentation geben
    • Sie ist durchsuchbar und auf bestimmte Abschnitte verlinkbar
    • Webdokumentation ist die umfassendste Form der Dokumentation
  • Es sollte auch terminalbasierte Dokumentation geben
    • Sie ist schnell zugänglich
    • Sie bleibt mit der installierten Tool-Version synchron
    • Sie funktioniert auch ohne Internet
  • Die Bereitstellung von man-Pages kann erwogen werden
    • Viele Nutzende schauen zuerst bei man mycmd nach
    • Wie bei git und npm kann über einen help-Unterbefehl auf man-Pages zugegriffen werden

Ausgaberichtlinien

  • Menschenlesbare Ausgabe ist am wichtigsten
    • Ein einfaches Kriterium dafür, ob ein bestimmter Ausgabestrom für Menschen lesbar sein soll, ist, ob es sich um ein TTY handelt
  • Im Rahmen, der die Nutzbarkeit nicht beeinträchtigt, sollte auch maschinenlesbare Ausgabe bereitgestellt werden
    • Ein Zeilenstrom aus Plain Text ist die universelle Schnittstelle von UNIX
    • Nutzer erwarten, dass sie die Ausgabe an Tools wie grep weiterleiten können und dass dies wie erwartet funktioniert
  • Wenn menschenfreundliche Ausgabe maschinenfreundliche Ausgabe zerstört, kann --plain angeboten werden
    • Wenn in einer tabellarischen Ausgabe Zellen auf mehrere Zeilen aufgeteilt werden, kann das die Erwartung verletzen, dass ein Datensatz genau eine Zeile belegt
    • --plain liefert für Skripte eine unveränderte tabellarische Ausgabe
  • Wenn --json übergeben wird, sollte formatiertes JSON ausgegeben werden
    • JSON ist einfach zu verwenden, um komplexe Datenstrukturen zu verarbeiten, und kann zusammen mit jq und verschiedenen JSON-CLI-Tools genutzt werden
    • Es eignet sich auch gut dafür, über Web-Services und curl direkt per Pipe verbunden zu werden
  • Bei Erfolg sollte ausgegeben werden, aber kurz bleiben
    • Traditionelle UNIX-Befehle geben bei fehlenden Problemen oft nichts aus, für Menschen kann das jedoch so wirken, als sei das Programm hängen geblieben
    • Wenn für Skripte keine Ausgabe nötig ist, kann mit der Option -q nicht essenzielle Ausgabe unterdrückt werden
  • Befehle, die den Zustand ändern, sollten dem Nutzer mitteilen, was sich geändert hat
    • git push ist ein Beispiel dafür, dass während der Ausführung gezeigt wird, was geschieht, und wie der neue Zustand des Remote-Branches aussieht
  • Der aktuelle Zustand des Systems sollte leicht sichtbar sein
    • git status zeigt den Zustand des Repositorys zusammen mit Hinweisen auf die als Nächstes möglichen Befehle
  • Aktionen, die die Grenze zur Außenwelt des Programms überschreiten, sollten in der Regel explizit sein
    • Wenn Dateien gelesen oder geschrieben werden, die der Nutzer nicht als Argument übergeben hat
    • Wenn mit einem Remote-Server kommuniziert wird, um Dateien herunterzuladen
  • Farbe sollte gezielt eingesetzt werden
    • Bei zu häufiger Verwendung verliert sie ihre Bedeutung und erschwert das Lesen
    • Wenn kein TTY vorliegt, NO_COLOR gesetzt ist, TERM=dumb gilt oder --no-color übergeben wird, sollte Farbe deaktiviert werden
  • Wenn stdout kein interaktives Terminal ist, sollten keine Animationen ausgegeben werden
    • So lässt sich verhindern, dass Fortschrittsanzeigen CI-Logs unübersichtlich machen
  • Bei viel Text kann ein Pager wie less verwendet werden
    • Am besten nur dann verwenden, wenn stdin oder stdout ein interaktives Terminal ist
    • less -FIRX kann als sinnvolle Optionskombination verwendet werden

Fehlermeldungen

  • Wenn Fehler wie Dokumentation gestaltet werden, müssen Nutzer weniger Zeit damit verbringen, Dokumentation nachzuschlagen
  • Vorhersehbare Fehler sollten abgefangen und so umgeschrieben werden, dass Menschen sie verstehen können
    • Beispiel: der Hinweis, dass nicht in file.txt geschrieben werden kann und möglicherweise chmod +w file.txt nötig ist
  • Das Verhältnis von Signal zu Rauschen ist wichtig
    • Je mehr irrelevante Ausgabe es gibt, desto schwerer fällt es Nutzern, das Problem zu erkennen
    • Wenn es mehrere Fehler derselben Art gibt, können sie unter einer erklärenden Überschrift zusammengefasst werden
  • Die wichtigste Information sollte möglichst ans Ende der Ausgabe gesetzt werden
    • Der Blick von Nutzern wird von roter Schrift angezogen, daher sollte sie bewusst sparsam eingesetzt werden
  • Wenn ein Fehler unerwartet ist oder sich schwer erklären lässt, sollten Debug- oder Traceback-Informationen und eine Möglichkeit zum Einreichen eines Bugs bereitgestellt werden
    • Um Nutzer nicht zu überfordern, können Debug-Logs statt ins Terminal in eine Datei geschrieben werden
  • Das Einreichen von Bugs sollte einfach gemacht werden
    • Ein Beispiel ist die Bereitstellung einer URL, die mögliche Informationen bereits vorausgefüllt enthält

Argumente und Flags

  • Argumente sind positionsbasierte Parameter, Flags sind benannte Parameter
    • In cp foo bar sind die Dateipfade Argumente
    • -r, --recursive, --file foo.txt sind Flags
  • Wo möglich, sollten Flags gegenüber Argumenten bevorzugt werden
    • Sie erfordern mehr Tipparbeit, aber ihre Bedeutung ist klarer
    • Künftige Änderungen an der Eingabemethode sind einfacher
  • Für alle Flags sollte es einen langen Namen geben
    • Etwa indem -h und --help gemeinsam angeboten werden
    • Das ist nützlich, um in Skripten die Bedeutung klar auszudrücken
  • Einbuchstabige Flags sollten nur für häufig verwendete Optionen genutzt werden
    • So bleibt kurzer Namensraum für Flags erhalten, die später hinzukommen
  • Wenn dieselbe einfache Operation auf mehrere Dateien angewendet wird, sind mehrere Argumente passend
    • Eine Form wie rm file1.txt file2.txt file3.txt passt gut zu Globbing
  • Wenn es zwei oder mehr Argumente mit unterschiedlicher Bedeutung gibt, ist das Design wahrscheinlich fehlerhaft
    • Eine Ausnahme sind häufige und zentrale Aktionen wie cp <source> <destination>, bei denen Kürze so wertvoll ist, dass sie merkbar bleiben sollte
  • Wenn es standardisierte Flag-Namen gibt, sollte man ihnen folgen
    • -a, --all
    • -d, --debug
    • -f, --force
    • --json
    • -h, --help
    • -n, --dry-run
    • --no-input
    • -o, --output
    • -p, --port
    • -q, --quiet
    • -u, --user
    • --version
  • Der Standardwert sollte das richtige Verhalten für die meisten Nutzer liefern
    • Wenn man davon ausgeht, dass Nutzer jedes Mal das passende Flag finden, sich merken und verwenden, verschlechtert sich die Erfahrung für die meisten Nutzer
  • Wenn es keine Eingabe gibt, kann per Prompt gefragt werden, aber ein Prompt darf nicht verpflichtend sein
    • Es sollte immer eine Möglichkeit geben, Eingaben per Flag oder Argument zu übergeben
    • Wenn stdin kein interaktives Terminal ist, sollte der Prompt übersprungen und die erforderlichen Flags oder Argumente verlangt werden
  • Vor gefährlichen Aktionen sollte eine Bestätigung eingeholt werden
    • Bei interaktiver Ausführung kann verlangt werden, y oder yes einzugeben
    • Bei nicht interaktiver Ausführung kann -f oder --force verlangt werden
    • Bei schwerwiegenden Aktionen kann verlangt werden, den Namen des zu löschenden Objekts direkt einzugeben, oder es kann ein Flag wie --confirm="name-of-thing" angeboten werden
  • Wenn Datei-Ein- oder -Ausgabe unterstützt wird, sollte - für stdin oder stdout unterstützt werden
    • So kann die Ausgabe anderer Befehle ohne temporäre Dateien verbunden werden
  • Wo möglich, sollten Argumente, Flags und Unterbefehle unabhängig von ihrer Reihenfolge verarbeitet werden
    • Denn Nutzer fügen häufig am Ende eines bereits verwendeten Befehls noch Optionen hinzu und führen ihn erneut aus
  • Geheimwerte sollten nicht direkt per Flag eingelesen werden
    • Ein Wert für --password kann in der Ausgabe von ps oder in der Shell-Historie sichtbar werden
    • Stattdessen sollten Dateieingaben wie --password-file oder stdin erwogen werden

Interaktion

  • Prompts oder interaktive Elemente sollten nur verwendet werden, wenn stdin ein TTY ist
    • Während Skripten oder Pipe-Eingaben funktionieren Prompts nicht; stattdessen sollte als Fehler ausgegeben werden, welche Flags übergeben werden müssen
  • Wenn --no-input übergeben wird, sollten keine Prompts oder interaktiven Verhaltensweisen stattfinden
    • Wenn Eingabe erforderlich ist, sollte der Vorgang fehlschlagen und erklären, wie die Eingabe per Flag übergeben werden kann
  • Bei der Passworteingabe sollte nicht angezeigt werden, was der Nutzer tippt
    • Dies wird umgesetzt, indem das Echo des Terminals deaktiviert wird
  • Nutzer sollten aussteigen können
    • Auch wenn etwa Netzwerk-I/O blockiert, sollte Ctrl-C funktionieren
    • Wenn es sich um Wrapper wie SSH, tmux oder telnet handelt, die sich nicht mit Ctrl-C beenden lassen, sollte der Ausstiegsweg klar erklärt werden

Unterbefehle

  • Wenn ein Tool hinreichend komplex ist, kann seine Komplexität durch eine Menge von Unterbefehlen reduziert werden
    • Mehrere zusammengehörige Tools unter einem Befehl zu bündeln, kann Nutzung und Auffindbarkeit erleichtern
    • Außerdem lassen sich globale Flags, Hilfe, Konfiguration und Speichermechanismen gut gemeinsam nutzen
  • Zwischen Unterbefehlen ist Konsistenz erforderlich
    • Für dieselbe Bedeutung sollten dieselben Flag-Namen verwendet werden
    • Auch die Ausgabeformate sollten ähnlich sein
  • Für mehrstufige Unterbefehle sollte ein konsistentes Benennungsschema verwendet werden
    • Ein Muster mit zwei Ebenen aus Substantiv und Verb wie docker container create ist verbreitet
    • Sowohl Substantiv Verb als auch Verb Substantiv sind möglich, aber Substantiv Verb scheint üblicher zu sein
  • Mehrdeutige oder ähnlich benannte Befehle sollten vermieden werden
    • Wenn update und upgrade beide vorhanden sind, kann das verwirrend sein

Robustheit

  • Alle vom Nutzer eingegebenen Daten sollten validiert werden
    • Da ungültige Daten eingegeben werden können, sollte früh geprüft und mit einer verständlichen Fehlermeldung abgebrochen werden
  • Reaktionsfähigkeit ist wichtiger als reine Geschwindigkeit
    • Es ist gut, dem Nutzer innerhalb von 100 ms irgendetwas auszugeben
    • Auch vor Netzwerkanfragen sollte eine Meldung ausgegeben werden, damit es nicht so wirkt, als wäre das Programm hängen geblieben
  • Bei lang laufenden Aufgaben sollte der Fortschritt angezeigt werden
    • Wenn eine Zeit lang keine Ausgabe erfolgt, wirkt das Programm schnell kaputt
    • Wenn ein Fortschrittsbalken lange an einer Stelle stehen bleibt, kann eine Restzeitschätzung oder Animation zeigen, dass weiter gearbeitet wird
  • Parallelverarbeitung sollte nach Möglichkeit genutzt werden, aber mit Vorsicht
    • Den Fortschritt paralleler Aufgaben in der Shell anzuzeigen ist schwierig, und durcheinandergeratene Ausgaben sind verwirrend
    • Die mehreren Fortschrittsbalken von docker pull sind ein Beispiel dafür, wie man zeigen kann, was gerade passiert
    • Auch wenn Logs im Normalbetrieb hinter Fortschrittsbalken verborgen werden, sollten sie bei Fehlern ausgegeben werden, damit Debugging möglich ist
  • Netzwerkoperationen sollten Timeouts haben
    • Timeouts sollten konfigurierbar sein und sinnvolle Standardwerte haben
  • Eine Wiederaufnahme nach Fehlern sollte möglich sein
    • Nach einem vorübergehenden Fehler sollte die Ausführung bei erneutem Start mit <up> und <enter> ab der Unterbrechungsstelle fortgesetzt werden können
  • Wo möglich, ist ein crash-only-Ansatz gut
    • Bei Fehlern oder Unterbrechungen kann sofort beendet werden, und das Aufräumen kann auf den nächsten Lauf verschoben werden
  • Nutzer können ein Tool auf unerwartete Weise verwenden
    • Sie können es in Skripte einbinden, mit instabiler Internetverbindung nutzen, mehrere Instanzen gleichzeitig ausführen oder in nicht getesteten Umgebungen starten

Zukunftskompatibilität

  • Unterbefehle, Argumente, Flags, Konfigurationsdateien und Umgebungsvariablen sind allesamt Schnittstellen und sollten langfristig funktionsfähig bleiben
  • Änderungen sollten nach Möglichkeit additiv sein
    • Statt das Verhalten eines bestehenden Flags zu brechen, kann ein neues Flag hinzugefügt werden
  • Wenn nicht additive Änderungen nötig sind, sollte frühzeitig gewarnt werden
    • Wenn ein nicht mehr empfohlener Flag verwendet wird, sollte auf künftige Änderungen hingewiesen und zugleich eine zukunftssichere Alternative genannt werden, die schon jetzt verwendet werden kann
  • Für menschenlesbare Ausgabe sind Änderungen meist unproblematisch
    • Nutzer sollten für stabile Ausgabe in Skripten zu --plain oder --json geführt werden
  • Catch-all-Unterbefehle sollten vermieden werden
    • Wenn unbekannte Unterbefehle automatisch als run behandelt werden, kann das spätere Hinzufügen neuer Unterbefehle bestehende Nutzung brechen
  • Beliebige Abkürzungen von Unterbefehlen sollten nicht erlaubt werden
    • Wenn install auch als i oder ins erlaubt ist, wird es später schwierig, andere Befehle hinzuzufügen, die mit i beginnen
    • Aliasse sollten explizit sein und stabil erhalten bleiben
  • Es sollte bedacht werden, ob ein Befehl auch in 20 Jahren noch auf dieselbe Weise ausgeführt werden kann
    • Man sollte keine Zeitbombe bauen, bei der sich externe Internet-Abhängigkeiten ändern oder verschwinden und der Befehl dadurch ausfällt

Signale und Steuerzeichen

  • Wenn der Nutzer Ctrl-C, also das INT-Signal, sendet, sollte so schnell wie möglich beendet werden
    • Noch bevor mit Aufräumarbeiten begonnen wird, sollte sofort irgendetwas ausgegeben werden
    • Für Aufräumcode sollte es ein Timeout geben
  • Wenn während langer Aufräumarbeiten erneut Ctrl-C gedrückt wird, sollte das Überspringen der Aufräumarbeiten möglich sein
    • Docker Compose weist darauf hin, dass ein weiterer Druck auf Ctrl-C während des Beendens Container sofort zwangsweise stoppen kann
  • Das Programm sollte davon ausgehen, dass es gestartet werden kann, ohne dass frühere Aufräumarbeiten ausgeführt wurden

Konfiguration und Umgebungsvariablen

  • Die Art der Konfiguration sollte sich nach Spezifität, Stabilität und Komplexität richten
    • Für Einstellungen, die sich bei jedem Lauf häufig ändern, sind Flags geeignet
    • Für vergleichsweise stabile Einstellungen, die je nach Nutzer, Projekt oder Maschine variieren, sind Flags und Umgebungsvariablen geeignet
    • Für stabile Einstellungen innerhalb eines Projekts für alle Nutzer ist eine versionsverwaltete, befehlsspezifische Konfigurationsdatei geeignet
  • Es ist sinnvoll, der XDG Base Directory Specification zu folgen
    • Damit werden allgemeine Konfigurationsorte wie ~/.config unterstützt, um die Zunahme von Dotfiles im Home-Verzeichnis zu verringern
  • Wenn Dateien automatisch geändert werden, die nicht die eigene Programmkonfiguration sind, sollte die Zustimmung des Nutzers eingeholt und genau erklärt werden, was getan wird
    • Statt bestehende Systemkonfigurationsdateien zu erweitern, ist es besser, neue Konfigurationsdateien anzulegen
  • Konfigurationsprioritäten sollten von hoch nach niedrig angewendet werden
    • Flags
    • Umgebungsvariablen der aktuell laufenden Shell
    • Konfiguration auf Projektebene
    • Konfiguration auf Nutzerebene
    • Systemweite Konfiguration
  • Umgebungsvariablen eignen sich für Verhalten, das sich je nach Kontext ändert, in dem ein Befehl ausgeführt wird
  • Namen von Umgebungsvariablen sollten für maximale Portabilität nur Großbuchstaben, Zahlen und Unterstriche verwenden und nicht mit einer Zahl beginnen
  • Werte sollten nach Möglichkeit einzeilig sein
    • Mehrzeilige Werte führen bei Verwendung mit dem Befehl env zu Problemen bei der Benutzbarkeit
  • Weit verbreitete Namen sollten nicht leichtfertig belegt werden
    • Dabei kann die Liste der POSIX-Standard-Umgebungsvariablen als Referenz dienen
  • Wo möglich, sollten allgemeine Umgebungsvariablen geprüft werden
    • NO_COLOR, FORCE_COLOR
    • DEBUG
    • EDITOR
    • HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, NO_PROXY
    • SHELL
    • TERM, TERMINFO, TERMCAP
    • TMPDIR
    • HOME
    • PAGER
    • LINES, COLUMNS
  • Falls passend, können Umgebungsvariablen aus .env gelesen werden
    • Das ist nützlich für Variablen, die sich während der Arbeit in einem bestimmten Verzeichnis kaum ändern
  • .env ist kein Ersatz für eine formale Konfigurationsdatei
    • Es wird oft nicht in die Versionsverwaltung aufgenommen
    • Es gibt nur den Typ String
    • Es neigt dazu, unordentlich zu werden
    • Es ist anfällig für Kodierungsprobleme
    • Es enthält leicht sensible Zugangsdaten und Schlüsselmaterial
  • Geheimnisse sollten nicht aus Umgebungsvariablen gelesen werden
    • Umgebungsvariablen können über Prozesse, Logs, docker inspect, systemctl show usw. offengelegt werden
    • Geheimnisse sollten über Credential-Dateien, Pipes, AF_UNIX-Sockets, Secret-Management-Dienste oder andere IPC-Mechanismen übergeben werden

Namen, Verteilung, Analytics-Erfassung

  • Der Name eines CLI-Programms sollte einfach und leicht zu merken sein, da Nutzer ihn häufig eingeben
    • Ist er zu allgemein, kann er mit anderen Befehlen kollidieren oder Nutzer verwirren
  • Der Name sollte möglichst nur Kleinbuchstaben und bei Bedarf Bindestriche verwenden
    • curl ist ein guter Name, DownloadURL ein Gegenbeispiel
  • Der Name sollte kurz sein, aber extrem kurze Namen passen eher zu sehr verbreiteten Utilities wie cd, ls, ps
  • Der Name sollte leicht einzugeben sein
    • Docker Compose hieß anfangs plum, wurde aber zu fig geändert, weil es sich mit einer Hand unhandlich tippen ließ
  • Wenn möglich, sollte als einzelne Binärdatei verteilt werden
    • Wenn eine einzelne Binärdatei schwierig ist, sollte über den nativen Paketinstaller der Plattform verteilt werden, sodass eine Deinstallation einfach ist
    • Sprachspezifische Tools, etwa Code-Linter, können davon ausgehen, dass Nutzer den entsprechenden Sprachinterpreter installiert haben
  • Die Deinstallation sollte einfach sein
    • Da Nutzer das Tool oft direkt nach der Installation wieder entfernen möchten, ist es sinnvoll, die Deinstallationsanleitung direkt unter die Installationsanleitung zu setzen
  • Nutzungsmetriken können helfen, ein Tool zu verbessern, aber CLI-Nutzer erwarten, dass sie ihre Umgebung kontrollieren
  • Nutzungs- oder Crash-Daten sollten nicht ohne Zustimmung übertragen werden
    • Es sollte klar angegeben werden, was gesammelt wird, warum es gesammelt wird, wie stark es anonymisiert wird, wie die Anonymisierung erfolgt und wie lange die Daten aufbewahrt werden
    • Im Idealfall ist opt-in gut; wenn standardmäßige Erfassung per opt-out gewählt wird, sollte dies auf der Website oder beim ersten Start klar kommuniziert werden und leicht abschaltbar sein
  • Auch Alternativen zur Analytics-Erfassung können erwogen werden
    • Webdokumentation instrumentieren
    • Downloads messen
    • Direkt mit Nutzern sprechen und Feedback sowie Feature-Wünsche in Dokumentation und Repository aktiv fördern

1 Kommentare

 
GN⁺ 2024-02-07
Meinungen auf Hacker News
  • Die Aussage „Heutzutage wissen die meisten nicht einmal, was eine Kommandozeile ist“ stimmt zwar, galt aber auch schon in den 1980er-Jahren, die der Artikel als Maßstab nimmt.
    Der Unterschied ist, dass es heute historisch gesehen die meisten Menschen gibt, die die Kommandozeile kennen und nutzen können; die Zahl ist mindestens um eine Größenordnung, vielleicht sogar um zwei Größenordnungen gestiegen. Man kann also durchaus von einem goldenen Zeitalter der CLI sprechen.

    • Wir hängen gerade Kommandozeilen-Tools an Teile unserer App, um den Monolithen aufzubrechen.
      Globaler Zustand und Abhängigkeiten erschweren das Schlussfolgern und machen am Ende auch Debugging und Performance-Optimierung schwieriger.
      Wenn man Code mit 500, 1.000, 5.000, 10.000 oder manchmal 50.000 Zeilen herauslöst, sodass er separat ausführbar ist, wird vieles deutlich klarer.
      Wenn man es richtig macht, kann das auch zu einer Struktur nach Functional Core, Imperative Shell führen, denn eine Kommandozeile im Sinne der Unix-Philosophie sollte viele nebenwirkungsfreie Operationen und nur wenige Operationen mit Nebenwirkungen haben.
      Man kann kleine Befehle bauen, die den Systemzustand kurz vor einer schlechten Entscheidung erzeugen und ausgeben, und sie praktisch gefahrlos auch in der Produktionsumgebung ausführen.
      Dann wird es auch einfacher, solche Tools an Personen weiterzugeben und sie einzubinden, die zum Bus-Faktor gehören sollten.
    • Wenn man „Menschen“ durch „Computer-Nutzer“ ersetzt, trifft der Punkt des Autors zu.
      Wie jemand in einem abgelegenen Dorf einer Amazonas-Zivilisation über Terminals denkt, ist dafür nicht relevant.
    • Man muss zwischen absoluten Zahlen und Anteilen unterscheiden.
      Wenn man qualitative Veränderungen bei einer größer gewordenen Menge beurteilen will, muss man auf die Anteile schauen; nur absolute Zahlen zu betrachten führt zu Aussagen, die zwar wahr, aber bedeutungslos sind.
      Zum Beispiel könnte die absolute Zahl der Jazz-Hörer heute größer sein als zur kulturellen Blütezeit des Genres, aber das liegt nicht daran, dass Jazz beliebter wäre, sondern daran, dass es insgesamt mehr Menschen gibt, die Musik hören.
      Trotzdem lässt sich schwer behaupten, Jazz sei in den USA heute wichtiger und einflussreicher als zu seiner Blütezeit.
    • Die entscheidende Frage ist, ob das auch anteilig unter den Computer-Nutzern der 1980er-Jahre so war.
  • Ich würde mir auch eine --dry-run-Option wünschen, die vorab zeigt, welche Aktion passieren würde, ohne tatsächlich etwas zu ändern.
    Das ist wirklich nützlich, um ein Tool zu lernen oder vor schwer rückgängig zu machenden Aktionen zu prüfen, ob man komplexe Optionen und Datei-Globs korrekt verwendet hat.

    • Wenn ein Befehl nicht rückgängig zu machen ist und große Nebenwirkungen hat, ist es besser, --dry-run zum Standard zu machen und für die tatsächliche Ausführung ein --execute-Flag zu verlangen.
    • WhatIf in PowerShell ist eine meiner Lieblingsfunktionen.
      Ich weiß nicht, wie ich ohne sie ausgekommen wäre; sie hat mich mehrfach vor Situationen bewahrt, in denen ich beinahe alles komplett kaputtgemacht hätte.
    • Umgekehrt bevorzuge ich es, das Standardverhalten so zu gestalten, dass keine echten Änderungen vorgenommen werden, und für die tatsächliche Ausführung --commit zu verlangen.
      Bei Skripten, die nicht oder nur schwer rückgängig zu machende Aktionen ausführen, fühlt sich dieser Ansatz sicherer an.
    • Ich wäre neugierig auf Beispiele für Kommandozeilen-Tools mit einem Dry-Run-Flag.
      Ich bin unsicher, wie ich das bei den Tools, die ich gerade baue, gestalten soll.
      Wenn ich eine API aufrufe, um Daten abzurufen, weiß ich nicht, wie man daraus einen Dry Run machen soll.
      Soll man dann ein Fake-Beispiel und eine Fake-Ausgabe liefern?
    • Meiner Meinung nach sollte dry-run eine Funktion sein, die man an jeden Befehl per Pipe hängen kann: do_thing.py | dry-run
      Man kann es sich so vorstellen, als würde man am Prompt fragen: „Erklär mir den Ablauf Schritt für Schritt.“
  • Es sollte nicht nur heißen, dass keine Animationen angezeigt werden sollen, wenn die Standardausgabe kein interaktives Terminal ist, sondern: Auf stdout sollten niemals Animationen angezeigt werden.
    Der Artikel war insgesamt gut, aber ich war enttäuscht, als ich nach einer Stelle suchte, an der der Unterschied zwischen stderr und stdout erklärt wird, und dann feststellte, dass das nicht passiert.
    stderr ist nicht nur für Fehler da, sondern für Logs und alle informativen Ausgaben; wenn es ein Terminal ist, kann dort auch Animation erscheinen.
    stdout sollte die eigentliche nützliche Ausgabe sein und unabhängig davon, ob es ein Terminal ist, konsistent bleiben.
    Bei echo foo | mysed 's/oo/aa/' | cat zum Beispiel ist stdout von mysed faa, während nach stderr Logs wie Versionsinformationen oder gefundene Inhalte gehören.
    Ich möchte nicht mit grep gegen ein Tool kämpfen müssen, nur um die echte Ausgabe zu bekommen, und ich möchte auch nicht, dass sich das Verhalten ändert, wenn man | cat weglässt, sodass Debugging schwieriger wird.

    • Wenn man die Regel „stdout ist nützliche Ausgabe“ etwas präzisiert, sollte stdout das sein, was der Nutzer angefordert hat.
      Wenn --help angefordert wurde, sollte es nach stdout gehen; wenn Logs angefordert wurden, sollten auch die Logs nach stdout gehen.
      Wenn Informationen nicht angefordert wurden, ist stderr richtig.
    • Eine gute Regel, aber der Name ist unglücklich.
      Wenn man die Zeit zurückdrehen könnte, hätte man Namen wie stdin, stdout und stdext wählen sollen; dann hätten sich Menschen wahrscheinlich eher an diese Konvention gehalten.
      Weil es aber stderr heißt, denken Entwickler vernünftigerweise „für Fehlerberichte“ und stecken alles, was kein Fehler ist, in stdout.
      Trotzdem ist diese Struktur für Programme besser, und auch wenn sie anfangs verwirrender sein kann, ist sie von Vorteil, weil man mit der Ausgabe Nützlicheres tun kann.
    • Dann frage ich mich, wo Animationen angezeigt werden sollten.
      Farben würde ich auch nicht gerade als Information betrachten.
  • Den Rat „Verwende Symbole und Emojis, wo sie Dinge klarer machen“ würde ich bitte vermeiden.
    Das als Beispiel genannte yubikey-agent zeigt genau das, was ich an GitHub-READMEs und verspielten Benutzeroberflächen nicht mag.
    Technisch gesehen können Symbole und Emojis je nach Terminal unterschiedlich gerendert werden und dadurch Nachrichten verwirrend machen.
    Auch ästhetisch unterscheidet sich die Toleranz für Verspieltheit und Fröhlichkeit stark von Person zu Person; man sollte sie daher sehr sparsam einsetzen und nur dann, wenn man wirklich weiß, was man tut.

    • Ich mag solche Ausgaben.
      Ich mag farbige Terminal-Ausgaben, und Emojis haben ebenfalls Farbe, was hilft, schnell ein Gesamtbild der Situation zu erfassen.
      Syntax-Highlighting mag ich auch; ohne finde ich Code viel schwerer lesbar.
      Nicht alle sehen das so, und genauso wenig wie ich erwarte, dass mein Geschmack der Standard ist, sollte auch der gegenteilige Geschmack nicht als Standard aufgezwungen werden.
    • Als optionale Funktion wäre es gut.
      Manche mögen es, andere nicht; der Standard sollte also aus sein, aber Nutzer sollten es auswählen können.
    • Als Richtlinie fände ich es gut, das in Ausgaben verwendete Emoji-Vokabular auf höchstens zwei zu beschränken.
      Zum Beispiel reicht eines für Erfolg und eines für Fehler.
      Außerdem müssen die Informationen, die ein Emoji vermittelt, unbedingt auch als Text wiederholt werden.
    • Stimme stark zu.
      Als ich die CLI Guidelines zum ersten Mal gesehen habe, habe ich bei diesem Abschnitt aufgehört zu lesen.
      Diesmal habe ich auch den Rest gelesen, und insgesamt war es ziemlich ordentlich.
  • Ich verstehe, dass manche CLIs wie aws sehr groß sind und Verschachtelung brauchen, aber sich durch eine verschachtelte CLI durchzuarbeiten, ist wirklich frustrierend.
    Bei den meisten Apps ist es besser, wenn sie in der Hilfe alle Optionen ausgeben und mich mit less das Nötige finden lassen.

    • Ich baue viele TUI-Apps und versehe jede App mit einem guten TUI-Frontend, das auch weniger technische Leute, etwa QA, nutzen können.
      Diese TUI ist eine reine UI/UX, die man optional verwenden kann, und übergibt die gewählten Einstellungen an eine separate generierte Datei oder Funktion.
      Diese Datei oder Funktion kann immer direkt im Terminal aufgerufen werden und stellt auch alle Optionen und Hilfetexte bereit, die die TUI nutzt.
      Dadurch ist sie auch skriptbar und für Nutzer mit unterschiedlichen Erfahrungsstufen nützlich.
    • Es hängt von der Anzahl der Unterbefehle und davon ab, wie klar diese Befehle sind.
      Wenn man den nötigen Unterbefehl kennt, ist es ziemlich nützlich, die Optionen auf das Relevante zu reduzieren; wenn nicht, kann es schmerzhaft sein.
      Eine riesige Optionsliste mit grep zu durchsuchen, ist ebenfalls mühsam, also braucht es ein Gleichgewicht.
    • Ist das nicht die Aufgabe von man-Pages?
    • Es kann helfen, die --help der Unterbefehle in den übergeordneten Befehl aufzuklappen.
      Zum Beispiel so, dass git stash --help direkt die Hilfe zu git stash samt aller Einträge anzeigt.
  • Die Aussage, „traditionell wurden UNIX-Befehle vor allem unter der Annahme geschrieben, dass andere Programme sie nutzen“, ist nicht korrekt.
    Ursprünglich waren sie hauptsächlich für die interaktive Nutzung in einer Login-Shell gedacht.
    Es gab Programme, die Ausgaben auf stdout erzeugten (ls, cat, find, tty, who, date), und stille Textfilter (tr, grep, cut, uniq, sort, wc); in jener Zeit konnte man grundlegende Computing-Aufgaben mit Einzeilern erledigen.
    Komplexe Programme wurden in C geschrieben, und nachdem domänenspezifische Sprachen wie sed und AWK aufkamen, wanderten einige Programme mit viel String-Verarbeitung in die Shell.
    Die Shell ist keine vollwertige Programmierumgebung und war auch nie für diesen Zweck gedacht.

    • Manchmal lässt sich ein C-Programm mit 10.000 Zeilen durch 10 Zeilen Shell ersetzen, und manchmal hätte ein Shell-Programm mit 1.000 Zeilen auch in 100 Zeilen C gepasst.
      Heute ist man mit Python oder Lua für beides oft besser bedient, aber Shell und C sind am weitesten verbreitet installiert.
    • Vollwertig oder nicht: Die Shell ist eine Programmiersprache, und im frühen Unix war das noch viel ausgeprägter.
      Die Aufspaltung in sh, bash, ksh und csh ist ein gutes Beispiel.
      Die standardmäßige POSIX-Tool-Sammlung als Standardbibliothek mehrerer Shell-Sprachen zu betrachten, halte ich für ziemlich plausibel.
  • Die heutige Unix-Kommandozeile ist einerseits „unglaublich nützlich“ und andererseits von der Konstruktion her kaputt.
    Wenn man überlegt, wie lange es dauern würde, Folgendes in C oder Rust zu schreiben, ist die Nützlichkeit offensichtlich:
    curl -sS https://go.dev/doc/devel/release | html2text | grep -o -P '\bgo\d+\.\d+\.\d+\b' | sort -V | uniq | tail -1
    Warum sie aber von der Konstruktion her kaputt ist, sieht man hier: https://news.ycombinator.com/item?id=29747034
    Das Problem ist, dass Kommandozeilen-Interfaces für Menschen gut lesbar und zugleich für Maschinen gut lesbar sein müssen, und dafür gibt es keine standardisierte Lösung.

    • Genau dieses Beispiel zeigt gut, warum das Design kaputt ist.
      Für das Problem, dass etwas zugleich für Menschen und Maschinen lesbar sein muss, hätte es ursprünglich vielleicht eine Standardlösung geben können.
      Apple entwickelte die Human Interface Guidelines, um die Bedeutung visueller Abstraktionen in Desktop-UIs zu konkretisieren.
      Die Kommandozeile wurde aber nicht von Leuten geschaffen, die wie Apple-Designer denken, sondern von Leuten, die dachten: „Faulheit, Ungeduld und Hybris sind Tugenden; wie drücke ich das, was ich will, mit möglichst wenig Code aus?“
      Damals war das keine falsche Entscheidung, und jedes einzelne Byte zählte, aber diese Designentscheidungen sind heute in eine unbewegliche Toolchain eingelassen.
      Um etwas Besseres als heute zu bekommen, müsste man die POSIX-Toolchain faktisch aufgeben; es ist schwer vorstellbar, auf der bestehenden Basis eine auffindbare und konzeptionell konsistente UX aufzubauen.
    • Computer sind letztlich dazu da, uns zu dienen; die endgültige Lösung sollte also darin bestehen, Maschinen so gut lesen zu lassen wie Menschen.
    • In der Praxis nutzen Menschen und Maschinen denselben Befehl meist nicht „gleichzeitig“, sondern zu unterschiedlichen Zeitpunkten.
      Auch zum selben Zeitpunkt gibt es viele Möglichkeiten, unterschiedliche Ausgaben zu ermöglichen.
      Allerdings müsste es einen Standard geben, dem alle folgen, und genau an dieser Stelle wird es kompliziert.
    • Dieses Beispiel beweist nicht nur nicht, dass das Design kaputt ist, sondern scheint sogar eine vergleichsweise einfache Lösung zu zeigen.
      Wenn das Problem darin besteht, dass kaum eine ls-Implementierung Dateinamen mit NUL-Zeichen statt mit Zeilenumbrüchen terminieren kann, wirkt die Lösung so offensichtlich, dass ich den Eindruck habe, etwas zu übersehen.
      Lehnt das Shell-Interface das aus irgendeinem Grund ab?
    • Der im Artikel behandelte Ansatz sind maschinenlesbare Ausgabemodi wie --json.
  • „Geheimnisse nicht aus Umgebungsvariablen lesen“, stattdessen werden Credential-Dateien, Pipes, AF_UNIX-Sockets, Secret-Management-Services und andere Interprozesskommunikation vorgeschlagen; ich frage mich, was davon am bequemsten und portabelsten ist
    Außerdem frage ich mich, ob Secret-Management-Services nur im beruflichen Umfeld genutzt werden oder auch in privaten Projekten

    • Ich bin einer der Autoren der CLI Guidelines
      Ein Artikel, der etwas tiefer auf den Umgang mit Geheimnissen auf der Kommandozeile eingeht, findet sich unter https://smallstep.com/blog/command-line-secrets/
      Credential-Dateien sind eine einfache und portable Option
      Dateien haben bereits ein Berechtigungsmodell und hängen nicht von externen Services oder proprietären APIs ab
      Wenn ein Programm eine Credential-Datei akzeptiert, ist es auch mit systemd credentials kompatibel
      systemd credentials sind sicherer als unverschlüsselte Credential-Dateien, werden verschlüsselt und können auch an ein TPM gebunden werden; die Software, die die Credentials verwendet, muss dafür aber selbst kein TPM unterstützen
    • Es wäre gut, wenn ein Programm einen Befehl zum Abrufen von Geheimnissen angeben ließe
      mbsync (https://isync.sourceforge.io/mbsync.html) bietet zum Beispiel ungefähr drei Möglichkeiten, ein IMAP-Authentifizierungspasswort bereitzustellen
      Wenn kein Passwort gesetzt ist, erscheint zur Laufzeit ein Prompt; man kann auch ein Klartextpasswort in die Konfigurationsdatei schreiben, aber das ist zum Teilen unpraktisch
      Außerdem lässt sich ein Befehl zum Abrufen des Passworts konfigurieren, sodass man die Verarbeitung an einen Passwortmanager wie pass(1) (https://www.passwordstore.org/) oder an einen interaktiven grafischen Prompt delegieren kann
    • Ein Secret-Management-Service dürfte am bequemsten sein
      Er ist gut dokumentiert und lässt sich einfach einrichten, ohne selbst noch etwas Zusätzliches bauen zu müssen
      Secret-Manager wie Doppler (https://Doppler.com) oder AWS Secrets Manager (https://aws.amazon.com/secrets-manager/) haben den Vorteil, Geheimnisse an einem sicheren Ort zu schützen und ihre Offenlegung zu minimieren
      Sogar gegenüber internen Entwicklern lässt sich die Offenlegung reduzieren, was dabei hilft, Datenlecks zu verhindern, die leicht vermeidbar gewesen wären
      Solche Lecks können ein ganzes Unternehmen gefährden und werden immer häufiger
  • Verwandte Beiträge: Command Line Interface Guidelines - https://news.ycombinator.com/item?id=38053692 - Oktober 2023, 1 Kommentar
    Command Line Interface Guidelines - https://news.ycombinator.com/item?id=31651161 - Juni 2022, 1 Kommentar
    Command Line Interface Guidelines - https://news.ycombinator.com/item?id=25492119 - Dezember 2020, 5 Kommentare

  • Es ist wirklich nervig, dass manche Terminals automatisch einen Befehl ausführen, wenn am Ende einer aus der Zwischenablage eingefügten Zeichenkette ein Zeilenumbruch steht
    Persönlich finde ich, eine Kommandozeilenschnittstelle sollte das nicht tun

    • In Bash kann man bind 'set enable-bracketed-paste on' verwenden
    • iTerm auf macOS fragt nach einer Bestätigung, wenn man versucht, eine Zeichenkette einzufügen, die Zeilenumbrüche enthält
      Wenn es nervt, kann man das auch abschalten
    • Die Fish shell fügt standardmäßig wie erwartet nur ein und erlaubt es, den Befehl vor der Ausführung zu bearbeiten
      Bei Bedarf sind auch mehrere Zeilen möglich, und erst durch Drücken von Enter wird ausgeführt
      Bei der Fish shell waren die Standardeinstellungen oft sinnvoll, und abgesehen vom Prompt habe ich bisher kaum etwas gefunden, das ich anpassen möchte
    • Ein Clipboard-Manager ist einen Versuch wert
      Die meisten, die ich ausprobiert habe, hatten eine Option, Zeilenumbrüche aus dem Inhalt der Zwischenablage zu entfernen
    • Wenn ich weiß, dass höchstens ein Zeilenumbruch enthalten ist, tippe ich zuerst # ein und füge dann ein
      Selbst wenn der Zeilenumbruch interpretiert wird, ist die ganze Zeile dadurch ein Kommentar und somit sicher; vor der tatsächlichen Ausführung muss man nur das # am Zeilenanfang entfernen