Julia. Skripte und Parsing-Befehlszeilenargumente


Wir beschäftigen uns weiterhin mit der Programmiersprache Julia. Da für eine Sprache, die sich auf Datenanalyse und -verarbeitung konzentriert, lediglich ein Stapelbetriebsmodus erforderlich ist, sollten Sie die Funktionen zum Implementieren von Skripten in der Sprache Julia und zum Übergeben von Argumenten über die Befehlszeile berücksichtigen. Dieses Thema mag für jemanden banal erscheinen, aber angesichts der Neuheit der Sprache hoffe ich, dass ein kleiner Überblick über die in Julia vorgestellten Methoden zum Parsen von Befehlszeilenargumenten und -bibliotheken weiterhin nützlich ist.


Zunächst ein paar Worte darüber, wie das Skript aufgebaut ist. Jedes Skript beginnt mit einer Zeile in einem speziellen Format, das den Interpreter angibt. Die Zeile beginnt mit einer Sequenz namens Shebang. Für Julia lautet diese Zeile:


#!/usr/bin/env julia 

Natürlich können Sie dies nicht tun, aber dann müssen Sie das Skript mit dem folgenden Befehl ausführen:


 julia .jl 

Außerdem muss jedes Skript mit einem Zeilenumbruchzeichen enden. Dies ist eine Anforderung des POSIX-Standards, der sich aus der Definition einer Zeichenfolge als Folge von Zeichen ergibt, die durch ein Zeilenumbruchzeichen abgeschlossen werden.


Damit das Skript direkt ausgeführt werden kann, muss es das executable Attribut haben. Sie können ein solches Attribut im Terminal mit dem folgenden Befehl hinzufügen:


 chmod +x .jl 

Diese Regeln gelten für alle modernen Betriebssysteme, außer möglicherweise MS Windows.


ARGS-Array


Fahren wir mit der ersten Option zum Übergeben von Parametern fort. Befehlszeilenargumente sind im Julia-Skript über die Array-Konstante Base.ARGS verfügbar. Bereiten wir das einfachste Skript vor:


 #!/usr/bin/env julia @show typeof(ARGS) @show ARGS 

Dieses Skript druckt einfach den Typ und den Inhalt des ARGS-Arrays auf die Konsole.


Sehr oft wird der Dateiname als Befehlszeilenargumente übergeben. Und hier gibt es eine Besonderheit bei der Verarbeitung einer als Argument übergebenen Dateivorlage. Führen Sie beispielsweise unser Skript mit dem Befehl ./args.jl *.jl und erhalten Sie:


 >./args.jl *.jl typeof(ARGS) = Array{String,1} ARGS = ["argparse.jl", "args.jl", "docopt.jl"] 

Und jetzt ändern wir den Befehlszeilenparameter ein wenig und umgeben die Maske mit Anführungszeichen:
./args.jl "*.jl" . Als Ergebnis erhalten wir:


 >./args.jl "*.jl" typeof(ARGS) = Array{String,1} ARGS = ["*.jl"] 

Wir sehen den offensichtlichen Unterschied. Im ersten Fall haben wir ein Array mit den Namen aller Dateien erhalten, die sich im selben Verzeichnis befinden. Im zweiten Fall ist dies nur dieselbe Maske, die als Argument an die Befehlszeile übergeben wurde. Der Grund für dieses unterschiedliche Verhalten des Skripts liegt darin, dass der Bash-Interpreter (sowie diejenigen in seiner Nähe), von denen aus das Skript ausgeführt wurde, Dateinamenvorlagen erkennt. Weitere finden Sie in der Suchmaschine für "Bash Pattern Matching" oder "Bash Wildcards". Und alles in allem heißt es Globs.


