How do I use command-line options in Ruby? - ruby

How do I use command options in scripts?
I have a script that connects to a device, runs commands and prints the output. I would like to use a file for the hosts, and one for the user and passwd info. When I try to run the script I get these errors:
ruby threat_detection.rb --host_file=hosts --config_file=config
threat_detection.rb:61:in '<main>': needless argument: --host_file=hosts (OptionParser::NeedlessArgument)
ruby threat_detection.rb '--host_file=hosts' '--config_file=config'
threat_detection.rb:61:in '<main>': needless argument: --host_file=hosts (OptionParser::NeedlessArgument)
ruby threat_detection.rb --host_file-hosts --config_file-config
threat_detection.rb:61:in '<main>': invalid option: --host_file-hosts (OptionParser::InvalidOption)
This is the code:
options ={}
opt_parser = OptionParser.new do |opt|
opt.banner = 'Usage: opt_parser COMMAND [OPTIONS]'
opt.on('--host_file', 'I need hosts, put them here') do |host_file|
options[:host_file] == host_file
end
opt.on('--config_file', 'I need config info, put it here') do |config_file|
options[:config_file] == config_file
end
opt.on('-h', '--help', 'What your looking at') do |help|
options[:help] == help
puts opt
end
end
opt_parser.parse!
How do I make these options get read? This is my guess (based off python experience). I'm just looking for it to print the lines right now:
if :config_file == true
File.open(:config_file, r) do |params|
puts params
end
end
if :host_file == true
File.open(:host_file, r) do |host|
put host
end
end

For taking arguments you need to use the following formats
"--switch=MANDATORY" or "--switch MANDATORY" # mandatory argument
"--switch[=OPTIONAL]" # optional argument
"--switch" # no argument
You are currently using the third format, which is being interpreted as taking no argument. You should use the first or second.
Also worth noting you probably want to be doing an assignment instead of a comparison when a flag is on
You want this
options[:host_file] = host_file
not this
options[:host_file] == host_file

Related

