Wirklich günstiges Parsen von Befehlszeilenoptionen in Ruby


114

BEARBEITEN: Bitte, bitte , bitte lesen Sie die beiden Anforderungen, die am Ende dieses Beitrags aufgeführt sind, bevor Sie antworten. Die Leute veröffentlichen immer wieder ihre neuen Juwelen und Bibliotheken und so weiter, die eindeutig nicht den Anforderungen entsprechen.

Manchmal möchte ich einige Befehlszeilenoptionen sehr billig in ein einfaches Skript hacken. Eine unterhaltsame Möglichkeit, dies zu tun, ohne sich mit Getopts, Parsing oder Ähnlichem zu befassen, ist:

...
$quiet       = ARGV.delete('-d')
$interactive = ARGV.delete('-i')
...
# Deal with ARGV as usual here, maybe using ARGF or whatever.

Es ist nicht ganz die normale Unix-Optionssyntax, da sie Optionen akzeptiert, die keine Optionsbefehlszeilenparameter sind, wie in " myprog -i foo bar -q", aber damit kann ich leben. (Einige Leute, wie die Subversion-Entwickler, bevorzugen dies. Manchmal auch.)

Eine Option, die nur vorhanden ist oder fehlt, kann nicht viel einfacher als die oben genannte implementiert werden. (Eine Zuweisung, ein Funktionsaufruf, ein Nebeneffekt.) Gibt es eine ebenso einfache Möglichkeit, mit Optionen umzugehen, die einen Parameter annehmen, z. B. " -f Dateiname "?

BEARBEITEN:

Ein Punkt, den ich vorher nicht angesprochen habe, weil mir erst klar geworden war, als der Autor von Trollop erwähnte, dass die Bibliothek "in eine [800-Zeilen] -Datei" passt, ist, dass ich nicht nur nach sauber suche Syntax, jedoch für eine Technik mit folgenden Eigenschaften:

  1. Der gesamte Code kann in die Skriptdatei aufgenommen werden (ohne das eigentliche Skript selbst zu überfordern, das nur ein paar Dutzend Zeilen umfassen kann), sodass eine einzelne Datei in einem binVerzeichnis auf jedem System mit einem Standard-Ruby 1.8 abgelegt werden kann [5-7] installieren und verwenden. Wenn Sie kein Ruby-Skript schreiben können, für das keine erforderlichen Anweisungen erforderlich sind und der Code zum Parsen einiger Optionen weniger als ein Dutzend Zeilen enthält, besteht diese Anforderung nicht.

  2. Der Code ist klein und einfach genug, dass man sich genug daran erinnern kann, um direkt Code einzugeben, der den Trick macht, anstatt ihn von einem anderen Ort aus auszuschneiden und einzufügen. Stellen Sie sich die Situation vor, in der Sie sich auf der Konsole eines Firewall-Servers ohne Internetzugang befinden und ein schnelles Skript für einen Client zusammenstellen möchten. Ich weiß nichts über dich, aber (abgesehen davon, dass ich die oben genannten Anforderungen nicht erfüllt habe) ist es mir nicht wichtig, mir selbst die 45 Zeilen vereinfachter Mikrooptik zu merken.


2
Nur neugierig auf den Einwand gegen getoptlong?
Mark Carey

Die Ausführlichkeit davon. Bei getoptlog ist der Parsing-Code für Optionen manchmal länger als der Teil des Skripts, der die Arbeit tatsächlich erledigt. Dies ist nicht nur ein ästhetisches Problem, sondern auch ein Problem der Wartungskosten.
cjs

8
Ich verstehe die Anforderungen für die Aufnahme von Skripten nicht - beide getoptlongund optparsebefinden sich in der Standard-Ruby-Bibliothek, sodass Sie sie bei der Bereitstellung Ihres Skripts nicht kopieren MÜSSEN - wenn Ruby auf diesem Computer funktioniert, dann require 'optparse'oder require 'getoptlong'auch.
Rampion

Siehe stackoverflow.com/questions/21357953/… sowie die Antwort von William Morgan zu Trollop.
Fearless_fool

@CurtSampson Ich kann nicht glauben, wie viele Leute Ihre Frage nicht beantwortet haben. So oder so, endlich eine gute Antwort über 3 Beiträge nach unten XD XD
OneChillDude

Antworten:


235

Als Autor von Trollop kann ich nicht glauben , was die Leute in einem Optionsparser für vernünftig halten. Ernsthaft. Es verwirrt den Geist.

