Was sind gute Angewohnheiten für das Entwerfen von Befehlszeilenargumenten?

190

Während der Entwicklung der Anwendung fragte ich mich: Wie soll ich Befehlszeilenargumente entwerfen?

Viele Programme verwenden Formeln wie diese -argument valueoder /argument value. Die Lösung, die mir in den Sinn kam, war argument:value. Ich fand es gut, weil es ohne Leerzeichen keine Möglichkeit gibt, Werte und Argumente durcheinander zu bringen. Es ist auch einfach, eine Zeichenfolge beim ersten :Zeichen von links in zwei Teile zu teilen .

Meine Fragen sind:

  1. Ist die beliebte -argument valueFormel besser als argument:value(lesbarer, einfacher zu schreiben, fehlerfrei, für erfahrene Entwickler einfacher zu verstehen)?
  2. Gibt es einige allgemein bekannte Regeln, denen ich beim Entwerfen von Befehlszeilenargumenten folgen sollte (außer wenn es funktioniert, ist es in Ordnung)?

Nach einigen Details gefragt, werde ich es zur Verfügung stellen. Ich denke jedoch, dass sie die Antworten nicht beeinflussen sollten. Die Frage ist über gute Gewohnheiten im Allgemeinen. Ich denke, sie sind für alle Arten von Anwendungen gleich.

Wir arbeiten an einer Anwendung, die an öffentlichen Orten (Touch Totems, Tabellen) verwendet werden soll. Anwendungen werden mit Qt Quick 5 (C ++, QML, JS) geschrieben. Auf Geräten ist Windows 8.1 / 10 installiert. Wir werden eine Front-End-Schnittstelle zur Verwaltung der Geräte bereitstellen. Einige erfahrene Administratoren möchten die Anwendung jedoch möglicherweise selbst konfigurieren. Es ist von der Seite des Geschäfts nicht sehr wichtig, aber da ich dem zustimme, was Kilian Foth sagte, möchte ich nicht, dass meine Anwendung für einen Benutzer schmerzhaft ist. Da ich im Internet nicht finde, was ich will, habe ich hier gefragt.


Für fortgeschrittene Stack Exchange-Benutzer: Ich wollte, dass diese Frage allgemein ist. Vielleicht qualifiziert es sich für das Community-Wiki (ich weiß nicht, ob vorhandene Fragen mit Antworten konvertiert werden können). Da ich möchte, dass diese Frage unabhängig von Betriebssystem und Programmiersprache ist, können die hier aufgeführten Antworten eine wertvolle Lektion für andere Entwickler sein.

Filip Hazubski
quelle
14
Schauen Sie sich die gängigen Befehlszeilentools an. Beispielsweise wird häufig der einzelne Bindestrich verwendet, um Kombinationsoptionen zu ermöglichen. Sie könnten zB schreiben ls -ltr, um die Optionen zu kombinieren -l, -tund -r. GNU-Programme erlauben normalerweise auch wortbasierte Optionen mit einem doppelten Bindestrich --reverseanstelle von -r. Es gibt andere gängige Konventionen -h, um Hilfe ---
Brandin,
72
Weder sind -argument valuenicht -argument:valueüblich. Üblich sind -a value, -avalueund --argument=value.
Reinierpost
39
Verwenden Sie eine beliebte Kommandozeilen-Parsing-Bibliothek (normalerweise so genannt getopt(s)), wo Sie können.
Reinierpost
5
@ k3b Wir arbeiten mit Qt. Wie Kevin Cline in seinem Kommentar sagte, können wir die bereits vorhandene Bibliothek verwenden. Ich nehme an, es ist plattformübergreifend und durchdacht. QCommandLineParser
Filip Hazubski
11
Das Problem mit Ihrem letzten Klappentext ist, dass das Parsen von Argumenten kein plattformunabhängiges Problem ist.
Pydsigner

Antworten:

237

