Es macht viel Arbeit, beim Erstellen von Programmen Optionen für die Kommandozeile zu definieren und zu parsen. Docopt senkt dabei den Aufwand.
Computerprogramme dienen immer einem bestimmten Zweck. Die Werkzeuge auf Linux-Systemen fallen dabei oft sehr spezialisiert aus und folgen der Idee, nur eine einzige Aufgabe zu erledigen, die aber so gut wie möglich [1]. Diese einzelnen Tools kombinieren Sie bei Bedarf miteinander zu mächtigen Ketten [2]. Mithilfe von Optionen, die Sie den Programmen beim Aufruf mitgeben, steuern Sie den Verlauf und fangen Sonderfälle ab (siehe Kasten “Nachgesteuert”).
Nachgesteuert
Das Konzept der Optionen ermöglicht es, Programmfunktionen über den Aufruf anzusteuern, etwa um zusätzliche Informationen auszugeben, Daten von einer bestimmten Quelle zu lesen oder in einem gewünschten Format zu interpretieren und zu verarbeiten. Programme erwarten diese Optionen je nach Plattform in unterschiedlicher Notation: Auf Unix-Systemen beginnen sie in der Regel mit einem Minus, gefolgt von einem Einzelbuchstaben, wie -h. Auf Systemen, die auf die GNU-Tools setzen, kommen Lang-Optionen hinzu. Sie beginnen mit einem doppelten Minus, gefolgt von einem Wort, wie --help. Unter Microsoft Windows leitet man Optionen dagegen mit einem Schrägstrich ein, gefolgt von einem Buchstaben oder einem Wort, wie /help.
Diese drei Ansätze existieren aus historischen Gründen. Viele Programme auf unixoiden Systemen unterstützen entweder den Unix-Weg [7], den GNU-Weg [8] oder beides. Die Unix-Notation ist kürzer, die GNU-Version lässt sich oft leichter lesen. Versuche, beide Schreibweisen in einem einheitlichen Schema zu vereinigen, waren bislang nicht von Erfolg gekrönt. Zudem gilt es, stets im Kopf zu behalten, dass sowohl die Schreibweise als auch die Bedeutung der Option spezifisch für das jeweilige Programm sind. Werfen Sie daher stets einen Blick in die Manpage, um sicherzugehen, wofür eine Option steht und was sie tatsächlich bewirkt. Allerdings folgen Entwickler häufig Konventionen: So stehen -h, --help und /h in der Regel für die Hilfe oder Zusatzinformationen zum Werkzeug.
Als Entwickler entscheiden Sie, welche Optionen im Programm gelten. Das erfordert nicht nur eine korrekte Definition, sondern auch eine saubere Auswertung durch die Software. Die Tabelle “Auswertung” stellt verschiedene Varianten dazu gegenüber. Befürworter von Python nutzen die Methode sys.argv() oder greifen auf eines der drei Module getopt, argparse oder optparse [3] zurück.
|
Implementierung |
C/C++ |
Python |
Perl |
Tcl |
|---|---|---|---|---|
|
Sprachstandard |
|
|
|
|
|
Getopt |
|
|
|
|
|
Argparse |
|
|
|
|
|
Optparse |
|
|
– |
– |
|
Docopt |
|
|
|
|
|
GetKnownOpt |
– |
– |
– |
|
|
Usage |
– |
– |
– |
|
|
Suboptions |
|
– |
– |
– |
Mit Docopt [4] erweitert sich der Spielraum demgegenüber erheblich. Damit drehen Sie sozusagen den Spieß in der Entwicklung um: Sie erzeugen zunächst eine Beschreibung, aus der Sie dann die einzelnen Optionen bestimmen. Der Ansatz ähnelt jenem von Literate Programming, wo Sie eine Beschreibung formulieren und daraus dann den Code des Programms erzeugen, Dokumentation inklusive [5].
Docopt entstammt ursprünglich der Python-Welt. Inzwischen haben die Entwickler weitere Programmiersprachen integriert, darunter C++, F#, Go und Ruby. Unter Debian und Ubuntu heißen die zugehörigen Pakete python-docopt beziehungsweise python3-docopt (für Python 3). Alle Beispiele hier im Beitrag nutzen Python 3. Sofern noch nicht vorhanden, installieren Sie eines der Pakete über die Paketverwaltung nach.
Docopt sieht sich selbst als “Command-line interface description language”. Ein Parser wertet die Beschreibung aus. Diese basiert auf den Gepflogenheiten zur Schreibweise für Hilfetexte und Handbuchseiten, wie sie seit Jahrzehnten unter Linux-Systemen üblich sind.
Alles zusammentragen
Docopt greift auf ein Konzept zurück, das Docstring heißt und so viel wie Angabe zur Dokumentation bedeutet. Am Anfang eines Python-Programms, einer Funktion oder Methode erfolgt eine Beschreibung der Parameter, die das Programm beziehungsweise der Block-Code akzeptiert [6]. Zur Dokumentation des Codes empfiehlt es sich ohnehin, das zu tun – warum also nicht doppelten Nutzen daraus ziehen?
Listing 1 zeigt, wie Sie das nutzen. Die Zeilen 3 bis 10 beschreiben den Aufruf in Form eines Hilfetexts auf einer Manpage. Diese Angaben erlauben noch weitere Möglichkeiten. Aus der Beschreibung ersehen Sie, dass Sie das Programm helloworld.py entweder ganz ohne Parameter oder mit den Schaltern --help beziehungsweise --verbose aufrufen.
Listing 1
#!/usr/bin/python
"""Usage:
./helloworld.py
./helloworld.py --help
./helloworld.py --verbose
Options:
--help display help information
--verbose increase the verbosity of output
"""
# include docopt module
from docopt import docopt
if __name__ == '__main__':
arguments = docopt(__doc__)
print (arguments)
if arguments["--verbose"]:
print("enabling verbose output")
Zeile 14 bindet das Modul docopt ins Programm ein. Das Auswerten der erlaubten Parameter erfolgt in Zeile 17. Dazu greift der Parser aus der Docopt-Klasse auf den Docstring aus den Zeilen 3 bis 10 zurück, die Python über die interne Variable __doc__ zugänglich macht.
Als Ergebnis liefert der Aufruf eine Liste der Parameter, die das Skript in Zeile 18 ausgibt. Sofern Sie keinen Wert für den Schalter angeben, merkt sich Docopt als Wert True oder False – True, wenn Sie den Parameter im Aufruf angegeben haben. Das nutzen Sie dann in Zeile 20 aus.
Abbildung 1 zeigt drei Aufrufe des kleinen Programms – einmal ohne Option, einmal mit --verbose und schließlich mit --help. Letzteres gibt den gesamten Docstring aus und ist bereits vordefiniert. Geben Sie eine Option an, die Sie nicht im Abschnitt mit der Dokumentation definiert haben, gibt das Programm alle Zeilen von dort bis zur ersten Leerzeile aus – in unserem Beispiel also die Zeilen 3 bis 6.