`[]': no implicit conversion of Symbol into Integer (TypeError) for a simple ruby Hash

In my ruby code, I define a new hash:
options = {
host: 'localhost',
user: nil,
file: nil,
}
I then parse the hash using the OptionsParser
OptionParser.new do |opts|
opts.banner = 'Usage: ruby-remote-tailer.rb [options]'
opts.on('-h', '--host', 'The host to run ssh session with') do |host|
options[:host] = "#{host}"
end
opts.on('-h', '--user', 'The user account that will run the session') do |user|
options[:user] = "#{user}"
end
opts.on('-f', '--file', 'The file to run the tail on') do |file|
options[:file] = "#{file}"
end
end
And run:
options = ARGV.parse!
puts options[:host]
The last puts line results in an error no implicit conversion of Symbol into Integer (TypeError). I know that the input I put in is correct as doing p options works. Any ideas on how to fix this?
(Note I would prefer not to use a .each loop as suggested in other answers to get the single values I need).
Thanks.
You are not using OptionParser correctly in several ways.
When defining the options you have to provide a placeholder for the actual value of the option. If you do not, it will be interpreted as a switch, returning either true or false depending on whether the switch was set.
opts.on('-h', '--host HOST', 'The host to run ssh session with') do |host|
options[:host] = "#{host}"
end
Another mistake is that you defined -h for both the host and the user option. You better define a different letter to each of them, you probably intended to have -u for the user anyway.
But the main problem, the one causing the error is that you treat the return value of the #parse! method as if it would return the parsed values. But the return value is actually the remainder of the ARGV array that was not matched by the parser. And because you try to access it like a Hash by asking for an element by Symbol, it complains because array elements are accessed only by Integer values. To fix it, just keep the options reference from before and don't assign the return value to it.
ARGV.parse!
From here on, I will give you some further criticism, but the things I will recommend should not be the reason of any errors:
Additionally you might skip the default nil values you provided in the Hash in the beginning, if you ask a Hash for an undefined key, you will provide you with a nil anyway.
options = {
host: 'localhost'
}
I'd say calling #parse! on the the command line argument array ARGV, while it seems to be an option to do so, is not very obvious. I'd recommend saving a reference to the parser to a variable and call the method on the parser.
parser = OptionParser.new do |opts|
…
end
parser.parse!(ARGV)
If you want you can even call it without an argument, it will use ARGV by default. But this would again make your code harder to understand in my opinion.
And you can also get rid of the string conversion of the values through interpolation. At least when you are actually getting your values from the command line arguments array ARGV you can be quite sure that all elements will be String objects. However if you intend to feed the parser with other arrays which are not entirely built with string elements, you should keep the conversion.
opts.on('-h', '--host HOST', 'The host to run ssh session with') do |host|
options[:host] = host
end
Also please note that there is a very, very widespread convention that you use an of exactly two spaces for each level of indentation in Ruby code. In your examples you use four and eight spaces, which a lot of Rubyists would dislike very much. See the The Ruby styleguide for more information.
Here is a fixed-up version of your code:
#!/usr/bin/env ruby
require 'optionparser'
options = {
host: 'localhost'
}
parser = OptionParser.new do |opts|
opts.banner = 'Usage: ruby-remote-tailer.rb [options]'
opts.on('-h', '--host HOST', 'The host to run ssh session with') do |host|
options[:host] = host
end
opts.on('-u', '--user USER', 'The user account that will run the session') do |user|
options[:user] = user
end
opts.on('-f', '--file FILE', 'The file to run the tail on') do |file|
options[:file] = file
end
end
parser.parse!(ARGV)
puts options[:host]

Optparse doesn't seem to return ARGV array. Argument Required Error

This is homework and I do not expect you to give me the complete answer.
I'm trying to parse a command line entry such as:
./apacheReport.rb -u testlog.txt
When I enter this:
./apacheReport.rb -u testlog.txt
I get:
Argument required
My code is:
require_relative 'CommonLog'
require 'optparse'
# puts ARGV.inspect
optparser = OptionParser.new
optU = false
optI = false
optS = false
optH = false
optparser.banner = "apacheReport.rb [options] filename"
optparser.parse!
rescue => m
puts m.message
puts optparser
exit
end
if ARGV.length < 1
puts "Argument required"
exit
end
userInputFile = ARGV[0]
userInputFile.to_s
file = CommonLog.new(userInputFile)
It should parse the leftover portion of the command into ARGV[0] then should store it as userInputFile and then create a CommonLog object using the file as the constructor. At that point I call the methods that were specified in the command.
It seems that for some reason my ARGV is not being returned. I'm not sure what the issue is.
Ruby's OptionParser is easy to use, but you have to puzzle through the documentation. Here's a little example that'd be useful for your code:
require 'optparse'
options = {}
OptionParser.new do |opt|
opt.on('-u', '--use_this FILE', 'Use this file') { |o| options[:use_this] = o }
end.parse!
options will contain the flags. In this case, if you pass in -u foo, options[:use_this] will be foo.
Save that and try running it without and with a parameter. Also try running it with just a -h flag.
You can search StackOverflow for more answers where I was dealing with OptionParser.
It's hard to tell what's wrong since you code doesn't seem to be working at the moment. The problem may be that the parse! method removes found options from ARGV. So when you write:
optparser.parse!
It removes your two parameters (-u testlog.txt) and this code always fails:
if ARGV.length < 1
puts "Argument required"
exit
end
Instead of looking at ARGV, you need to set up optparser correctly. Perhaps something like:
optparser = OptionParser.new do |opts|
opts.banner = "apacheReport.rb [options] filename"
opts.on("-u", "--u-short-for-this", "Whatever u stands for") do |u|
optU = u
end
end
Then optU will be true only if the user passed -u and the filename will be in ARGV[0].

How do you use OptionParser to open a file with command-line options?

I'm getting this error when trying to print line from a file via command-line options
no implicit conversion of true into String (TypeError)
from threat_detection.rb:64:in 'new'
from threat_detection.rb:64:in '<main>'
If I use the file name instead of options[:config_file] it prints the lines of the file, as expected.
if options[:config_file]
File.new(options[:config_file], 'r').each { |params| puts params }
end
if options[:host_file]
File.new(options[:host_file], 'r').each { |host| puts host }
end
Looks like you are trying to use the OptionParser class in Ruby. Since it is not a part of the core library, make make sure to include the following at the top of your program:
require 'optparse'
Furthermore, make sure you are creating your options correctly:
options = {}
optparse = OptionParser.new do |opts|
options[:config_file] = nil
opts.on('-f', '--config-file', 'Enter the config file to open.') do
options[:dry_run] = true
end
end
optparse.parse!
When using a flag in command line, you are essentially setting a variable to true or false. For example, by default, -v (verbose) is set to false for most operations (like rm). Following the command and its optional flags are (sometimes required) command-line arguments, which is the filename in your case.
Calling your script should look similar to
$ ruby ./my_program.rb --config-file /path/to/some/file
^ ^ ^
program flag argument
As you saw, an optparse option must be a boolean. You only want to open a file if the flag is present (option is true). Just a slight change is needed for your program to run:
if options[:config_file]
File.new(ARGV[0], 'r').each { |params| puts params }
end
ARGV is an array of all command-line arguments to your script (following flags). If you only include one argument, you will want the first element, or index 0 (ARGV[0]). All arguments are separated by spaces. So if you implement the same technique for options[:host_file], you can use ARGV[1].

OptionParser to parse arguments form file instead of command line

I am using Ruby to execute a code that takes command line arguments.
now i trying to use the same program with differnt options so i am putting the options in a file and i want the program to read each line interpret the options and execute the program accordingly.
but i get this error. "C:/Ruby193/lib/ruby/1.9.1/optparse.rb:1348:in block in parse_in_order': undefined methodshift' for "--c execue --query unix --Servername abc123":String (NoMethodError)"
i understand that its reading the file and treating the line as string. but wondering if there is a way to overcome this shift error and treat the line as if it was entered in command prompt. or any better solution.
here is my code.
require 'optparse'
require 'micro-optparse'
# --command execue --query unix command --Servername abc123
f =File.open("list_of_commands.txt", "r")
f.each_line { |line|
line= line.chomp
#line = "--c execue --query unix --Servername abc123"
#line = eval("\"#{line}\"")
puts line
options = {}
OptionParser.new do |opts|
opts.on("-c", "--command result,execue,chart,scpfile", String, "Single command to execute ") do |c|
options[:comd] = c
end
opts.on("-q", "--query remote command, unix command", String, "performs the command on local or remote machine") do |q|
options[:query] = q
end
opts.on("-s", "--servername CHSXEDWDC002 ", String, "server name to execute the command") do |v|
options[:hname] = v
end
opts.on_tail('-h', '--help', 'Show this message') do
puts opts
exit
end
end.parse!(line)
p options
}
the contents of the file are below
--c execue --query unix --Servername abc123
i also tried to use micro-optparse but facing same error. any workaround ?
Update:
as per suggestion from "#mu is too short" i tried below options.
end.parse!("#{Shellwords.shellsplit(line)}") and/or
end.parse!(Shellwords.shellsplit(line)).
but none of them worked.
i also tried to split the line as array using "line = line.split("\t")" and then
end.parse!(line). out put as
--c execue
--query unix
--Servername abc123
but now i get error as block in : invalid option --c execute
Update:#2
looking at the error, the issue is with the wrong parameter(-c. but thanks to user "#mu is too short" for suggesting to use Array.
Update: 3
passing the array only worked for short form of the arguments such as -c but when long form was supplied it failed with invalid argument erorr.
i dont see much documentation on the optparse. i even tried micro-parse but it requres default valuves and its not an option for me :(
The parse! method wants an array as its argument, not a string. You'll probable want to use Shellwords.shellsplit rather than String#split (or similar hand-rolled method) to convert your line to an array just in case you have to deal with quoting and whatnot. Something like this:
OptionParser.new do |opts|
#...
end.parse!(Shellwords.shellsplit(line))
While you can put your command-line arguments into a file, flags and all, there are better ways to remember configuration settings.
Instead of storing the flags, use a YAML file. YAML is a great data format, that translates easily to Ruby hashes and objects. "Yaml Cookbook" is a very useful page for learning the ins and outs of the format with Ruby. There are YAML parsers for a myriad other languages, making it easy to share the settings, which can be useful as a system grows.
With a little creative code you can use your YAML as the base settings, and let your CLI flags override the stored settings.
If you're not familiar with YAML, it's easy to get a start on the file using something like:
require 'yaml'
data = {
'command' => %w[result execute chart scpfile],
'query' => ['remote command', 'unix command'],
'servername' => 'CHSXEDWHDC002',
}
puts data.to_yaml
Which outputs:
---
command:
- result
- execute
- chart
- scpfile
query:
- remote command
- unix command
servername: CHSXEDWHDC002
Redirect that output to a file ending in .yaml and you're on your way.
To read it back into a script use:
require 'yaml'
data = YAML.load_file('path/to/data.yaml')
A quick round-trip test shows:
require 'yaml'
data = {
'command' => %w[result execute chart scpfile],
'query' => ['remote command', 'unix command'],
'servername' => 'CHSXEDWHDC002',
}
YAML.load(data.to_yaml)
Which looks like:
{"command"=>["result", "execute", "chart", "scpfile"],
"query"=>["remote command", "unix command"],
"servername"=>"CHSXEDWHDC002"}
If you want to have defaults, stored in the YAML file, and override them with command-line flags, read the data from the file then use that resulting object as the base for OptionParse:
require 'optparse'
require 'yaml'
# Note, YAML can deal with symbols as keys, but other languages might not like them.
options = {
:comd => %w[result execute chart scpfile],
:query => ['remote command', 'unix command'],
:hname => 'CHSXEDWHDC002',
}
# we'll overwrite the options variable to pretend we loaded it from a file.
options = YAML.load(options.to_yaml)
OptionParser.new do |opts|
opts.on("-c", "--Command result,execue,chart,scpfile", String, "Single command to execute ") do |c|
options[:comd] = c
end
opts.on("-q", "--query remote command, unix command", String, "performs the command on local or remote machine") do |q|
options[:query] = q
end
opts.on("-s", "--Servername CHSXEDWHDC002 ", String, "server name to execute the command") do |v|
options[:hname] = v
end
opts.on_tail('-h', '--help', 'Show this message') do
puts opts
exit
end
end.parse!
That's not tested, but we do similar things at work all the time, so save it to a file and poke at it with a stick for a while, and see what you come up with.

Extracting filenames from command line arguments with Ruby

I'm trying to use optparse to parse command line arguments. I would like my program to accept arguments like that:
$ ./myscript.rb [options] filename
I can easily manage the [options] part:
require 'optparse'
options = { :verbose => false, :type => :html }
opts = OptionParser.new do |opts|
opts.on('-v', '--verbose') do
options[:verbose] = true
end
opts.on('-t', '--type', [:html, :css]) do |type|
options[:type] = type
end
end
opts.parse!(ARGV)
But how do I get the filename?
I could extract it manually from ARGV, but there has to be a better solution, just can't figure out how
The "parse" method returns the unprocessed ARGV. So in your example, it will return a one element array with the filename in it.
I can't just use ARGV.pop. For example
when the last argument is "css" it
could either be a file or belong to
--type switch.
But if your script requires the last argument to be a filename (which is what your usage output inquires) this case should never happen the script should exit with a non-zero and the user should get a usage report or error.
Now if you want to make a default filename or not require a filename as the last argument but leave it optional then you could just test to see if the last argument is a valid file. If so use it as expected otherwise continue without etc.
Hope this answer can still be useful.
Ruby has one built-in variable __FILE__ can do this type of work.
puts __FILE__
it will print out your file's name.
I don't think extracting it before sending it to OptionParser is bad, I think it makes sense. I probably say this because I have never used OptionParser before, but oh well.
require 'optparse'
file = ARGV.pop
opts = OptionParser.new do |opts|
# ...
end

Resources