Warum sollte ich ein Modul erstellen müssen, das ein anderes Modul erweitert, um Optionen zu analysieren? Warum sollte ich etwas unterordnen müssen? Warum sollte ich ein "Framework" abonnieren müssen, um die Befehlszeile zu analysieren?

Hier ist die Trollop-Version von oben:

opts = Trollop::options do
  opt :quiet, "Use minimal output", :short => 'q'
  opt :interactive, "Be interactive"
  opt :filename, "File to process", :type => String
end

Und das ist es. optsist nun ein Hash mit Tasten :quiet, :interactive, und :filename. Sie können damit machen, was Sie wollen. Und Sie erhalten eine schöne Hilfeseite, die an Ihre Bildschirmbreite angepasst ist, automatische Kurzargumentnamen, Typprüfung ... alles, was Sie brauchen.

Es ist eine Datei, sodass Sie sie in Ihrem lib / -Verzeichnis ablegen können, wenn Sie keine formale Abhängigkeit wünschen. Es hat ein minimales DSL, das leicht zu erlernen ist.

LOC pro Option Personen. Es ist wichtig.


39
Übrigens +1 für das Schreiben von Trollop (das hier bereits erwähnt wurde), aber Sie können den ersten Absatz gerne etwas abschwächen.
cjs

33
Er hat das Recht, sich in diesem Fall zu beschweren, fürchte ich. Wenn Sie sich die Alternativen ansehen: [1 ] [2 ] [3 ], für was im Grunde nur ein einfaches String-Array verarbeitet wird (nein, lassen Sie das wirklich einwirken), müssen Sie sich fragen, WARUM? Was gewinnen Sie von all dem Aufblähen? Dies ist nicht C, wo Zeichenfolgen "problematisch" sind. Natürlich für jeden sein eigenes. :)
srcspider

50
Bitte lindern Sie dies nicht ein wenig. Es ist ein gerechter Estrich, Bruder.
William Pietri

7
Fühlen Sie sich frei, das zehnte Wort ein wenig abzuschwächen.
Andrew Grimm

3
+1 für Trollop. Ich benutze es für mein Testautomatisierungssystem und es funktioniert einfach. Außerdem ist das Codieren so einfach, dass ich manchmal mein Banner neu anordne, um die Freude daran zu erleben.
Kinofrost

76

Ich teile Ihre Abneigung gegen require 'getopts', vor allem wegen der Großartigkeit, die ist OptionParser:

% cat temp.rb                                                            
require 'optparse'
OptionParser.new do |o|
  o.on('-d') { |b| $quiet = b }
  o.on('-i') { |b| $interactive = b }
  o.on('-f FILENAME') { |filename| $filename = filename }
  o.on('-h') { puts o; exit }
  o.parse!
end
p :quiet => $quiet, :interactive => $interactive, :filename => $filename
% ruby temp.rb                                                           
{:interactive=>nil, :filename=>nil, :quiet=>nil}
% ruby temp.rb -h                                                        
Usage: temp [options]
    -d
    -i
    -f FILENAME
    -h
% ruby temp.rb -d                                                        
{:interactive=>nil, :filename=>nil, :quiet=>true}
% ruby temp.rb -i                                                        
{:interactive=>true, :filename=>nil, :quiet=>nil}
% ruby temp.rb -di                                                       
{:interactive=>true, :filename=>nil, :quiet=>true}
% ruby temp.rb -dif apelad                                               
{:interactive=>true, :filename=>"apelad", :quiet=>true}
% ruby temp.rb -f apelad -i                                              
{:interactive=>true, :filename=>"apelad", :quiet=>nil}

6
Vielen Dank, ich kann nicht sehen, wie dies nicht zur OPs-Anfrage passt, insbesondere wenn man bedenkt, dass alles in der Standardbibliothek enthalten ist, verglichen mit der Notwendigkeit, nicht standardmäßigen Code zu installieren / zu
verkaufen

3
Dies sieht genauso aus wie die Trollop-Version, benötigt jedoch keine zusätzliche Datei.
Claudiu

59

Hier ist die Standardtechnik, die ich normalerweise verwende:

#!/usr/bin/env ruby

def usage(s)
    $stderr.puts(s)
    $stderr.puts("Usage: #{File.basename($0)}: [-l <logfile] [-q] file ...")
    exit(2)
end

$quiet   = false
$logfile = nil

loop { case ARGV[0]
    when '-q' then  ARGV.shift; $quiet = true
    when '-l' then  ARGV.shift; $logfile = ARGV.shift
    when /^-/ then  usage("Unknown option: #{ARGV[0].inspect}")
    else break
end; }

