Zum Hauptinhalt springen

Anatomie der Befehlszeile

·2092 Wörter

Meine Intention mit diesem Artikel ist es, die verschiedenen Stile von Befehlszeilenargumenten nebeneinander zu stellen. Dadurch soll das Verständnis für die verschiedenen verbreiteten Stile gefördert werden. Im Ergebnis soll das Publikum sicherer mit Befehlszeilenargumenten umgehen können und bewusste Entscheidungen bei der Auswahl oder dem Design von Parsern für Befehlszeilenargumente in eigenen Anwendungen treffen können.

Dieser Artikel ist Teil der Serie Befehlszeile.

Einleitung #

Meine Neugier für dieses Thema wurde geweckt, als ich versuchte mir die Befehlszeilensyntax für den Linux-Befehl tar einzuprägen. Im Internet und in gedruckten Nachschlagewerken findet man häufig tar xzf <Archivdatei>, um ein Archiv zu entpacken. Aber was bedeutet dieses xzf. Ist das eine Abkürzung? Wie kann ich mir eine Eselsbrücke bilden, um mir dieses kryptisch erscheinende Argument zu merken?

Tatsächlich habe ich mir diese kryptische Zeichenfolge gar nicht eingeprägt. Statt dessen habe ich verstanden, wie sie entstanden ist. Deshalb weiß ich auch aus dem Kopf, dass man ebenfalls tar zxf <Archivdatei> schreiben kann, aber nicht tar xfz <Archivdatei>. Außerdem schreibe ich den Befehl nun ganz anders, als die meisten Nachschlagewerke es empfehlen. Warum, das möchte ich hier mit etwas technischem Hintergrund erklären.

TLDR #

tar x -z -f <Datei>
tar x -z -f <Datei>
│   │  │  │ └ Optionswert
│   │  │  └ Optionsschlüsselwort "file"
│   │  └ Schalter "gzip"
│   └ Unterbefehl "extract"
└ Befehl

Technischer Hintergrund #

“What is physics?”1… wird ein Prozess vom Betriebssystem gestartet, richtet das Betriebssystem im Arbeitsspeicherbereich des Prozesses einen Bereich für die Befehlszeile ein. Dieser dient dazu, dem Programm mitzuteilen, wie es aufgerufen wurde. Dazu gehört zum einen der Pfad der ausführbaren Datei aus der das Programm geladen wurde, und zum anderen die Parameter, die auf der Befehlszeile für das Programm eingegeben wurden.

> find . -type file

Das Linux-Programm find erhält beim Start die folgenden Zeichenketten übergeben:

  • /usr/bin/find
  • .
  • -type
  • file
> diskpart /S "Layout Partition 1.txt"

Das Windows-Programm diskpart erhält beim Start die folgenden Zeichenketten übergeben:

  • C:\Windows\System32\diskpart.exe
  • /s
  • Layout Partition 1.txt

Wenn das Betriebssystem durch einen Systemaufruf beauftragt wird, einen Prozess zu starten, wird ihm entweder eine Liste mit Argumenten übergeben die es unverändert an den Prozess weiterreichen kann oder es wird nur eine Befehlszeile übergeben und das Betriebssystem muss die Befehlszeile in Argumente auftrennen. Die Kommandozeile (Shell) übernimmt i. d. R. das Auftrennen der Argumente, so dass die Regeln für das Auftrennen auf unterschiedlichen Kommandozeilen im Detail von einander abweichen können.

Für einige Befehle auf der Kommandozeile gilt dieses Verhalten nicht. Befehle, die keine Programme sind, also nicht aus einer ausführbaren Datei geladen werden, sondern direkt im Befehlszeileninterpreter implementiert sind, unterliegen nicht den Restriktionen der standardisierten Schnittstelle zwischen Programm und Betriebssystem. So ignoriert der ECHO-Befehl der klassischen Windows-Befehlszeile CMD Anführungszeichen und Leerzeichen und gibt einfach alles aus, was als Argument übergeben wird — verschluckt aber einen führenden Punkt der ohne Leerzeichen auf den ECHO-Befehl folgen darf!?

