web-dev-qa-db-fra.com

Analyser les arguments de ligne de commande dans un script Ruby

Je souhaite appeler un script Ruby à partir de la ligne de commande et transmettre des paramètres qui sont des paires clé/valeur.

Appel en ligne de commande:

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

mon_script.rb:

puts args.first_name + args.last_name

Quelle est la méthode standard de Ruby pour ce faire? Dans d'autres langues, je dois généralement utiliser un analyseur d'options. En Ruby, j’ai vu que nous avions ARGF.read, mais cela ne semble pas fonctionner comme une paire clé/valeur comme dans cet exemple.

OptionParser semble prometteur, mais je ne peux pas dire s'il prend réellement en charge cette affaire.

40
Don P

Basé sur la réponse de @MartinCortez, voici un court-métrage qui crée un hachage de paires clé/valeur, où les valeurs doivent être jointes avec un signe =. Il supporte également les arguments d'indicateur sans valeur:

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

…Ou bien…

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

Appelé avec -x=foo -h --jim=jam, il retourne {"x"=>"foo", "h"=>nil, "jim"=>"jam"} pour que vous puissiez faire des choses comme:

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

Bien qu'il existe plusieurs bibliothèques pour gérer cela - y compris GetoptLong inclus avec Ruby - Personnellement, je préfère lancer la mienne. Voici le modèle que j'utilise, qui le rend raisonnablement générique, non lié à un format d'utilisation spécifique, et suffisamment flexible pour autoriser des indicateurs, des options et des arguments obligatoires mélangés dans différents ordres:

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.
28
Phrogz

Ruby intégré OptionParser le fait bien. Combinez-le avec OpenStruct et vous êtes chez vous gratuitement:

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 contiendra les paramètres et les valeurs sous forme de hachage.

L'enregistrement et l'exécution sur la ligne de commande sans paramètre entraînent:

$ Ruby test.rb
{}

L'exécuter avec des paramètres:

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

Cet exemple utilise un hachage pour contenir les options, mais vous pouvez utiliser un OpenStruct qui générera un usage similaire à votre requête:

require 'optparse'
require 'ostruct'

options = OpenStruct.new
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!

puts options.first_name + ' ' + options.last_name

$ Ruby test.rb --first_name=foo --last_name=bar
foo bar

Il crée même automatiquement votre option -h ou --help:

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

Vous pouvez aussi utiliser des drapeaux courts:

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

Exécuter cela à son rythme:

$ 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"}

Il est facile d'ajouter des explications en ligne pour les options aussi:

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!

et:

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

OptionParser prend également en charge la conversion du paramètre en un type, tel qu'un entier ou un tableau. Reportez-vous à la documentation pour plus d'exemples et d'informations.

Vous devriez également consulter la liste de questions connexes à droite:

85
the Tin Man

J'utilise personnellement Docopt . Ceci est beaucoup plus clair, maintenable et facile à lire.

Jetez un coup d'œil à documentation en ligne sur l'implémentation Ruby pour des exemples L'utilisation est vraiment simple.

gem install docopt

Code Ruby:

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']}"

Puis en appelant:

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

Et sans argument:

$ ./says_hello.rb
Usage:
  says_hello.rb --first_name=<first_name> --last_name=<last_name>
2
brunetton

Un peu de Ruby standard 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']

Et sur la ligne de commande:

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

Produit: 

$ donald knuth

1
Marty Cortez

Une version améliorée qui gère des arguments qui ne sont pas des options, des arguments avec un paramètre et -a ainsi que --a.

def parse(args)
  parsed = {}

  args.each do |arg|
    match = /^-?-(?<key>.*?)(=(?<value>.*)|)$/.match(arg)
    if match
      parsed[match[:key].to_sym] = match[:value]
    else
      parsed[:text] = "#{parsed[:text]} #{arg}".strip
    end
  end

  parsed
end
0
Chap Chipley