# Program carries on here.
puts("quiet: #{$quiet} logfile: #{$logfile.inspect} args: #{ARGV.inspect}")

3
Beantwortet die Frage, aber Mann, Trollop scheint viel einfacher zu handhaben zu sein. Warum das Rad neu erfinden, wenn das vorgefertigte Rad so viel glatter ist?
Mikey TK

7
Das vorgefertigte Rad ist nicht glatter. Lesen Sie die Frage noch einmal sorgfältig durch und achten Sie dabei genau auf die Anforderungen.
cjs

2
+1 Manchmal müssen Sie das Rad neu erfinden, weil Sie andere Abhängigkeiten wie Trollop nicht wollen oder einfach nicht verwenden können.
lzap

Trollop muss nicht als Juwel installiert werden. Sie können einfach eine Datei in libIhrem Ordner oder Code ablegen und verwenden, ohne Rubygems zu berühren.
Overbryd

Für mich musste ich ändern when /^-/ then usage("Unknown option: #{ARGV[0].inspect}")zu when /^-/ then usage("Unknown option: #{ARGV.shift.inspect}")oder es würde in eine unendliche Nutzungs Schleife bekommen
casey

36

Da schien es niemand zu erwähnen, und der Titel bezieht sich billig Befehlszeilen Parsing, warum nicht nur die Ruby - Interpreter lassen die Arbeit für Sie tun? Wenn Sie den -sSchalter übergeben (zum Beispiel in Ihrem Shebang), erhalten Sie kostenlos schmutzige Schalter, die globalen Variablen mit einem Buchstaben zugewiesen sind. Hier ist Ihr Beispiel mit diesem Schalter:

#!/usr/bin/env ruby -s
puts "#$0: Quiet=#$q Interactive=#$i, ARGV=#{ARGV.inspect}"

Und hier ist die Ausgabe, wenn ich das speichere als ./test und chmod +x:

$ ./test
./test: Quiet= Interactive=, ARGV=[]
$ ./test -q foo
./test: Quiet=true Interactive=, ARGV=["foo"]
$ ./test -q -i foo bar baz
./test: Quiet=true Interactive=true, ARGV=["foo", "bar", "baz"]
$ ./test -q=very foo
./test: Quiet=very Interactive=, ARGV=["foo"]

Siehe ruby -hfür Details.

Das muss so billig sein wie es nur geht. Es wird ein NameError ausgelöst, wenn Sie einen Schalter wie versuchen -:, also gibt es dort eine Validierung. Natürlich können Sie nach einem Nicht-Schalter-Argument keine Schalter haben, aber wenn Sie etwas Besonderes benötigen, sollten Sie wirklich mindestens den OptionParser verwenden. Das einzige, was mich an dieser Technik ärgert, ist, dass Sie beim Zugriff auf eine nicht gesetzte globale Variable eine Warnung erhalten (wenn Sie sie aktiviert haben), aber sie ist immer noch falsch, sodass sie für Wegwerfwerkzeuge gut und schnell funktioniert Skripte.

Eine Einschränkung, auf die FelipeC in den Kommentaren in " Wie man wirklich billige Befehlszeilenoptionen in Ruby analysiert " hinweist , ist, dass Ihre Shell den 3-Token-Shebang möglicherweise nicht unterstützt. Möglicherweise müssen Sie /usr/bin/env ruby -wden tatsächlichen Pfad zu Ihrem Ruby (wie /usr/local/bin/ruby -w) ersetzen oder ihn über ein Wrapper-Skript oder etwas anderes ausführen.


2
Danke :) Ich hoffe, er hat in den letzten zwei Jahren nicht auf diese Antwort gewartet.
DarkHeart

3
Ich habe in der Tat die letzten zwei Jahre auf diese Antwort gewartet. :-) Im Ernst, das ist die Art von klugem Denken, nach der ich gesucht habe. Die Warnung ist etwas nervig, aber ich kann mir Möglichkeiten vorstellen, dies zu mildern.
cjs

Ich bin froh, dass ich (irgendwann) helfen konnte, @CurtSampson. Die Flaggen der MRT werden direkt aus Perl herausgerissen, wo sie in der Regel unentgeltlich in Shell-Einzeilern verwendet werden. Fühlen Sie sich frei zu akzeptieren, wenn die Antwort für Sie noch nützlich ist. :)
bjjb