Abbildung 1: Ein einfaches Skript demonstriert, wie Sie die Dokumentation der Optionen direkt im Programmcode unterbringen.
Zweiter Streich
Hier bleibt noch Raum für Verbesserungen – etwa lange und kurze Parameter. Es genügt dazu, den Hilfetext anzupassen. Listing 2 ergänzt -v zu --verbose und -h zu --help. Die Schreibweise mit den eckigen Klammern und der Pipe in den Zeilen 5 und 6 des Docstrings bedeutet entweder/oder. Für den Aufruf ist es ab jetzt unerheblich, welchen Stil für die Optionen Sie bevorzugen – es funktioniert beides.
Listing 2
"""Usage: ./helloworld.py ./helloworld.py [--help | -h] ./helloworld.py [--verbose | -v] Options: --help -h display help information --verbose -v increase the verbosity of output """
Doch es gibt zwei Stolperfallen, eine davon beim Auswerten der Parameter im Programm. Existiert für eine lange Option zusätzlich eine kurze Version, schreiben Sie nur eine Abfrage für die lange Version – Docopt wertet die kurze Variante dann automatisch mit aus. Eine zusätzliche Abfrage für den kurzen Parameter löst bislang eine Fehlermeldung aus.
Die zweite Falle besteht bei der Angabe der Optionen im Docstring. Geben Sie zuerst die lange Version an und danach die kurze, trennen Sie beide Werte mit einem Leerzeichen. Gehen Sie umgekehrt vor, müssen Sie beide Werte stattdessen mit einem Komma trennen (Listing 3).
Listing 3
Options: -h, --help display help information --verbose -v increase the verbosity of output
Es empfiehlt sich, im Docstring mindestens zwei Leerzeichen zwischen die Option und den Beschreibungstext zu setzen. Damit signalisieren Sie dem Parser, dass keine weitere Option folgt, sondern eine Erläuterung (Listing 4).
Listing 4
Options: --left -l nach links # gut --right -r nach rechts # schlecht
Positional Arguments kennzeichnen Sie in Form von Worten in Kleinbuchstaben, die Sie mit spitzen Klammern einschließen (Listing 5). Docopt akzeptiert das Wort aber selbst dann, wenn Sie es vollständig in Großbuchstaben schreiben.
Listing 5
"""Usage: ping <host> PORT """
Optionen
Kurze Optionen dürfen Sie zusammenziehen, dass heißt, die drei Optionen -a -b -c dürfen Sie als -abc schreiben. Lange Optionen erlauben zusätzliche Werte nach einem Gleichheitszeichen, etwa target=/dev/sdb1. Kurze Optionen erlauben zusätzliche Werte sowohl mit als auch ohne Leerzeichen, also gleichermaßen als -t/dev/sdb1 und -t /dev/sdb1.
Listing 6 zeigt Ihnen ein Beispiel aus dem Alltag, das das Kommando ls funktional nachempfindet. Es gibt die Namen aller Einträge im aktuellen Verzeichnis aus. Mittels -f oder --files liefert es nur Dateien, mittels -d oder --dirs nur Verzeichnisse.
Listing 6
#!/usr/bin/python
"""Usage:
./fileinfo.py
./fileinfo.py [--help | -h]
./fileinfo.py [--verbose | -v]
./fileinfo.py [--files | -f]
./fileinfo.py [--dirs | -d]
Options:
--help -h display help information
--verbose -v increase the verbosity of output
--files -f display files only
--dirs -d display directories only
"""
# include docopt module
from docopt import docopt
# import other modules
import os
if __name__ == '__main__':
arguments = docopt(__doc__)
# define which information to show
showFiles = True
showDirectories = True
path = "."
selection = "*"
verbose = False
if arguments["--verbose"]:
print("enabling verbose output ")
verbose = True
if arguments["--files"]:
showDirectories = False
if arguments["--dirs"]:
showFiles = False
# define display path
displayPath = path
for root, dirs, files in os.walk(displayPath):
if showFiles:
for filename in files:
print(filename)
if showDirectories:
for dirname in dirs:
print(dirname)
Das Programm unterscheidet bei der Ausgabe mittels der beiden Variablen showFiles und showDirectories, die es in Abhängigkeit von den auf der Kommandozeile angegebenen Optionen auf True oder False setzt. In der For-Schleife wertet es diese beiden Variablen aus und liefert damit entweder nur Dateien oder Verzeichnisse zurück oder beides.
Alles, was nicht in das bisherige Schema der Parameter passt, behandelt Docopt als Unterkommando. Diese Form kommt etwa beim Verwalten von Paketen via Apt zum Einsatz in Form von apt-get install Paket.
Notwendige Parameter
Neben den eckigen und spitzen Klammern haben auch runde Klammern in Docopt eine Bedeutung: Darin stehen notwendige Parameter. Nützlich ist das unter anderem für Schnittstellen, in denen das Programm einen von mehreren Parametern benötigt. Listing 7 zeigt den Docstring für eine minimale Variante des Paketverwalters apt-get. Dabei tauchen hinter der Angabe von <package> drei Punkte auf. Diese geben an, dass der Parameter mehrfach vorkommen darf; im konkreten Beispiel dürfen Sie also mehr als ein Paket angeben.
Listing 7
"""Usage: apt-get (update | upgrade | dselect-upgrade | dist-upgrade) apt-get (install | remove | purge | source | download) <package>... apt-get (check | clean | autoclean | autoremove) """
Optionen vorbelegen
Docopt kann Vorgabewerte verarbeiten. Dazu ergänzen Sie bei der Beschreibung der jeweiligen Option die Zeichenkette [default: Wert], wobei Sie Wert konkret benennen. Das Beispiel in Listing 8 nutzt das zum Berechnen der Umsatzsteuer aus: Machen Sie keine andere Angabe, verwendet es als Vorgabewert 19 Prozent. Die dafür verantwortliche Zeile 9 lässt sich wie folgt lesen: entweder -t oder --tax, gefolgt von einem Prozentwert und einem Zahlenwert. Geben Sie weder -t oder --tax an, setzt das Programm den Wert dafür auf 19 Prozent.
Die beiden Zeilen 19 und 20 lesen den Zahlenwert und den Prozentsatz aus. In Zeile 21 berechnet das Skript den Wert für die Steuer, in Zeile 22 gibt es ihn als Betrag mit maximal zwei Nachkommastellen aus. Dafür sorgt die Angabe %.2f im Print-Statement. Abbildung 2 illustriert die Ausgabe des Programms mit verschiedenen Werten im Aufruf – ohne Angabe des Prozentsatzes, mit 7 Prozent und in Kurzschreibweise, mit 15 Prozent in Langform und am Ende gänzlich ohne Parameter.
Listing 8
#!/usr/bin/python
"""Usage:
./ust.py <value>
./ust.py -t <percentage> <value>
./ust.py --tax=percentage <value>
Options:
-t <percentage> <value> --tax=<percentage> <value> calculate tax [default: 19]
"""
# include docopt module
from docopt import docopt
if __name__ == '__main__':
arguments = docopt(__doc__)
print (arguments)
value = int(arguments["<value>"])
percentage = int(arguments["--tax"])
tax = value * percentage / 100
print("the %i percent tax for %i is %.2f" % (percentage, value,tax))
Fazit
Das von Vladimir Keleshev im Jahr 2012 erstmalig entwickelte Docopt-Projekt für Python hatte einen gewaltigen Zuspruch und breitete sich in der Folge rasant auf andere Programmiersprachen aus, für die es heute als freie Bibliothek bereitsteht. Mit Docopt generieren Sie schnell und einfach Hilfstexte und werten komfortabel die Aufrufparameter für ein Programm auf der Kommandozeile aus.
Der Vorteil dabei liegt klar auf der Hand: Der Einsatz der Bibliothek erspart das Einbinden von Argparse und viele If-Abfragen. Dass der Docstring gleich noch den Hilfetext für die Kommandozeile liefert, halbiert quasi den Aufwand. Das macht sich insbesondere bei umfangreicheren Schnittstellen bezahlt. Allerdings erfordert Docopt ein Umdenken bei der Arbeit: Das Definieren der Befehle, Argumente und Optionen erfordert verschiedene Arten von Klammern (eckige, spitze und runde), die Sie für eine einwandfreie Funktion sauber setzen müssen. Haben Sie das Konzept aber erst einmal verinnerlicht, macht Ihnen Docopt das Leben leichter.
Über die Autoren
Frank Hofmann arbeitet von unterwegs, bevorzugt in Berlin, Genf und Kapstadt, als Entwickler, Trainer und Autor. Er ist Koautor des Debian-Paketmanagement-Buchs (http://www.dpmb.org). Mandy Neumeyer arbeitet im Tourismus, lebt seit neun Jahren in Südafrika und baut zurzeit ein zusätzliches Einkommen als digitaler Nomade auf.
Infos
-
“Basics of the Unix Philosophy”: http://homepage.cs.uri.edu/~thenry/resources/unix_art/ch01s06.html
-
Helfer für die Kommandozeile: Axel Beckert: “Die Helfer der Kommandozeile”, http://noone.org/talks/commandline-helpers/
-
CLI-Argumente in Python: Frank Hofmann, “Command Line Arguments in Python”, http://www.stackabuse.com/command-line-arguments-in-python/
-
Docopt: http://docopt.org
-
Literate Programming: Wolfram Eifler, “Literate Programming mit Python”, LM 01/2016, http://www.linux-magazin.de/Ausgaben/2016/01/Literate-Programming
-
Python-Docstrings: http://www.pythonforbeginners.com/basics/python-docstrings
-
“Standard Command-Line Options”: http://tldp.org/LDP/abs/html/standard-options.html
-
Standards für CLIs: https://www.gnu.org/prep/standards/html_node/