Auf POSIX-Systemen (z. B. Linux, MacOSX) würde ich, zumindest für Programme, die möglicherweise in einem Shell-Terminal gestartet wurden (z. B. den meisten von ihnen), die Verwendung der GNU-Kodierungskonventionen empfehlen (in denen auch gebräuchliche Argumentnamen aufgeführt sind) und in den Richtlinien für POSIX-Dienstprogramme nachsehen , auch für proprietäre Software:

  • immer behandeln --versionund--help ( /bin/trueakzeptiert sie sogar !!). Ich verfluche die Autoren von Software nicht verstehend --help, ich hasse sie (weil prog --help es der erste Befehl ist, den ich auf einem neuen Programm versuche)! Oft --helpkann als abgekürzt werden-h

  • Lassen Sie die --helpNachricht alle Optionen auflisten (es sei denn, Sie haben zu viele davon ... in diesem Fall listen Sie die am häufigsten verwendeten auf und verweisen Sie explizit auf eine manSeite oder eine URL) und Standardwerte von Optionen, und möglicherweise wichtig (und programmspezifisch) ) Umgebungsvariablen. Diese Optionslisten bei Optionsargumentfehler anzeigen.

  • akzeptieren -akurze Argument (Einzelbuchstaben) und einige Äquivalent haben --long-argument, so -a2 --long-argument=2, --long-argument 2; Natürlich könnten Sie (für selten verwendete Optionen) einen --only-long-argumentNamen haben; für modale Argumente ohne zusätzliche Optionen -cfwird in der Regel wie folgt vorgegangen: -c -fusw. Ihr -argument:valueVorschlag ist also komisch, und ich empfehle, dies nicht zu tun.

  • benutze GLIBC getopt_long oder besser (zB argp_parse , in OCaml ist es ein ArgModul , ...)

  • verwenden oft -für Standard - Ein- oder Ausgang (wenn Sie nicht tun können, handhaben /dev/stdinund /dev/stdoutauch auf die wenigen Betriebssysteme mit ihnen nicht)

  • das Verhalten ähnlicher Programme nachahmen, indem die meisten ihrer Optionskonventionen wiederverwendet werden; insbesondere -nfür Trockenlauf (à la make), -hfür Hilfe, -vfür Ausführlichkeit, etc ...

  • Verwenden Sie --als Trennzeichen zwischen Optionen und Dateien oder anderen Argumenten

  • Wenn Ihr Programm verwendet isatty, um zu testen, dass stdin ein Terminal ist (und sich in diesem Fall "interaktiv" verhält), stellen Sie eine Option bereit, um den nicht interaktiven Modus zu erzwingen, ebenso, wenn Ihr Programm eine GUI-Oberfläche hat (und getenv("DISPLAY")auf dem X11-Desktop testet ), dies aber auch könnte in Batch oder Kommandozeile verwendet werden.

  • Einige Programme (z. B. gcc) akzeptieren indirekte Argumentlisten, @somefile.txtdh, sie lesen Programmargumente aus somefile.txt. Dies kann nützlich sein, wenn Ihr Programm sehr viele Argumente akzeptiert (mehr als die Ihres Kernels ARG_MAX).

Übrigens können Sie sogar einige automatische Vervollständigungsfunktionen für Ihr Programm und übliche Shells (wie bashoder zsh) hinzufügen.

Einige alte Unix-Befehle (z. B. ddoder sogar sed) haben seltsame Befehlsargumente für die historische Kompatibilität. Ich würde empfehlen, ihren schlechten Gewohnheiten nicht zu folgen (es sei denn, Sie machen eine bessere Variante davon).

Wenn Ihre Software eine Reihe von zugehörigen Befehlszeilenprogramme, nehmen Inspiration von git (was Sie sicherlich als Entwicklungswerkzeug verwenden), die akzeptiert git helpund git --helpund haben viele gitsubcommandundgitsubcommand--help

In seltenen Fällen können Sie auch verwenden argv[0](indem Sie Symlinks in Ihrem Programm verwenden), z. B. bashaufgerufen, weil rbashein anderes Verhalten vorliegt ( eingeschränkte Shell). Aber normalerweise empfehle ich das nicht. Es kann sinnvoll sein, Ihr Programm als Skriptinterpreter mit shebang zu verwenden, dh #!in der ersten Zeile, die von execve (2) interpretiert wird . Wenn Sie solche Tricks ausführen, müssen Sie sie dokumentieren, auch in --helpNachrichten.

Denken Sie daran , dass auf POSIX die Schale ist Globbing Argumente ( vor Ihrem Programm läuft!), So vermeiden Zeichen erfordern (wie *oder $oder ~) in Optionen , die Shell-escaped sein müsste.