1
Sie können unter Linux nicht mehrere Argumente in einem Shebang verwenden. / usr / bin / env: 'ruby -s': Keine solche Datei oder kein solches Verzeichnis
FelipeC

13

Ich habe Micro-Optparse entwickelt , um diesen offensichtlichen Bedarf an einem kurzen, aber einfach zu verwendenden Options-Parser zu decken . Es hat eine ähnliche Syntax wie Trollop und ist 70 Zeilen kurz. Wenn Sie keine Validierungen benötigen und auf leere Zeilen verzichten können, können Sie diese auf 45 Zeilen reduzieren. Ich denke, genau das haben Sie gesucht.

Kurzes Beispiel:

options = Parser.new do |p|
  p.version = "fancy script version 1.0"
  p.option :verbose, "turn on verbose mode"
  p.option :number_of_chairs, "defines how many chairs are in the classroom", :default => 1
  p.option :room_number, "select room number", :default => 2, :value_in_set => [1,2,3,4]
end.process!

Aufruf des Skripts mit -hoder --helpwird gedruckt

Usage: micro-optparse-example [options]
    -v, --[no-]verbose               turn on verbose mode
    -n, --number-of-chairs 1         defines how many chairs are in the classroom
    -r, --room-number 2              select room number
    -h, --help                       Show this message
    -V, --version                    Print version

Es prüft, ob die Eingabe vom gleichen Typ wie der Standardwert ist, generiert kurze und lange Accessoren, druckt beschreibende Fehlermeldungen, wenn ungültige Argumente angegeben werden, und vieles mehr.

Ich habe mehrere Optionsparser verglichen, indem ich jeden Optionsparser für das Problem verwendet habe, das ich hatte. Sie können diese Beispiele und meine Zusammenfassung verwenden, um eine informative Entscheidung zu treffen. Fühlen Sie sich frei, der Implementierung weitere Implementierungen hinzuzufügen. :) :)


Die Bibliothek selbst sieht großartig aus. Ist es jedoch nicht unaufrichtig, die Anzahl der Zeilen mit Trollop zu vergleichen, da Sie von optparse1937 Zeilen abhängen und diese benötigen (geben oder nehmen).
Telemachos

6
Das Vergleichen der Zeilenanzahl ist absolut in Ordnung, da optparsees sich um eine Standardbibliothek handelt, dh sie wird mit jeder Ruby-Installation geliefert. Trollopist eine Bibliothek eines Drittanbieters, daher müssen Sie den vollständigen Code jedes Mal importieren, wenn Sie ihn in ein Projekt aufnehmen möchten. µ-optparse benötigt immer nur die ~ 70 Zeilen, da diese optparsebereits vorhanden sind.
Florian Pilz

8

Ich verstehe vollkommen, warum Sie Optparse vermeiden wollen - es kann zu viel werden. Es gibt jedoch einige weitaus "leichtere" Lösungen (im Vergleich zu OptParse), die als Bibliotheken geliefert werden, aber einfach genug sind, um eine einzelne Edelsteininstallation lohnenswert zu machen.

Zum Beispiel Besuche dieses OptiFlag Beispiel . Nur ein paar Zeilen für die Bearbeitung. Ein leicht verkürztes Beispiel, das auf Ihren Fall zugeschnitten ist:

require 'optiflag'

module Whatever extend OptiFlagSet
  flag "f"
  and_process!
end 

ARGV.flags.f # => .. whatever ..

Es gibt auch Tonnen von kundenspezifischen Beispielen . Ich erinnere mich, dass ich ein anderes verwendet habe, das noch einfacher war, aber es ist mir vorerst entgangen, aber ich werde zurückkommen und hier einen Kommentar hinzufügen, wenn ich ihn finde.


Sie können Ihre Antwort jederzeit bearbeiten, um sie besser an die geklärte Frage anzupassen.
cjs

4

Dies ist, was ich für wirklich, wirklich billige Argumente benutze:

def main
  ARGV.each { |a| eval a }
end

main

Wenn Sie also ausführen programname foo bar, wird foo und dann bar aufgerufen. Es ist praktisch für Wegwerfskripte.


3

Sie können etwas versuchen wie:

if( ARGV.include( '-f' ) )
  file = ARGV[ARGV.indexof( '-f' ) + 1 )]
  ARGV.delete('-f')
  ARGV.delete(file)
end

3

Haben Sie Thor von Wycats in Betracht gezogen? Ich denke, es ist viel sauberer als optparse. Wenn Sie bereits ein Skript geschrieben haben, ist es möglicherweise etwas aufwendiger, es zu formatieren oder für thor umzugestalten, aber es macht die Handhabungsoptionen sehr einfach.