Unter den Vorlagen ist es möglich, mehrere Zeichen zu maskieren - *, ein Zeichen zu maskieren -? .. Suche nach Bereich [...] und sogar die Möglichkeit, komplexe Kombinationen anzugeben:


 >./args.jl {args,doc}* typeof(ARGS) = Array{String,1} ARGS = ["args.jl", "docopt.jl"] 

Weitere Informationen finden Sie in der Zusammenfassung der GNU / Linux-Befehlszeilentools.


Wenn wir aus irgendeinem Grund den von bash bereitgestellten Globs-Mechanismus nicht verwenden möchten, können Sie Dateien anhand der Maske bereits im Skript mithilfe des Globs.jl-Pakets finden.
Der folgende Code konvertiert alles, was in der Argumentzeichenfolge gefunden wird, in ein einzelnes Array von Dateinamen. Das heißt, unabhängig davon, ob der Benutzer die Maske in Anführungszeichen ohne Anführungszeichen angegeben oder einfach die Namen vorhandener oder nicht vorhandener Dateien aufgelistet hat, verbleiben nur die Namen der tatsächlichen Dateien oder Verzeichnisse im resultierenden filelist .


 using Glob filelist = unique(collect(Iterators.flatten(map(arg -> glob(arg), ARGS)))) 

Diese einfachen Beispiele sind in der Tat eine Demonstration der Verwendung des ARGS-Arrays, bei dem der Programmierer die gesamte Logik zum Parsen von Argumenten implementiert. Dieser Ansatz wird häufig verwendet, wenn die Argumentation äußerst einfach ist. Zum Beispiel eine Liste von Dateinamen. Oder eine oder zwei Optionen, die durch einfache Zeichenfolgenoperationen behandelt werden können. Der Zugriff auf ARGS-Elemente ist der gleiche wie auf Elemente eines anderen Arrays. Denken Sie nur daran, dass der Index des ersten Elements des Arrays in Julia 1 ist.


Paket ArgParse.jl


Es ist ein flexibles Tool zum Beschreiben von Attributen und Optionen der Befehlszeile, ohne dass eine Parsing-Logik implementiert werden muss.
Wir werden ein leicht modifiziertes Beispiel aus der Paketdokumentation verwenden - http://carlobaldassi.imtqy.com/ArgParse.jl/stable/ :


 #!/usr/bin/env julia using ArgParse function parse_commandline() s = ArgParseSettings() @add_arg_table s begin "--opt1" help = "an option with an argument" "--opt2", "-o" help = "another option with an argument" arg_type = Int default = 0 "--flag1" help = "an option without argument, ie a flag" action = :store_true "arg1" help = "a positional argument" required = true end return parse_args(s) end function main() @show parsed_args = parse_commandline() println("Parsed args:") for (arg,val) in parsed_args print(" $arg => ") show(val) println() end end main() 

Wenn wir dieses Skript ohne Argumente ausführen, erhalten wir die Ausgabe von Referenzinformationen zu ihrer Zusammensetzung:


 >./argparse.jl required argument arg1 was not provided usage: argparse.jl [--opt1 OPT1] [-o OPT2] [--flag1] arg1 

Darüber hinaus sehen wir in eckigen Klammern optionale Argumente. Während das als arg1 gekennzeichnete arg1 ( arg1 was wir es ersetzen) obligatorisch ist.


Führen Sie es erneut aus, geben Sie jedoch das erforderliche Attribut arg1 .


 >./argparse.jl test parsed_args = parse_commandline() = Dict{String,Any}("flag1"=>false,"arg1"=>"test","opt1"=>nothing,"opt2"=>0) Parsed args: flag1 => false arg1 => "test" opt1 => nothing opt2 => 0 

Wir können sehen, dass parsed_args ein assoziatives Array ist, bei dem die Schlüssel die Namen der Attribute gemäß der in der Funktion parse_commandline abgegebenen Deklaration sind und deren Werte standardmäßig festgelegt oder als Werte der Befehlszeilenargumente übergeben wurden. Darüber hinaus sind die Werte von dem Typ, der in der Deklaration explizit angegeben wird.