In einigen Fällen können Sie einen Interpreter wie GNU Guile oder Lua in Ihre Software einbinden (vermeiden Sie es, Ihre eigene Turing-complete- Skriptsprache zu erfinden, wenn Sie keine Experten für Programmiersprachen sind). Dies hat tiefgreifende Konsequenzen für das Design Ihrer Software (sollte also frühzeitig bedacht werden!). Sie sollten dann problemlos in der Lage sein, ein Skript oder einen Ausdruck an diesen Interpreter zu übergeben. Wenn Sie diesen interessanten Ansatz wählen, sollten Sie Ihre Software und ihre interpretierten Grundelemente sorgfältig entwerfen. Sie könnten einige seltsame Benutzer haben, die große Skripte für Ihr Ding programmieren.

In anderen Fällen möchten Sie Ihre fortgeschrittenen Benutzer möglicherweise ihr Plugin in Ihre Software laden lassen (unter Verwendung dynamischer Ladetechniken à la dlopen& dlsym). Auch dies ist eine sehr wichtige Entwurfsentscheidung (also definieren und dokumentieren Sie die Plugin-Oberfläche sorgfältig), und Sie müssen eine Konvention definieren, um Programmoptionen an diese Plugins zu übergeben.

Wenn Ihre Software komplex ist, lassen Sie sie einige Konfigurationsdateien akzeptieren (zusätzlich oder anstelle von Programmargumenten) und haben möglicherweise die Möglichkeit, diese Konfigurationsdateien zu testen (oder nur zu analysieren), ohne den gesamten Code auszuführen. Beispielsweise ist ein Mail-Transfer-Agent (wie Exim oder Postfix) ziemlich komplex und es ist nützlich, ihn "halbtrocken" auszuführen (z. B. zu beobachten, wie er mit einer bestimmten E-Mail-Adresse umgeht, ohne tatsächlich eine E-Mail zu senden).


Beachten Sie, dass dies /optioneine Windows- oder VMS-Sache ist. Auf POSIX-Systemen wäre dies verrückt (da die Dateihierarchie /einen Verzeichnisseparator verwendet und die Shell das Globbing ausführt). Meine Antwort ist hauptsächlich für Linux (und POSIX).


PS Wenn möglich, machen Sie Ihr Programm zu einer kostenlosen Software , Sie würden Verbesserungen von einigen Benutzern und Entwicklern erhalten (und das Hinzufügen einer neuen Programmoption ist oft eines der einfachsten Dinge, die Sie zu einer vorhandenen kostenlosen Software hinzufügen können). Auch hängt Ihre Frage viel von dem beabsichtigten Publikum : ein Spiel für Jugendliche oder ein Browser für Oma wahrscheinlich nicht die gleiche Art und Menge von Optionen als ein Compiler benötigt, oder einen Netzwerk - Inspektoren für Rechenzentrum sysadmins oder eine CAD - Software für den Mikroprozessor Architekten oder für Brückenbauer. Ein Ingenieur, der mit Programmieren und Skripten vertraut ist, mag wahrscheinlich viel mehr einstellbare Optionen als Ihre Oma und möchte möglicherweise Ihre Anwendung ohne X11 ausführen können (möglicherweise in einem crontabJob).

Basile Starynkevitch
quelle
18
Einverstanden, ist aber git ein besseres Beispiel. Ich würde es nicht empfehlen , sich cvs2016 einmal umzusehen .
Basile Starynkevitch
8
+ Im Falle einer ungültigen Option einfach die Hilfe anzeigen. Es ist soooo ärgerlich, eine unnütze Fehlermeldung zu bekommen, zum Beispiel für dd -h.
Domen
8
GNU-Programme unterstützen --helpin der Regel, erkennen aber oft nicht, -hwas ärgerlich ist. Normalerweise gebe ich -h ein, wenn ich eine Option für ein Programm vergesse. Es ist ärgerlich, den Befehl mit der längeren Option erneut eingeben zu müssen --help. Immerhin habe ich -h eingegeben, weil ich etwas vergessen habe. Warum sollte der Benutzer wissen, für welche Programme '--help' und für welche Programme '-h' erforderlich ist, um den Hilfebildschirm anzuzeigen? Fügen Sie einfach eine Option für -h und --help ein.
Brandin
30
Der erste Teil dieser Antwort ist gut, aber irgendwo in Ihrer Umgebung gehen Sie auf Tangenten los. Beispielsweise haben Konfigurationsdateien, Plugins und Dlopen, die Auswahl von Softwarelizenzen und Webbrowsern keinen wirklichen Bezug mehr zu den Konventionen einer Befehlszeilenschnittstelle.
Brandin
5
Und bitte. Wenn Sie Ihre Ausgabe für den Bildschirm formatieren, fügen Sie eine Ausgabeformatüberschreibung hinzu. Nichts stört mich mehr als Befehle, die Zeilen abschneiden oder umbrechen, wenn ich versuche, sie in einem Skript zu verwenden.
Sobrique
68

