How do I pass a hash from commandline?

时间:2015-06-15 14:40:15

标签: ruby

I have a ruby script that has a hash.

Example:

animal_sound = { 'dog' => 'bark', 'cat' => 'meow' }

I want to add 'snake' => 'hiss'

Example:

myscript.rb --addsound "'snake' => 'hiss'"

Then in my script have it add it to animal_sound.

Example:

animal_sound.merge! 'snake' => 'hiss' 
=> {"dog"=>"bark", "cat"=>"meow", "snake"=>"hiss"}

Is there a way to do this?

Here is the whole script:

#!/usr/bin/env ruby
require 'rubygems'
require 'micro-optparse'

options = Parser.new do |p|

        p.option :addsound, "add sound"
end.process!

animal_sound = { 'dog' => 'bark', 'cat' => 'meow' }

if options[:add_sound]
  newsound = options[:add_sound]
  animal_sound.merge! newsound 
end

puts animal_sound

When I run my script I get:

$ bin/myscript.rb --addsound "'snake' => 'hiss'"
bin/myscript.rb:14:in `merge!': can't convert true into Hash (TypeError)
    from bin/myscript.rb:14:in `<main>'

SOLVED: Using PSkocik's solution I got the script to work using animal, sound = options[:addsound].split(' => '); animal_sound[animal] = sound

I also used Simone Carletti's idea to simplify the CLI command. FYI it also works if I want to pass in hash format, like myscript.rb --addsound "'snake' => 'hiss'". Of course the split has to be changed back to split(' => '). I like the simpler CLI using the :.

Example:

myscript.rb --addsound snake:hiss

Final Code:

#!/usr/bin/env ruby
require 'rubygems'
require 'micro-optparse'

options = Parser.new do |p|

        p.option :addsound, "add sound", default: ""
end.process!

animal_sound = { 'dog' => 'bark', 'cat' => 'meow' }

if options[:addsound]
  animal, sound = options[:addsound].split(':') 
  animal_sound[animal] = sound
end

puts animal_sound

Command line:

$ bin/myscript.rb --addsound snake:hiss
{"dog"=>"bark", "cat"=>"meow", "snake"=>"hiss"}

I never could get the merge to work. Each post was helpful. Thanks.

3 个答案:

答案 0 :(得分:1)

It's a good idea to keep the CLI interface detached from the underlying implementation. In fact, you may decide to switch the script in the future from Ruby to another language, and you don't really want to change the way the code is invoked.

My suggestion is to pass a serialized value, for example

myscript.rb --addsound snake:hiss

In the code, simply decompose the content and merge it.

if options[:add_sound]
  animal, sound = options[:add_sound].split(":")
  animal_sound.merge!(animal => sound)
end

答案 1 :(得分:0)

In regards to your error

Notice the line if options[:add_sound]. That basically evaluates to if true. You are getting your error because you are setting newsound to true, and trying to merge a Boolean into a hash. To my knowledge, the .merge only works like so: hash1.merge(hash2).

Passing command line argument

Rather than passing the argument "'snake' => 'hiss'", I suggest making this a comma-delineated list, like so: "snake,hiss". From there, in your if options[:add_sound] block, you can split the string into an array, using a comma as a splitter. Finally, rather than using .merge, you can add your key:value as you normally would for any hash in Ruby. animal_sound[arr[0]] = arr[1].

Mind you, this method will work best with a single key:value pair. I am sure you can submit multiple pairs, but you would need to (by this method) split into more arrays by an additional character(like / maybe).

答案 2 :(得分:0)

p.option :addsound, "add sound"

^ this makes it a flag (true or false)

What you want is make it into a switch whose value is the next argument:

p.option :addsound, "add sound", default: ""

^ this makes it a switch, the string value will be assigned to options[:addsound]

newsound = options[:addsound]

^ Here you need to drop the underscore and parse the string into a hash. Eval is evil. For example, you could split it on ' => ' and forget about quoting:

newsound = [ options[:addsound].split(' => ') ].to_h #and then merge it

(Passing the argument like so --addsound snake:hiss and then splitting on ':' instead of ' => ' is another good option.)

^splitting on ' => ' should yield a two-member array. Here I put it into another array (arrays of two-member arrays are convertible to hashes) to make it convertible into a hash.

Or you do completely without merging and constructing another hash:

animal, sound = options[:addsound].split(' => ')
animal_sound[animal] = sound