2014-10-17 8 views
31

Ich möchte ein Ruby-Skript über die Befehlszeile aufrufen und Parameter übergeben, bei denen es sich um Schlüssel/Wert-Paare handelt.Analysieren von Befehlszeilenargumenten in einem Ruby-Skript

Kommandozeilenaufruf:

$ ruby my_script.rb --first_name=donald --last_name=knuth 

my_script.rb:

puts args.first_name + args.last_name 

Was ist der Standard-Ruby-Weg, dies zu tun? In anderen Sprachen muss ich normalerweise einen Optionsparser verwenden. In Ruby habe ich gesehen, wir haben ARGF.read, aber das scheint nicht funktionieren Schlüssel/Wert-Paare wie in diesem Beispiel.

OptionParser sieht vielversprechend aus, aber ich kann nicht sagen, ob es tatsächlich diesen Fall unterstützt.

+0

https://www.ruby-toolbox.com/categories/CLI_Option_Parsers –

+0

Wenn ich mich nicht irre, scheint Highline l ike Helferfunktionen, um Benutzer zur Eingabe zu bitten. Also würde ich Highline benutzen, um meine Konsole 'Vorname:' ​​sagen zu lassen und auf ihre Eingabe zu warten. Gibt es eine bestimmte Funktion, die ich darin sehen sollte? –

+1

Es gibt viele Edelsteine, aus denen Sie wählen können; Diese Website kategorisiert die Bibliotheken und sortiert sie nach Beliebtheit. Ich habe sogar mein eigenes Juwel namens "acclaim" geschrieben, und es unterstützt die Syntax "--option = value". Ich hatte jedoch keine Zeit, meine freien Software-Projekte zu pflegen. Sie sollten eine besser unterstützte Bibliothek auswählen. –

Antwort

23

Basierend auf der Antwort von @MartinCortez ist hier eine kurze Einzelheit, die einen Hash der Schlüssel/Wert-Paare macht, wobei die Werte mit einem = Zeichen verbunden werden müssen. Es unterstützt auch die Flagge Argumente ohne Werte:

args = Hash[ ARGV.join(' ').scan(/--?([^=\s]+)(?:=(\S+))?/) ] 

... oder alternativ ...

args = Hash[ ARGV.flat_map{|s| s.scan(/--?([^=\s]+)(?:=(\S+))?/) } ] 

Called mit -x=foo -h --jim=jam es gibt {"x"=>"foo", "h"=>nil, "jim"=>"jam"} so können Sie tun Dinge wie:

puts args['jim'] if args.key?('h') 
#=> jam 

Während Es gibt mehrere Bibliotheken, um dies zu handhaben - einschließlich GetoptLong included with Ruby -Ich persönlich bevorzuge es, meine eigenen zu rollen. Hier ist das Muster, das ich verwende, die es vernünftigerweise generic macht, nicht auf ein bestimmtes Nutzungs Format gebunden, und flexibel genug, um vermischte Flags, Optionen und erforderlichen Argumente in verschiedenen Reihenfolgen zu ermöglichen:

USAGE = <<ENDUSAGE 
Usage: 
    docubot [-h] [-v] [create [-s shell] [-f]] directory [-w writer] [-o output_file] [-n] [-l log_file] 
ENDUSAGE 