Die Tatsache, dass eine Datenformatkonvention beliebt ist, ist ihr Vorteil.

Sie können leicht erkennen, dass die Verwendung von = oder: oder sogar '' als Trennzeichen triviale Unterschiede sind, die vom Computer mit geringem Aufwand ineinander umgewandelt werden können. Was für einen Menschen eine große Anstrengung wäre, ist sich zu erinnern: "Nun sehen Sie, hat dieses selten verwendete Programm die Dinge mit :oder mit begrenzt =? Hmmm ..."

Mit anderen Worten, weichen Sie aus Liebe zu Gott nicht ohne zwingenden Grund von extrem tief verwurzelten Konventionen ab. Die Leute werden sich an Ihr Programm als "das mit der seltsamen und nervigen cmdline-Syntax" erinnern, anstatt "das, das meinen College-Aufsatz gerettet hat".

Kilian Foth
quelle
19
Für fast alle Sprachen gibt es Bibliotheksfunktionen, mit denen Befehlszeilenargumente verarbeitet werden können. Verwenden Sie einen von ihnen.
Kevin Cline
9
Guter Rat, aber was sind das für Konventionen? -1
RubberDuck
14
Es gibt ein interessantes Beispiel für ein häufig verwendetes UNIX-Tool, das absichtlich gegen diese Konvention verstößt: ddVerwendet eine key=valueSyntax. Der Grund für diese Entwurfsentscheidung ist, dass dieses Werkzeug (Spitzname: d ata d estroyer) bei falscher Verwendung viel Schaden anrichten kann. Indem sie den Benutzer zwingen, seine gewohnte Gewohnheit aufzugeben, zwingen sie den Benutzer, genauer darüber nachzudenken, was er tut.
Philipp
18
Sind Sie sicher, dass dies der Grund ist dd? Ich glaube, dass es einfach zu einer Zeit (1970er Jahre?) Codiert wurde, als der ARG_MAX des Kernels klein war, Shells keine automatischen Vervollständigungen hatten und --long-argumentsnicht existierten. Seitdem besser ddabwärtskompatibel geblieben
Basile Starynkevitch
9
ddstammte aus einem anderen Betriebssystem (als UNIX) - einer Skriptsprache (JCL) unter IBM OS / 360 -, in der die Konventionen unterschiedlich waren, bevor sie mehr oder weniger unverändert auf UNIX portiert wurden - teilweise, weil diejenigen, die es wahrscheinlich verwenden, es von dem kannten vorheriges System.
Baard Kopperud
29

In Laienbegriffen

Wenn du in Rom bist, mach wie es die Römer tun.

  • Wenn Ihre CLI-App für Linux / Unix vorgesehen ist, verwenden Sie die Konvention -p valueoder --parameter value. Linux verfügt über Tools, um solche Parameter und Flags auf einfache Weise zu analysieren.

Normalerweise mache ich so etwas:

while [[ $# > 0 ]]
do
key="$1"
case $key in
    --dummy)
    #this is a flag do something here
    ;;
    --audit_sessiones)
    #this is a flag do something here
    ;;
    --destination_path)
    # this is a key-value parameter
    # the value is always in $2 , 
    # you must shift to skip over for the next iteration
    path=$2
    shift
    ;;
    *)
    # unknown option
    ;;