Es liegt nun in der Verantwortung des Programms die im Arbeitsspeicher übergebenen Zeichenketten zu interpretieren und in geeigneter Weise auf sie zu reagieren. Für einige Programme genügt eine einzige Zeichenkette (z. B. der Pfad zu einer Datei), um das Programm vollständig zu parametrisieren. Das Programm prüft dann einfach, ob es neben seinem eigenen Pfad genau eine weitere Zeichenkette (Argument) übergeben bekommt und nutzt diese als Parameter für seine Arbeit. Wird das Programm mit keinem oder mit mehreren Befehlszeilenargumenten aufgerufen, beendet es sich mit einer Fehlermeldung. Die meisten Programme benötigen jedoch mehr als ein Argument, um parametrisiert zu werden. Oder sie bieten optionale Parameter, die dann in beliebiger Reihenfolge als Argumente übergeben werden können. Um mehrere Argumente richtig zu erkennen, muss das Programm einige Regeln und Schlüsselzeichenketten vorgeben, die bei der Formulierung der Argumente berücksichtigt werden müssen.

Das Konzept von Befehlszeilenargumenten gibt es seit den 1960er Jahren. Im Laufe der Jahrzehnte haben sich unterschiedliche syntaktische Muster für Befehlszeilenargumente entwickelt, die ich im folgenden Abschnitt darstelle. Dabei wurden in den verschiedenen Betriebssystemen (Multics, UNIX, BSD, GNU/Linux, …) unterschiedliche Konventionen gepflegt. Programme, die mit unterschiedlichen Konventionen entwickelt wurden, werden heute plattformübergreifend genutzt. Daraus entsteht heute ein auf den ersten Blick inkonsistenter Mix.

Syntax #

Ich will hier zunächst einmal unterschiedliche Argumenttypen unterscheiden. Dann beleuchte ich kurz die Erforderlichkeit und Reihenfolge von Argumenten. Das komplizierte Thema von Quoting und Escaping reiße ich hier nur an.

Argumenttypen #

Es ist hilfreich, die folgenden Argumenttypen zu unterscheiden:

  • Schalter (Schlüsselwort)
  • Verstärker (wiederholtes Schlüsselwort)
  • Optionen (Schlüssel und Wert)
  • Mehrwertoptionen (Schlüssel und mehrere Werte)
  • Positionsargumente (Wert)
  • Unterbefehl (Verzweigung)
  • Domänenspezifische Sprache (DSL)

Ein Schalter ist ein Schlüsselwort, das häufig aber nicht immer durch vorangestellte Steuerzeichen wie -, -- oder /gekennzeichnet ist. Ein Schalter löst ohne weitere Parameter eine bestimmte Wirkung aus oder legt einen Wahrheitswert fest. Einige Programme erlauben es, mehrere Schalter mit oder ohne führende Steuerzeichen in ein Argument zu kombinieren.

Beispiele:

  • -z (Kurzform mit Steuerzeichen -)
  • /? (Standardhilfeschalter in DOS-Form mit Steuerzeichen /)
  • --help (lange Form mit Steuerzeichen --)
  • --no-logs (negierender Schalter in langer Form mit Steuerzeichen --)
  • cz (zwei kombinierte Schalter ohne Steuerzeichen)

Ein Verstärker ist syntaktisch ein Schalter, der aber durch Wiederholung seine Wirkung verstärkt. Üblich ist dies zum Beispiel für den Detailgrad der Ausgaben eines Programms (Verbosity).

Beispiele:

  • --verbose --verbose (wiederholter Verstärker in langer Form)
  • -vvv (kombinierter Verstärker in kurzer Form)