Hier ist das Beispiel-Snippet aus der README:

class MyApp < Thor                                                # [1]
  map "-L" => :list                                               # [2]

  desc "install APP_NAME", "install one of the available apps"    # [3]
  method_options :force => :boolean, :alias => :optional          # [4]
  def install(name)
    user_alias = options[:alias]
    if options.force?
      # do something
    end
    # ... other code ...
  end

  desc "list [SEARCH]", "list all of the available apps, limited by SEARCH"
  def list(search = "")
    # list everything
  end
end

Thor ordnet Befehle automatisch als solche zu:

app install myname --force

Das wird konvertiert zu:

MyApp.new.install("myname")
# with {'force' => true} as options hash
  1. Von Thor erben, um eine Klasse in einen Optionsmapper zu verwandeln
  2. Ordnen Sie bestimmten Methoden zusätzliche ungültige Bezeichner zu. In diesem Fall konvertieren Sie -L in: list
  3. Beschreiben Sie die Methode direkt unten. Der erste Parameter ist die Verwendungsinformation und der zweite Parameter ist die Beschreibung.
  4. Geben Sie zusätzliche Optionen an. Diese werden von - und - Parametern gemarshallt. In diesem Fall werden die Optionen --force und -f hinzugefügt.

Ich mag die Befehlszuordnung, da ich oft eine einzelne Binärdatei mit einer Reihe von Unterbefehlen mache. Trotzdem, obwohl du einen Weg von 'Licht' gegangen bist. Könnten Sie einen noch einfacheren Weg finden, um dieselbe Funktionalität auszudrücken? Was ist, wenn Sie keine --helpAusgabe drucken müssen ? Was ist, wenn "head myprogram.rb" die Hilfeausgabe war?
cjs

3

Hier ist mein Lieblings-Parser für schnelle und schmutzige Optionen:

case ARGV.join
when /-h/
  puts "help message"
  exit
when /-opt1/
  puts "running opt1"
end

Die Optionen sind reguläre Ausdrücke, daher würde "-h" auch mit "--help" übereinstimmen.

Lesbar, leicht zu merken, keine externe Bibliothek und minimaler Code.


Ja es würde. Wenn das ein Problem ist, können Sie zum Beispiel mehr Regex hinzufügen/-h(\b|elp)
EdwardTeach

2

Trollop ist ziemlich billig.


Das wäre < trollop.rubyforge.org >. Ich mag es eher, denke ich, obwohl ich wirklich keine Bibliothek gesucht habe.
cjs

Es ist wahr, es ist eine Bibliothek. Bei <800 LOC ist dies jedoch ziemlich vernachlässigbar. gitorious.org/trollop/mainline/blobs/master/lib/trollop.rb
g33kz0r

1
Ich dachte irgendwie, dass vielleicht 30-50 Zeilen gut wären, wenn ich so weit gehen würde, eine "Bibliothek" zu benutzen. Andererseits ist das API-Design wichtiger als die Zeilenanzahl, sobald Sie eine separate Datei mit Code erstellt haben. Trotzdem bin ich mir nicht sicher, ob ich es in ein einmaliges Skript aufnehmen möchte, das ich nur auf einem zufälligen System in das bin-Verzeichnis plumpsen möchte.
cjs

1
Sie haben es verkehrt herum: Es geht darum, eine komplexere Bereitstellungsstrategie zu vermeiden.
cjs

1
Sie liegen völlig falsch, weil Sie die Bedürfnisse und Absichten der Person, die die Frage gestellt hat, völlig falsch interpretieren (oder ignorieren). Ich schlage vor, dass Sie die Frage sorgfältig durchlesen, insbesondere die letzten beiden Punkte.
cjs

2

Wenn Sie einen einfachen Befehlszeilenparser für Schlüssel- / Wertbefehle ohne Verwendung von Edelsteinen benötigen:

Aber dieses funktioniert jedoch nur, wenn Sie immer Schlüssel / Wert-Paare haben.

# example
# script.rb -u username -p mypass

# check if there are even set of params given
if ARGV.count.odd? 
    puts 'invalid number of arguments'
    exit 1
end

# holds key/value pair of cl params {key1 => value1, key2 => valye2, ...}
opts = {} 

(ARGV.count/2).times do |i|
    k,v = ARGV.shift(2)
    opts[k] = v # create k/v pair
end