esac
shift
done
  • Wenn Ihre CLI-App für Windows vorgesehen ist, verwenden Sie /flagund /flag:valueKonventionen.

  • Einige Apps wie Oracle verwenden jedoch keine. Oracle Dienstprogramme verwenden PARAMETER=VALUE.

  • Eine Sache, die ich gerne mache, ist neben dem Akzeptieren von Parametern in der Befehlszeile die Option, eine Pardatei zu verwenden , bei der es sich um eine Schlüssel-Wert- Paardatei handelt , um lange Parameterketten zu vermeiden. Dafür sollten Sie einen zusätzlichen --parfile mifile.parParameter angeben. Wenn --parfileverwendet, werden natürlich alle anderen Parameter zugunsten des Inhalts der Parfile verworfen.

  • Ein weiterer Vorschlag ist, die Verwendung einiger benutzerdefinierter Umgebungsvariablen zuzulassen. Wenn Sie beispielsweise Umgebungsvariablen MYAPP_WRKSPACE=/tmpfestlegen, ist es nicht erforderlich, diese immer festzulegen --wrkspace /tmp.

  • Vergessen Sie unter Linux nicht, die automatische Vervollständigung von Parametern hinzuzufügen , dh Benutzer können einen halben Schalter eingeben, drücken TABund dann die Shell für sie ausführen.
Tulains Córdova
quelle
1
Nun, verschiedene GNU-Dienstprogramme (zB gcc) behandeln @mifile.parIhre --parfile mifile.parVorschläge.
Basile Starynkevitch
7
Sind Sie sicher, dass Sie alle anderen Optionen ignorieren, wenn eine Parameterdatei vorhanden ist? Das klingt für mich bestenfalls uninteressant.
Jonathan Leffler
8
Ich stimme Jonathan in Bezug auf Parameterdateien zu. Wenn die Parameterdatei vorhanden ist, würde ich erwarten, dass sie für Standardwerte verwendet wird, wobei die in der Befehlszeile angegebenen Argumente über denen in der Pardatei liegen. Wenn die Verwendung einer Pardatei aus irgendeinem Grund die Verwendung zusätzlicher Befehlszeilenargumente ausschließt, sollte das Vorhandensein zusätzlicher Argumente ein Fehler sein.
Eldritch Cheese
@EldritchCheese Wenn in den von mir geschriebenen CLI-Apps eine Pardatei angegeben wird, generiert jeder zusätzliche Parameter einen Fehler.
Tulains Córdova
1
Wenn Sie eine leistungsfähige Shell verwenden, ist diese Art der Option "Konfigurationsdateiparameter" nicht erforderlich. zB schreibst du einfach fooCmd -opt1 -opt2 $(cat more_options.opts). Daher würde ich erwarten, dass eine "config file parameter" -Option, falls vorhanden, grundsätzlich auf die gleiche Weise funktioniert.
Brandin
19

Eine Sache, die noch nicht aufgetaucht ist:

Versuchen Sie, Ihre Software anhand der Befehlszeilenargumente nach oben zu entwerfen . Bedeutung:

Entwerfen Sie vor dem Entwerfen der Funktionalität die Benutzeroberfläche.

Auf diese Weise können Sie frühzeitig einen Drilldown zu Randfällen und allgemeinen Fällen durchführen. Natürlich werden Sie immer noch das Äußere und das Innere abstrahieren, aber es wird viel bessere Ergebnisse bringen, als nur den gesamten Code zu schreiben und dann eine CLI darauf zu knacken.

Schauen Sie sich außerdem docopt ( http://docopt.org/ ) an.

docopt ist dabei in vielen Sprachen eine große Hilfe, insbesondere bei Python, bei dem stark eingeschränkte, vom Benutzer unerwünschte Argument-Parser wie argparse weiterhin als "OK" eingestuft werden. Anstatt Parser und Subparser und Bedingungswörter zu haben, definieren Sie einfach die Syntaxhilfe, und der Rest wird erledigt.

Florian Heigl
quelle
Ich liebe diese Antwort und danke Ihnen dafür, weil ich derzeit nicht frustriert bin argparse, Programmierer, Benutzeroberfläche oder benutzerfreundlich zu sein.
Katze
Ich habe docopt ausprobiert und es gefällt mir nicht. Das Klicken führt zu einem viel saubereren Code, obwohl die Optionen bei Unterbefehlen etwas umständlich sind.
jpmc26
2
Dies ist zu sehr wie eine Werbung. Erwähnen Sie die Ressource nur einmal, wenn sie relevant ist. Aber so wie es jetzt ist, klingt 50% Ihrer Antwort nur so, als würden Sie eine externe Ressource bewerben.
Brandin
Ich würde es als "erst das Nutzungsmodell entwerfen" bezeichnen. Die Trennung des Benutzererlebnisses von der Funktionalität kann in vielen Fällen eine künstliche Unterscheidung sein (Tooleinschränkungen wirken sich auf die Benutzeroberfläche aus).
Kupfer.hat
1
+1 für Docopt. Es löste alle meine CLI-Dilemmata, völlig schmerzfrei. Manchmal ist es schwierig, Werbung von echter Begeisterung zu unterscheiden, aber hier ist es - ich bin seit Jahren ein Docopt-Enthusiast, in keiner Weise verbunden;)
vom
3