Eine Option legt einen Wert fest und ist häufig, aber nicht immer, optional. Sie kombiniert ein Schlüsselwort mit einem Wert. Das Schlüsselwort ist üblicherweise wie ein Schalter durch vorangestellte Steuerzeichen wie -, -- oder / gekennzeichnet. Manchmal werden Schlüsselwort und Wert in einem einzigen Argument kombiniert und dabei meist durch ein Trennzeichen wie = oder : verbunden. Häufiger sind sie aber zwei aufeinanderfolgende Argumente.

Beispiele:

  • -x 123 (Kurzform in zwei Argumenten)
  • -uhttps://endpoint.my-server.de (Kurzform ohne Trennzeichen)
  • --output ~/reports/monthly.csv (lange Form in zwei Argumenten)
  • --url=https://my-server.com (lange Form mit = als Trennzeichen)
  • /p:Verbosity=minimal (kurze Form mit Steuerzeichen / und Trennzeichen :, Schlüsselwort ist p, Wert ist Verbosity=minimal)

Eine Mehrwertoption legt einen Listenwert fest. Sie kombiniert also ein Schlüsselwort mit mehreren Werten. Mehrwertoptionen werden nicht so häufig implementiert, weil sie als Einschränkung haben, dass nach einer Mehrwertoption keine Positionsargumente folgen können. Diese Einschränkung wird manchmal dadurch umgangen, dass ein Argument mit zwei Bindestrichen -- die Liste der Werte beendet — was wiederum die Einschränkung mit sich bringt, dass ein Listenwert nicht die Zeichenkette mit zwei Bindestrichen sein kann.

Beispiel:

  • --files ~/Project\ Alpha/abc.txt /mnt/media/usb1/xyz.txt

Ein Positionsargument ist nicht durch vorangestellte Steuerzeichen oder ein Schlüsselwort erkennbar. Ein Argument ist genau dann ein Positionsargument, wenn es nicht auf das Schlüsselwort einer Option folgt und kein Schalter ist.

Beispiel:

notepad.exe "C:\Users\Me\AppData\Local\Application X\settings.json"

Ein Positionsargument kann nicht ohne weiteres von einem Schalter unterschieden werden, wenn Schalter ohne vorangestelltes Steuerzeichen zulässig sind. Syntaktisch können ein Schalter, gefolgt von einem Positionsargument, identisch mit einer Option sein. Daher muss man sich mit den Schaltern und Optionen eines Programms vertraut machen, um sie sicher unterscheiden zu können. Im folgenden Beispiel könnte -x eine Option mit dem darauf folgenden Dateipfad als Wert sein; -x könnte aber auch ein Schalter, gefolgt von einem Dateipfad als Positionsargument, sein.

Beispiel:

-x /var/logs/yesterday.log

Ein Unterbefehl sieht syntaktisch wie ein Positionsargument oder ein Schalter ohne vorangestelltes Steuerzeichen aus. Er verzweigt das Verhalten des Programms und muss häufig als eines der ersten Argumente übergeben werden.

Beispiele:

  • tar c
  • docker run

Einige umfangreiche Befehlszeilenprogramme unterstützen eine Domänen Spezifische Sprache (DSL) auf der Befehlszeile. Dabei gibt das Programm eine eigene Syntax für die Argumente vor, die von der üblichen Syntax von Schaltern, Optionen und Positionsargumenten abweicht oder bei der Reihenfolge und Wiederholung von Schaltern und Optionen eine besondere Bedeutung erhalten.

Beispiel:

ffmpeg -i punch.mp4 -filter_complex "[0:v]avgblur=sizeX=16:planes=15:sizeY=16[out_v]" -map "[out_v]" -map 0:a out.mp4

Viele Programme unterstützen Schalter, die Schlüsselwörter von Optionen und manchmal auch Unterbefehle in einer kurzen (-x) und einer langen (--extract) Schreibweise. Dabei eignet sich die kurze für die direkte Eingabe auf der Kommandozeile. Die lange Schreibweise sollte in Shell-Skripten bevorzugt werden, weil dies i. d. R. die Lesbarkeit verbessert.

Erforderlichkeit #