# set defaults if no params are given
opts['-u'] ||= 'root'

# example use of opts
puts "username:#{opts['-u']} password:#{opts['-p']}"

Wenn Sie keine Überprüfung benötigen, können Sie einfach Folgendes verwenden:

opts = {} 

(ARGV.count/2).times do |i|
    k,v = ARGV.shift(2)
    opts[k] = v # create k/v pair
end

2

Hier ist das Code-Snippet, das ich oben in den meisten meiner Skripte verwende:

arghash = Hash.new.tap { |h| # Parse ARGV into a hash
    i = -1                      
    ARGV.map{  |s| /(-[a-zA-Z_-])?([^=]+)?(=)?(.+)?/m.match(s).to_a }
     .each{ |(_,a,b,c,d)| h[ a ? "#{a}#{b}#{c}" : (i+=1) ] =
                             (a ? (c ? "#{d}" : true) : "#{b}#{c}#{d}") 
          }
    [[:argc,Proc.new  {|| h.count{|(k,_)| !k.is_a?(String)}}],
     [:switches, Proc.new {|| h.keys.select{|k| k[0] == '-' }}]
    ].each{|(n,p)| h.define_singleton_method(n,&p) }
}

Ich hasse es auch, zusätzliche Dateien in meinen schnellen und schmutzigen Skripten zu benötigen. Meine Lösung ist fast das, wonach Sie fragen. Ich füge einen 10-zeiligen Codeausschnitt oben in eines meiner Skripte ein, der die Befehlszeile analysiert, Positionsargumente speichert und in ein Hash-Objekt wechselt (normalerweise einem Objekt zugewiesen, das ich arghash genannt habe in den folgenden Beispielen ).

Hier ist ein Beispiel für eine Befehlszeile, die Sie möglicherweise analysieren möchten ...

./myexampleprog.rb -s -x=15 --longswitch arg1 --longswitch2=val1 arg2

Welches würde ein Hash wie dieser werden.

 { 
   '-s' => true, 
   '-x=' => '15', 
   '--longswitch' => true, 
   '--longswitch2=' => 'val1', 
   0 => 'arg1', 
   1 => 'arg2'
 }

Darüber hinaus werden dem Hash zwei praktische Methoden hinzugefügt:

  • argc() gibt die Anzahl der Nicht-Switch-Argumente zurück.
  • switches() gibt ein Array zurück, das die Schlüssel für vorhandene Schalter enthält

Dies soll ein paar schnelle und schmutzige Sachen wie ...

  • Überprüfen Sie, ob ich die richtige Anzahl von Positionsargumenten habe, unabhängig von den übergebenen Schaltern ( arghash.argc == 2 ) übergebenen
  • Greifen Sie auf Positionsargumente über ihre relative Position zu, unabhängig davon, ob Schalter vor oder mit Positionsargumenten durchsetzt sind (z. B. wird arghash[1]immer das zweite Nicht-Schalter-Argument abgerufen).
  • Unterstützt wertzugewiesene Schalter in der Befehlszeile wie "--max = 15", auf die zugegriffen werden kann, arghash['--max=']wodurch in der Beispielbefehlszeile der Wert '15' erhalten wird.
  • Testen Sie das Vorhandensein oder Fehlen eines Schalters in der Befehlszeile mit einer sehr einfachen Notation, die beispielsweise als arghash['-s']wahr ausgewertet wird, wenn sie vorhanden ist, und als Null, wenn sie nicht vorhanden ist.
  • Testen Sie das Vorhandensein eines Schalters oder von Alternativen von Schaltern mit festgelegten Operationen wie

    puts USAGETEXT if !(%w(-h --help) & arghash.switches()).empty?

  • Identifizieren Sie die Verwendung ungültiger Schalter mithilfe von Set-Operationen wie z

    puts "Invalid switch found!" if !(arghash.switches - %w(-valid1 -valid2)).empty?

  • Geben Sie Standardwerte für fehlende Argumente an, indem Sie ein einfaches Beispiel Hash.merge()wie das folgende verwenden, das einen Wert für -max = ausfüllt, wenn eines nicht festgelegt wurde, und ein viertes Positionsargument hinzufügt, wenn eines nicht übergeben wurde.

    with_defaults = {'-max=' => 20, 3 => 'default.txt'}.merge(arghash)