Einige wertvolle Kommentare (@Florian, Basile), aber lassen Sie mich hinzufügen ... OP sagt:

Wir werden eine Front-End-Schnittstelle zur Verwaltung der Geräte bereitstellen. Einige fortgeschrittene Administratoren möchten die Anwendung jedoch möglicherweise selbst konfigurieren

Aber auch Bemerkungen:

Ich wollte nicht, dass diese Frage plattform- oder sprachspezifisch ist

Sie müssen Ihre Zielgruppe berücksichtigen - fortgeschrittene Administratoren . Auf welcher Plattform arbeiten sie normalerweise - Win / Unix / Mac? Und auf welcher Plattform läuft deine App? Befolgen Sie die CLI-Konventionen, die bereits für diese Plattform festgelegt wurden. Wünschen / benötigen Ihre "fortgeschrittenen" Administratoren ein GUI-basiertes Tool?

Sie möchten, dass die Oberfläche intern und mit anderen Verwaltungstools konsistent ist. Ich will nicht aufhören und denken, ist es cmd -p <arg>oder cmd -p:<arg>oder cmd /p <arg>. Brauche ich Anführungszeichen, weil es ein Leerzeichen gibt? Kann ich cmd -p <val1> <val2>oder cmd -p <val1> -p <val2>für mehrere Ziele? Sind sie auftragsspezifisch? Überladbar? Funktioniert das cmd -p2 <arg> -p1 <arg>auch? Tut ls -l -r -t dir1 dir2== ls -trl dir1 dir2?

Bei meinen Unix-Admin-Tools habe ich immer die Anleitungen von Heiners Shelldorado sowie die anderen erwähnten Referenzen im Auge behalten.

Ebenso wichtig wie das Entwerfen der CLI ist es, sicherzustellen, dass Ihre Anwendung mit Befehlszeilenargumenten arbeitet, die mit denen der GUI identisch sind. Das heißt: Keine Geschäftslogik in der GUI oder Verwendung des allgemeinen Befehls, der sowohl von der GUI als auch von der CLI aufgerufen wird.

Die meisten UNIX-basierten Verwaltungstools sind eigentlich zuerst als Befehlszeilentools konzipiert, und die bereitgestellte GUI erleichtert lediglich das "Auffüllen" der Optionen für die Befehlszeile. Dieser Ansatz ermöglicht Automatisierung, Verwendung von Antwortdateien usw. und Hand-off-Management (weniger Arbeit für mich!)

Als klassisches Toolset wird hier Tcl / Tk verwendet . Nicht vorschlagen, dass Sie das Werkzeug wechseln; Betrachten Sie einfach den Entwurfsansatz, indem Sie zuerst eine GUI-basierte Verwaltungs-App als Befehlszeilen-Tool in die App schreiben. Legen Sie dann die GUI der Einfachheit halber auf. Irgendwann werden Sie wahrscheinlich feststellen, dass die grafische Benutzeroberfläche mühsam (und fehleranfällig) ist, wenn Sie mehrere Konfigurationen durchführen und im Allgemeinen immer wieder dieselben Optionen eingeben müssen, und Sie werden nach einem automatisierten Ansatz suchen.

Denken Sie daran, dass Ihr Administrator wahrscheinlich ohnehin die richtigen Werte in die richtigen Felder eingeben muss.

Ian W
quelle
Großartig, ja! Eine Frage ist auch: Kann ich ./this-script --hosts <hosts.txt
Florian Heigl