Argumente werden mit dem Makro @add_arg_table . Es ist möglich, Optionen zu deklarieren:


  "--opt2", "-o" help = "another option with an argument" arg_type = Int default = 0 

Oder Argumente


  "arg1" help = "a positional argument" required = true 

Darüber hinaus können Optionen angegeben werden, die die vollständige und die kurze Form angeben (gleichzeitig --opt2 und -o ). Oder nur in einer einzigen Form. Der Typ wird im Feld arg_type angegeben. Der Standardwert kann mit default = ... eingestellt werden default = ... Eine Alternative zum Standardwert besteht darin, ein Argument zu erfordern - required = true .
Es ist möglich, eine automatische Aktion zu deklarieren, z. B. je nach Vorhandensein oder Fehlen eines Arguments true oder false zuzuweisen. Dies geschieht mit action = :store_true


  "--flag1" help = "an option without argument, ie a flag" action = :store_true 

Das help enthält den Text, der in der Eingabeaufforderung in der Befehlszeile angezeigt wird.
Wenn wir beim Start alle Attribute angeben, erhalten wir:


 >./argparse.jl --opt1 "2+2" --opt2 "4" somearg --flag parsed_args = parse_commandline() = Dict{String,Any}("flag1"=>true,"arg1"=>"somearg","opt1"=>"2+2","opt2"=>4) Parsed args: flag1 => true arg1 => "somearg" opt1 => "2+2" opt2 => 4 

Zum Debuggen von der Atom / Juno-IDE können Sie in den ersten Zeilen des Skripts den folgenden, etwas schmutzigen, aber funktionierenden Code hinzufügen, um das ARGS-Array zu initialisieren.


 if (Base.source_path() != Base.basename(@__FILE__)) vcat(Base.ARGS, ["--opt1", "2+2", "--opt2", "4", "somearg", "--flag"] ) end 

Das Makro @__FILE__ ist der Name der Datei, in der das Makro bereitgestellt wird. Dieser Name für REPL unterscheidet sich vom Namen der aktuellen Programmdatei, die über Base.source_path() abgerufen wurde. Es ist unmöglich, die Base.ARGS Array- Base.ARGS anderen Wert zu initialisieren. Base.ARGS können Sie jedoch neue Zeilen hinzufügen, da das Array selbst keine Konstante ist. Ein Array ist eine Spalte für Julia, daher verwenden wir vcat (vertikale Verkettung).


In den Einstellungen des Juno-Editors können Sie jedoch die Argumente für die Ausführung des Skripts festlegen. Sie müssen jedoch jedes Mal für jedes debuggte Skript einzeln geändert werden.


Paket DocOpt.jl


Diese Option ist eine Implementierung des docopt-Markup-Sprachansatzes - http://docopt.org/ . Die Hauptidee dieser Sprache ist eine deklarative Beschreibung von Optionen und Argumenten in einer Form, die auch eine interne Beschreibung eines Skripts sein kann. Eine spezielle Vorlagensprache wird verwendet.


Wir werden ein Beispiel aus der Dokumentation für dieses Paket https://github.com/docopt/DocOpt.jl verwenden


 #!/usr/bin/env julia doc = """Naval Fate. Usage: naval_fate.jl ship new <name>... naval_fate.jl ship <name> move <x> <y> [--speed=<kn>] naval_fate.jl ship shoot <x> <y> naval_fate.jl mine (set|remove) <x> <y> [--moored|--drifting] naval_fate.jl -h | --help naval_fate.jl --version Options: -h --help Show this screen. --version Show version. --speed=<kn> Speed in knots [default: 10]. --moored Moored (anchored) mine. --drifting Drifting mine. """ using DocOpt # import docopt function args = docopt(doc, version=v"2.0.0") @show args 