Um eine korrekte Befehlszeile für ein Programm zu formulieren, muss man sich mit der Erforderlichkeit von Argumenten vertraut machen.

  • erforderlich
  • optional
  • ignoriert
  • verboten

Schalter und Optionen sind üblicherweise optional. Manchmal bedingen sie sich aber. So kann ein Schalter ein Verhalten aktivieren, welches eine Option erforderlich macht. Durch Unterbefehle ändern sich häufig die nachfolgend erlaubten oder erforderlichen Schalter und Optionen. So kann eine Option bei einem Unterbefehl optional und bei einem anderen verboten sein.

Einige Programme ignorieren Schalter und Optionen die sie nicht erkennen stillschweigend; andere brechen mit einer Fehlermeldung ab.

Reihenfolge #

Häufig ist die Reihenfolge von Schaltern, Optionen und Positionsargumenten unwichtig. Aber es gibt Ausnahmen. Einige Programme erwarten z. B. alle Positionsargumente am Ende der Befehlszeile. Unterbefehle müssen i. d. R. am Anfang der Befehlszeile stehen. Schalter oder Optionen, die das Verhalten eines Programms unabhängig von einem durch einen Unterbefehl gewählten Unterprogramm beeinflussen, müssen aber oft vor dem Unterbefehl stehen. Und manchmal hat die Reihenfolge von wiederholten Optionen Auswirkungen auf das Programmverhalten.

Maskierung #

Wenn in den Werten von Optionen oder in einem Positionsargument Zeichen vorkommen, die auf der Kommandozeile oder für das Erkennen der Befehlszeilenargumente relevant sind, müssen diese maskiert werden.

Grundsätzlich gibt es zwei Typen von Maskierungen:

  • umfassende Maskierung (Quoting)
  • zeichenweise Maskierung (Escaping)

Eine umfassende Maskierung wird häufig durch doppelte " oder einfache ' Anführungszeichen erreicht. Selten kommen auch Backticks ` zum Einsatz. Unsymmetrischen Anführungszeichen „“ werden nicht zur Maskierung auf der Befehlszeile genutzt. Wichtig ist, dass die Zeichen, die von der Kommandozeile oder dem Betriebssystem zur Maskierung unterstützt werden, selbst zeichenweise maskiert werden müssen, wenn sie Teil eines Optionswertes oder eines Positionsarguments sind.

Beispiele:

  • --file "E:\Projekt Alpha\2025-01-21.xlsx" (Wert ist umfassend maskiert)
  • --file="E:\Projekt Alpha\2025-01-22.xlsx" (= als Trennzeichen, Wert ist umfassend maskiert)
  • "--file=E:\Projekt Alpha\2020-01-23.xlsx" (= als Trennzeichen, vollständiges Argument umfassend maskiert)
  • --numbers "1|2|3|4" (Pipe-Zeichen umfassend maskiert)

Die unterschiedlichen Zeichen zur umfassenden Maskierung haben manchmal unterschiedliche Wirkung auf einer Kommandozeile. So werden in Zeichenfolgen, die mit doppelten Anführungszeichen umfasst sind, Umgebungsvariablen vor der Übergabe an das Programm ersetzt. Wohingegen Umgebungsvariablen in Zeichenfolgen, die mit einfachen Anführungszeichen umfasst sind, unverändert an das Programm übergeben werden.

Beispiele:

  • CMD
    • "%USERPROFILE%"C:\Users\Me
    • '%USERPROFILE%'%USERPROFILE%
  • Bash
    • "${http_proxy}/"http:192.168.40.2:3128/
    • '${http_proxy}/'${http_proxy}/