(Ich habe dies bearbeitet, um die Code-Formatierung zu bereinigen. Dabei wurde hauptsächlich die Ausrichtung verwendet, um die Block- und Steuerelementstruktur klarer zu machen. Dies ist meiner Meinung nach besonders wichtig bei etwas, das so dicht mit Interpunktion ist. Wenn Sie die neue Formatierung hassen, fühlen Sie sich bitte frei um die Bearbeitung rückgängig zu machen.)
cjs

Das ist ziemlich schön, wenn nicht das am einfachsten zu lesende auf der Welt. Ich finde es gut, dass es auch zeigt, dass das Ändern der Argument "Syntax" (hier das Zulassen von Optionen nach Positionsargumenten und das Nichtzulassen von Optionsargumenten, außer durch Verwendung =) einen Unterschied im benötigten Code bewirken kann.
cjs

Danke für die Neuformatierung. Es ist definitiv dunkel zu lesen und man könnte leicht die Länge des Codes gegen Klarheit eintauschen. Jetzt, da ich diesem Code mehr oder weniger vertraue, behandle ich ihn wie ein Juwel und versuche nie herauszufinden, was er unter der Decke tut (Klarheit ist jetzt, da ich Vertrauen habe, nicht mehr wichtig).
David Foster

1

Dies ist der akzeptierten Antwort sehr ähnlich, aber mit ARGV.delete_if welcher ich sie in meinem einfachen Parser . Der einzige wirkliche Unterschied besteht darin, dass Optionen mit Argumenten zusammen sein müssen (z -l=file. B. ).

def usage
  "usage: #{File.basename($0)}: [-l=<logfile>] [-q] file ..."
end

$quiet = false
$logfile = nil

ARGV.delete_if do |cur|
  next false if cur[0] != '-'
  case cur
  when '-q'
    $quiet = true
  when /^-l=(.+)$/
    $logfile = $1
  else
    $stderr.puts "Unknown option: #{cur}"
    $stderr.puts usage
    exit 1
  end
end

puts "quiet: #{$quiet} logfile: #{$logfile.inspect} args: #{ARGV.inspect}"

0

Anscheinend denken @WilliamMorgan und ich gleich. Ich habe gestern Abend für Github veröffentlicht. Was ich jetzt sehe, ist eine ähnliche Bibliothek wie Trollop (benannt wie?), Nachdem ich auf Github nach OptionParser gesucht habe, siehe Switches

Es gibt einige Unterschiede, aber die Philosophie ist dieselbe. Ein offensichtlicher Unterschied besteht darin, dass Switches von OptionParser abhängig sind.


0

Ich entwickle mein eigenes Optionsparser-Juwel namens Acclaim .

Ich habe es geschrieben, weil ich Befehlszeilenschnittstellen im Git-Stil erstellen und die Funktionalität jedes Befehls sauber in separate Klassen unterteilen wollte, aber es kann auch ohne das gesamte Befehlsframework verwendet werden:

(options = []) << Acclaim::Option.new(:verbose, '-v', '--verbose')
values = Acclaim::Option::Parser.new(ARGV, options).parse!
puts 'Verbose.' if values.verbose?

Noch keine stabile Version, aber ich habe bereits einige Funktionen implementiert wie:

  • Parser für benutzerdefinierte Optionen
  • Flexibles Parsen der Optionsargumente, das sowohl minimale als auch optionale Argumente zulässt
  • Unterstützung für viele Optionsstile
  • Ersetzen, Anhängen oder Erhöhen mehrerer Instanzen derselben Option
  • benutzerdefinierte Optionshandler
  • Benutzerdefinierte Typhandler
  • vordefinierte Handler für die allgemeinen Standardbibliotheksklassen

Es wird viel Wert auf Befehle gelegt, daher ist es für das einfache Parsen von Befehlszeilen möglicherweise etwas schwer, aber es funktioniert gut und ich habe es in all meinen Projekten verwendet. Wenn Sie sich für den Aspekt der Befehlsoberfläche interessieren, finden Sie auf der GitHub-Seite des Projekts weitere Informationen und Beispiele.


1
Ich kann Acclaim nur empfehlen. Es ist einfach zu bedienen und bietet alle Optionen, die Sie benötigen.
Bowsersenior

0

Angenommen, ein Befehl hat höchstens eine Aktion und eine beliebige Anzahl solcher Optionen:

cmd.rb
cmd.rb action
cmd.rb action -a -b ...
cmd.rb action -ab ...

Das Parsen ohne Validierung kann folgendermaßen aussehen:

ACTION = ARGV.shift
OPTIONS = ARGV.join.tr('-', '')

if ACTION == '***'
  ...
  if OPTIONS.include? '*'
    ...
  end
  ...