Die Notation doc = ... ist die Erstellung der Julia- doc , die die gesamte Deklaration für docopt enthält. Das Ergebnis der Ausführung in der Befehlszeile ohne Argumente lautet:


 >./docopt.jl Usage: naval_fate.jl ship new <name>... naval_fate.jl ship <name> move <x> <y> [--speed=<kn>] naval_fate.jl ship shoot <x> <y> naval_fate.jl mine (set|remove) <x> <y> [--moored|--drifting] naval_fate.jl -h | --help naval_fate.jl --version 

Wenn wir den Hinweis verwenden und versuchen, ein neues Schiff zu erstellen, erhalten wir einen Ausdruck des assoziativen args Arrays, das durch das Parsen der Befehlszeile generiert wurde


 >./docopt.jl ship new Bystriy args = Dict{String,Any}( "remove"=>false, "--help"=>false, "<name>"=>["Bystriy"], "--drifting"=>false, "mine"=>false, "move"=>false, "--version"=>false, "--moored"=>false, "<x>"=>nothing, "ship"=>true, "new"=>true, "shoot"=>false, "set"=>false, "<y>"=>nothing, "--speed"=>"10") 

Die docopt Funktion docopt wie docopt deklariert:


 docopt(doc::AbstractString, argv=ARGS; help=true, version=nothing, options_first=false, exit_on_error=true) 

Die benannten Argumente help , version , oprtions_first , exit_on_error geben exit_on_error das Verhalten des Argumentbefehlsparsers an. Im Falle von Fehlern - um die Ausführung abzuschließen, geben Sie in der Versionsanforderung den hier ersetzten Wert version=… in der Anforderung -h zurück - geben Sie Hilfe aus. options_first verwendet, um anzugeben, dass Optionen vor Positionsargumenten stehen sollen.


Schauen wir uns nun diese deklarative Sprache und die Reaktion des Argumentparsers auf die eingegebenen Werte genauer an.


Die Deklaration beginnt mit einem beliebigen Text, der zusätzlich zum Text für die Befehlszeile Teil der Dokumentation des Skripts selbst sein kann. Das Dienstwort "Verwendung:" deklariert Verwendungsmuster für dieses Skript.


 Usage: naval_fate.jl ship new <name>... naval_fate.jl ship <name> move <x> <y> [--speed=<kn>] 

Argumente werden in der Form <name> , <x> , <y> deklariert. Beachten Sie, dass diese Argumente in dem zuvor erhaltenen assoziativen Array args als Schlüssel fungieren. Wir haben das ./docopt.jl ship new Bystriy , um ./docopt.jl ship new Bystriy , sodass wir die folgenden explizit initialisierten Werte erhalten haben:


  "<name>"=>["Bystriy"], "ship"=>true, "new"=>true, 

Entsprechend der docopt-Sprache werden optionale Elemente in eckigen Klammern angegeben. Zum Beispiel [--speed=<kn>] . Obligatorische Elemente sind in Klammern angegeben, jedoch unter bestimmten Bedingungen. Zum Beispiel (set|remove) legt die Anforderung für einen von ihnen fest. Wenn das Element ohne Klammern angegeben wird, z. B. naval_fate.jl --version , heißt es, dass in dieser bestimmten Ausführungsoption --version eine erforderliche Option ist.


Der nächste Abschnitt ist der Abschnitt mit der Optionsbeschreibung. Es beginnt mit dem Wort "Optionen:"
Optionen werden jeweils in einer separaten Zeile deklariert. Einrückungen links vom Zeilenanfang sind wichtig. Für jede Option können Sie das vollständige und das kurze Formular angeben. Sowie die Beschreibung der Option, die im Tooltip angezeigt wird. In diesem Fall sind die Optionen -h | --help, --version -h | --help, --version automatisch erkannt. Die Antwort darauf wird durch die Argumente an die docopt Funktion gegeben. Interessant zu berücksichtigen ist die Erklärung:


  --speed=<kn> Speed in knots [default: 10]. 