HELP = <<ENDHELP 
    -h, --help  Show this help. 
    -v, --version Show the version number (#{DocuBot::VERSION}). 
    create   Create a starter directory filled with example files; 
        also copies the template for easy modification, if desired. 
    -s, --shell  The shell to copy from. 
        Available shells: #{DocuBot::SHELLS.join(', ')} 
    -f, --force  Force create over an existing directory, 
        deleting any existing files. 
    -w, --writer  The output type to create [Defaults to 'chm'] 
        Available writers: #{DocuBot::Writer::INSTALLED_WRITERS.join(', ')} 
    -o, --output  The file or folder (depending on the writer) to create. 
        [Default value depends on the writer chosen.] 
    -n, --nopreview Disable automatic preview of .chm. 
    -l, --logfile Specify the filename to log to. 

ENDHELP 

ARGS = { :shell=>'default', :writer=>'chm' } # Setting default values 
UNFLAGGED_ARGS = [ :directory ]    # Bare arguments (no flag) 
next_arg = UNFLAGGED_ARGS.first 
ARGV.each do |arg| 
    case arg 
    when '-h','--help'  then ARGS[:help]  = true 
    when 'create'   then ARGS[:create] = true 
    when '-f','--force'  then ARGS[:force]  = true 
    when '-n','--nopreview' then ARGS[:nopreview] = true 
    when '-v','--version' then ARGS[:version] = true 
    when '-s','--shell'  then next_arg = :shell 
    when '-w','--writer' then next_arg = :writer 
    when '-o','--output' then next_arg = :output 
    when '-l','--logfile' then next_arg = :logfile 
    else 
     if next_arg 
     ARGS[next_arg] = arg 
     UNFLAGGED_ARGS.delete(next_arg) 
     end 
     next_arg = UNFLAGGED_ARGS.first 
    end 
end 

puts "DocuBot v#{DocuBot::VERSION}" if ARGS[:version] 

if ARGS[:help] or !ARGS[:directory] 
    puts USAGE unless ARGS[:version] 
    puts HELP if ARGS[:help] 
    exit 
end 

if ARGS[:logfile] 
    $stdout.reopen(ARGS[:logfile], "w") 
    $stdout.sync = true 
    $stderr.reopen($stdout) 
end 

# etc. 
+0

Das ist nett, aber wenn Sie auch das eingebaute 'ARGF' verwenden, um Streams über Dateinamen/Stdin zu lesen, müssen Sie sicherstellen, dass Sie die Argumente von' ARGV' vor der Verwendung von 'ARGF.read' konsumieren am Ende mit "Keine solche Datei oder Verzeichnis" Fehler. – Lauren

2

Ein bisschen Standard Ruby-Regexp in myscript.rb:

args = {} 

ARGV.each do |arg| 
    match = /--(?<key>.*?)=(?<value>.*)/.match(arg) 
    args[match[:key]] = match[:value] # e.g. args['first_name'] = 'donald' 
end 

puts args['first_name'] + ' ' + args['last_name'] 

Und auf der Kommandozeile:

$ ruby script.rb --first_name=donald --last_name=knuth

Produziert:

$ donald knuth

+1

Das ist ziemlich nett! Die Anforderung, '=' zu verwenden, um zwischen '-f foo' und' -x -y bar' zu unterscheiden, ist für mich ein bisschen ein Nichtstarter für den realen Gebrauch, aber es ist ein netter schneller Hack. – Phrogz

+1

Wahr. Ich folgte nur seinem Format oben. Aufgrund der Frage bin ich mir nicht sicher, ob ihm die Begriffsklärung wichtig ist. –

+0

Das sah fantastisch aus, aber wie würdest du 's' definieren? 'ARGV.join.to_s'? –

72

Rubys eingebaute in OptionParser tut das schön. Kombinieren Sie es mit OpenStruct und du bist frei Haus:

require 'optparse' 

options = {} 
OptionParser.new do |opt| 
    opt.on('--first_name FIRSTNAME') { |o| options[:first_name] = o } 
    opt.on('--last_name LASTNAME') { |o| options[:last_name] = o } 
end.parse! 

puts options 

options werden die Parameter und Werte als Hash enthalten.

Speicher und ausgeführt, dass ohne Parameter Ergebnisse in der Befehlszeile in:

$ ruby test.rb 
{} 

es mit den Parametern ausgeführt wird:

$ ruby test.rb --first_name=foo --last_name=bar 
{:first_name=>"foo", :last_name=>"bar"} 

Das Beispiel eines Hash wird mit den Optionen enthalten, aber Sie kann ein OpenStruct verwenden, das zu einer Nutzung wie Ihre Anfrage führt:

I t sogar automatisch erstellt Ihre -h oder --help Option:

$ ruby test.rb -h 
Usage: test [options] 
     --first_name FIRSTNAME 
     --last_name LASTNAME 

Sie können zu kurz Fahnen verwenden:

require 'optparse' 

options = {} 
OptionParser.new do |opt| 
    opt.on('-f', '--first_name FIRSTNAME') { |o| options[:first_name] = o } 
    opt.on('-l', '--last_name LASTNAME') { |o| options[:last_name] = o } 
end.parse! 

puts options 

Lauf dass auf Herz und Nieren:

$ ruby test.rb -h 
Usage: test [options] 
    -f, --first_name FIRSTNAME 
    -l, --last_name LASTNAME 
$ ruby test.rb -f foo --l bar 
{:first_name=>"foo", :last_name=>"bar"} 

Es ist einfach, Inline-Erklärungen hinzufügen für die Optionen auch:

OptionParser.new do |opt| 
    opt.on('-f', '--first_name FIRSTNAME', 'The first name') { |o| options[:first_name] = o } 
    opt.on('-l', '--last_name LASTNAME', 'The last name') { |o| options[:last_name] = o } 
end.parse! 

und:

$ ruby test.rb -h 
Usage: test [options] 
    -f, --first_name FIRSTNAME  The first name 
    -l, --last_name LASTNAME   The last name 

OptionParser unterstützt auch den Parameter in einen Typ umzuwandeln, wie beispielsweise eine ganze oder ein Array. Weitere Beispiele und Informationen finden Sie in der Dokumentation.

Sie sollten auch an der damit verbundenen Fragen Liste rechts aussehen:

0

Ich benutze Docopt personnaly. Dies ist viel klarer, wartbar und esay-reading.

Werfen Sie einen Blick auf online Ruby implementation doc für Beispiele. Die Verwendung ist sehr einfach.

gem install docopt 

Ruby-Code:

doc = <<DOCOPT 
My program who says hello 

Usage: 
    #{__FILE__} --first_name=<first_name> --last_name=<last_name> 
DOCOPT 

begin 
    args = Docopt::docopt(doc) 
rescue Docopt::Exit => e 
    puts e.message 
    exit 
end 

print "Hello #{args['--first_name']} #{args['--last_name']}" 

Dann ruft:

$ ./says_hello.rb --first_name=Homer --last_name=Simpsons 
Hello Homer Simpsons 

Und ohne Argumente:

$ ./says_hello.rb 
Usage: 
    says_hello.rb --first_name=<first_name> --last_name=<last_name> 
Verwandte Themen