end

0

https://github.com/soveran/clap

other_args = Clap.run ARGV,
  "-s" => lambda { |s| switch = s },
  "-o" => lambda { other = true }

46LOC (bei 1.0.0), keine Abhängigkeit vom externen Optionsparser. Erledigt die Arbeit. Wahrscheinlich nicht so voll ausgestattet wie andere, aber es ist 46LOC.

Wenn Sie den Code überprüfen, können Sie die zugrunde liegende Technik ziemlich einfach duplizieren - weisen Sie Lambdas zu und verwenden Sie die Arität, um sicherzustellen, dass die richtige Anzahl von Argumenten dem Flag folgt, wenn Sie wirklich keine externe Bibliothek möchten.

Einfach. Billig.


BEARBEITEN : Das zugrunde liegende Konzept ist auf den Punkt gebracht, da Sie es möglicherweise kopieren / in ein Skript einfügen, um einen vernünftigen Befehlszeilenparser zu erstellen. Es ist definitiv nichts, was ich in Erinnerung behalten würde, aber die Lambda-Arität als billigen Parser zu verwenden, ist eine neuartige Idee:

flag = false
option = nil
opts = {
  "--flag" => ->() { flag = true },
  "--option" => ->(v) { option = v }
}

argv = ARGV
args = []

while argv.any?
  item = argv.shift
  flag = opts[item]

  if flag
    raise ArgumentError if argv.size < arity
    flag.call(*argv.shift(arity))
  else
    args << item
  end
end

# ...do stuff...

Bitte lesen Sie Punkt 1 am Ende der Frage. Wenn Sie hier nicht den gesamten erforderlichen Code direkt in Ihre Antwort eingeben können, ist dies keine Antwort auf die Frage.
cjs

Guter Punkt! Ich denke, zu der Zeit, als ich davon ausgegangen bin, dass die Bibliothek klein genug ist, dass Sie das Ganze in jedes Skript kopieren / einfügen können, an dem Sie gearbeitet haben, ohne eine externe Abhängigkeit zu benötigen, aber es ist definitiv kein sauberer Einzeiler, den ich in den Speicher übernehmen würde um Ihren Punkt # 2 zu erfüllen. Ich denke, das zugrunde liegende Konzept ist neuartig und cool genug, dass ich eine überarbeitete Version erstellt habe, die Ihre Frage etwas angemessener beantwortet.
Ben Alavi

-1

Ich werde meinen eigenen einfachen Optionsparser teilen, an dem ich seit einiger Zeit arbeite. Es sind lediglich 74 Codezeilen und es werden die Grundlagen des internen Optionsparsers des Git erläutert. Ich habe OptionParser als Inspiration genommen und auch Git's.

https://gist.github.com/felipec/6772110

Es sieht aus wie das:

opts = ParseOpt.new
opts.usage = "git foo"

opts.on("b", "bool", help: "Boolean") do |v|
 $bool = v
end

opts.on("s", "string", help: "String") do |v|
 $str = v
end

opts.on("n", "number", help: "Number") do |v|
 $num = v.to_i
end

opts.parse

Sie haben nicht einmal den Code überprüft. Ich habe eine andere Antwort gegeben, die den Parsing-Code herausnimmt.
FelipeC

Ich musste nicht sagen, dass Sie sagten, es sei 74 Zeilen lang. Ich habe es mir jedoch gerade angesehen und es verstößt immer noch gegen den ersten Satz von Anforderung 2. (Diese Antwort verstößt auch gegen die Stack Overflow-Konvention, dass Sie den Code in Ihre Antwort aufnehmen sollten, anstatt einen externen Link anzugeben.)
cjs

-1

EasyOptions erfordert überhaupt keinen Optionsparsing-Code. Schreiben Sie einfach den Hilfetext, benötigen Sie, fertig.

## Options:
##   -i, --interactive  Interactive mode
##   -q, --quiet        Silent mode

require 'easyoptions'
unless EasyOptions.options[:quiet]
    puts 'Interactive mode enabled' if EasyOptions.options[:interactive]
    EasyOptions.arguments.each { |item| puts "Argument: #{item}" }
end

EasyOptions ist eine einzelne Ruby-Datei ohne erforderliche Anweisungen, und es gibt keinen Parsing-Code, an den man sich erinnern kann. Anscheinend möchten Sie stattdessen etwas Einbettbares, das leistungsfähig genug und dennoch einfach zu merken ist.
Renato Silva
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.