Hier definiert die Form ...=<kn> das Vorhandensein eines Wertes und [default: 10] definiert den Standardwert. Wir wenden uns wieder den Werten zu, die in args :


 "--speed"=>"10" 

Der grundlegende Unterschied zum ArgParse-Paket besteht beispielsweise darin, dass die Werte nicht eingegeben werden. Das heißt, der default: 10 wird als Zeichenfolge "10" festgelegt.
Bei den anderen Argumenten, die als Ergebnis der Analyse der Argumente in Argumenten dargestellt werden, sollten Sie auf ihre Werte achten:


  "remove"=>false, "--help"=>false, "--drifting"=>false, "mine"=>false, "move"=>false, "--version"=>false, "--moored"=>false, "<x>"=>nothing, "shoot"=>false, "set"=>false, "<y>"=>nothing, 

Das heißt, absolut alle Elemente der Vorlage, die in der docopt-Deklaration für alle Anwendungsfälle angegeben sind, werden als Ergebnis einer Analyse mit den ursprünglichen Namen dargestellt. Alle optionalen Argumente, die nicht in der Befehlszeile vorhanden waren, sind hier falsch. Die Argumente <x> , <y> ebenfalls in der Startzeile und haben den Wert nichts. Andere Argumente, für die das Analysemuster übereinstimmte, erhielten wahre Werte:


  "ship"=>true, "new"=>true, 

Und wir haben bereits spezifische Werte für die folgenden Elemente der Vorlage erhalten:


  "<name>"=>["Bystriy"], "--speed"=>"10" 

Der erste Wert wurde explizit in der Befehlszeile als Argumentersetzung festgelegt, und der zweite Wert war eine Option mit einem Standardwert.

Beachten Sie auch, dass der Name des aktuellen Skripts automatisch berechnet werden kann.
Zum Beispiel können wir eingeben:


 doc = """Naval Fate. Usage: $(Base.basename(@__FILE__)) ship new <name>… """ 

Eine zusätzliche Empfehlung zum Platzieren des Befehlszeilenargument-Parsers besteht darin, ihn ganz am Anfang der Datei zu platzieren. Ein unangenehmes Merkmal von Julia ist im Moment eine ziemlich lange Verbindung von Modulen. Zum Beispiel using Plots; using DataFrames using Plots; using DataFrames kann ein Skript using Plots; using DataFrames werden, das einige Sekunden wartet. Dies ist kein Problem für serverseitige Skripts mit einem Ladevorgang, aber es ärgert Benutzer, die nur einen Hinweis für Befehlszeilenargumente sehen möchten. Aus diesem Grund müssen Sie zuerst Hilfe ausgeben und die Befehlszeilenargumente überprüfen. Anschließend müssen Sie die für die Arbeit erforderlichen Bibliotheken herunterladen.


Fazit


Der Artikel gibt nicht vor, alle Methoden zum Parsen von Argumenten in Julia zu berücksichtigen. Die betrachteten Optionen decken jedoch tatsächlich drei mögliche Optionen ab. Vollständig manuelle Analyse des ARGS Arrays. Streng deklarierte, aber automatisch analysierte Argumente in ArgParse. Und eine vollständig deklarative, wenn auch nicht strenge Form von docopt. Die Wahl des Anwendungsfalls hängt vollständig von der Komplexität der analysierten Argumente ab. Die Option mit docopt scheint am einfachsten zu verwenden, erfordert jedoch eine explizite Typkonvertierung für die Werte der empfangenen Argumente. Wenn das Skript jedoch nichts anderes als den Dateinamen akzeptiert, können Sie die Hilfe mit der üblichen println("Run me with file name") und die Dateinamen wie gezeigt direkt aus ARGS analysieren im ersten Abschnitt.


Referenzen


Source: https://habr.com/ru/post/de430942/


All Articles