Eine zeichenweise Maskierung wird durch das Voranstellen eines speziellen Escape-Zeichens vor das zu maskierende Zeichen erreicht. Übliche Escape-Zeichen sind der Backslash \, ein Backtick ` oder auch ein Zirkumflex ^. Soll das Symbol eines zur Maskierung genutzten Zeichens unverändert in einem Optionswert oder einem Positionsargument auftauchen, so wird das Escape-Zeichen i. d. R. gedoppelt.

Beispiele:

  • PowerShell
    • "abc`$d" (Dollar-Zeichen maskiert)
  • CMD
    • "ABC ^| abc" (Pipe-Zeichen maskiert)
  • Bash
    • --file ~/Project\ Alpha\document.html (Leerzeichen maskiert)
    • echo "ABC \> abc" (Umleitungszeichen maskiert)

Die Erfordernis zur Maskierung von bestimmten Zeichen für Kommandozeile führt leider regelmäßig zu Zeichensalat und schlecht lesbaren Befehlszeilen.

Konventionen #

Die folgenden Konventionen für die Syntax von Befehlszeilenargumenten sind mir bisher begegnet:

  • BSD
    • Schalter und Optionen sind nicht durch ein Steuerzeichen gekennzeichnet
    • Mehrere Schalter können in ein Argument zusammengefasst werden
  • UNIX
    • Schalter und Optionen werden mit einem einfachen führenden Bindestrich - gekennzeichnet
    • Mehrere Schalter können in ein Argument zusammengefasst werden
  • POSIX
    • Schalter und Optionen werden mit einem einfachen führenden Bindestrich - gekennzeichnet
    • Schlüsselwort und Wert bei Optionen wird durch ein Leerzeichen getrennt (zwei Argumente)
    • Schalter und Optionen werden in Kebab-Case geschrieben: Mehrere Wörter werden mit Bindestrichen getrennt. Alles wird klein geschrieben.
  • GNU Linux
    • Schalter und Optionen erhalten eine Langform, die durch zwei führende Bindestriche -- gekennzeichnet ist
  • DOS/Windows
    • Schalter und Optionen sind durch einen führenden Schrägstrich / gekennzeichnet
    • Der Doppelpunkt : wird häufig als Trennzeichen zwischen Wert in Schlüsselwort von Optionen genutzt
    • Schalter und Optionen werden oft vollständig groß geschrieben (kapital)
    • Großkleinschreibung von Schaltern und Optionen wird häufig ignoriert
  • PowerShell
    • Schalter und Optionen sind durch einen einfachen führenden Bindestrich - gekennzeichnet
    • Schalter und Optionen werden in Pascal-Case geschrieben: Wörter werden ohne Trennzeichen zusammengeschrieben. Der Anfangsbuchstabe jedes Wortes ist groß geschrieben.
    • Es gibt keine Kurzformen für Schalter und Optionen
    • Schlüsselwort und Wert bei Optionen wird durch Leerzeichen getrennt (zwei Argumente)

Abschluss #

Jetzt komme ich wieder auf den eingangs erwähnten tar-Befehl zurück. Der tar Befehl strebt danach, BSD-, POSIX- und GNU-kompatibel zu sein. Daher unterstützt er mehrere syntaktische Varianten für Schalter und Optionen.

  1. tar xzf <Datei> im reinen BSD-Stil mit kombinierten Schaltern und Optionen
  2. tar xz f <Datei> Durch Abtrennung von f wird erkennbar, dass es eine Option mit dem Namen der Archivdatei ist
  3. tar -xz -f <Datei> im UNIX-Stil mit kombinierten Schaltern
  4. tar -x -z -f <Datei> im POSIX-Stil ohne kombinierte Schalter
  5. tar --extract --gzip --file=<Datei> In der ausführlichen GNU-Schreibweise; das Gleichheitszeichen bindet Schlüsselwort und Wert der file-Option zusammen
  6. tar x --gzip --file=<Datei> Die Kurzschreibweise von --extract als x, wieder im BSD-Stil, trennt das Argument optisch von den übrigen Optionen und hebt es als Unterbefehl hervor
  7. tar x -z -f <Datei> Ist für mich der beste Kompromiss aus Lesbarkeit und Schreibaufwand

  1. Fernsehserie “Big Bang Theory” S3E10 https://www.youtube.com/watch?v=AEIn3T6nDAo ↩︎