I have a Ruby app with a relatively broad set of command-line arguments. I would like to suppress "short" variants for a number of options so that they can only be used in the long ("double dash") form.
Can I somehow suppress short dash variants for some options?
UPDATE 2013/10/08
It turned out that indeed omitting the short variant works! However, for me it didn't because for some reason in my program all the short keys were prefixed with a space. So a simple case like this:
require 'optparse'
op = OptionParser.new
op.on(" -f", "--from FORMAT", "Use the specific format") {}
op.on("--flip", "Do a flip") {}
op.parse!
caused the exception:
ruby why.rb -f some-foos
why.rb:17:in `<main>': ambiguous option: -f (OptionParser::AmbiguousOption
while the advice given (note the lack of space after the opening quote):
require 'optparse'
OptionParser.new do |opts|
opts.on("-d", "--ding DING", "Should not conflict with dangerous-option") do
puts "ding set!"
end
opts.on("--dangerous-option", "Set dangerous option") do |v|
puts "dangerous option set to #{v}"
end
end.parse!
works fine.
$ruby dang.rb -d xyz
ding set!
So thanks p11y for pointing me in the right direction with a working example. Also, if this "leading space" is in place, optparse will not complain - but it will change the interpretation of your short keys (or, better to say, will ignore them and show them as part of your help line! - and still use the auto-generated keys instead).
Just drop the short form:
require 'optparse'
OptionParser.new do |opts|
opts.on("--dangerous-option", "Set dangerous option") do |v|
puts "dangerous option set to #{v}"
end
end.parse!
$ ruby foo.rb -h
Usage: foo [options]
--dangerous-option Set dangerous option
$ ruby foo.rb --dangerous-option
dangerous option set to true
Related
I am writing a script with different options in ruby, and I can't understand how the OptionParser could help me.
In particular, there is an example in the documentation: https://docs.ruby-lang.org/en/2.1.0/OptionParser.html
require 'optparse'
options = {}
OptionParser.new do |opts|
opts.banner = "Usage: example.rb [options]"
opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
options[:verbose] = v
end
end.parse!
p options
p ARGV
I can understand the exclamation mark on the "end.parse" line (but I expected a parameter after that), but I find the following 2 lines confusing, p hasn't been declared, and I can't understand if it's part of the example source.
And how do I use the '-v' option? Do I simply check if options[:v] is nil or true?
Last thing, what happens to the other options? Does the OptionParser only parse switches? What if I had other parameters after the '-v'? Like myscript -v duck ketchup banana?
To answer your questions:
I can understand the exclamation mark on the "end.parse" line (but I expected a parameter after that)
The documentation states that parse! takes an optional argv parameter. If it is not supplied, it defaults to default_argv, which I imagine is the string containing the arguments passed to this script in the command line.
p hasn't been declared, and I can't understand if it's part of the example source
p is defined in Kernel, so it is (almost) always available in Ruby. p obj is equivalent to puts obj.inspect.
In this context, p is just used to illustrate that after parsing the arguments, the options hash contains all the flags/options you defined in the OptionParser block.
And how do I use the '-v' option? Do I simply check if options[:v] is nil or true?
Yes, but that would actually be options[:verbose].
Last thing, what happens to the other options? Does the OptionParser only parse switches? What if I had other parameters after the '-v'? Like myscript -v duck ketchup banana?
You will have to make multiple calls to opts.on to match all the other switches/arguments you are interested in. Look at the documentation here for explanations on how to do that.
I have a ruby cli parsing script and it seems that there is some sort of regex behavior for parsing options:
op = OptionParser.new do |x|
x.on("--output-config PATH", "The filesystem location for the output config file") do |output_config|
options[:output_config] = output_config
end
x.on("-j", "--json", "If this is set, then json is output instead of tabular form") do
options[:disp_json] = true
end
x.on("-h", "--help", "Show this message") do
puts op
exit 0
end
x.on("-v", "--version", "Show version") do
puts "version #{VERSION_NUMBER}"
exit 0
end
end
# do input validation and check leftovers for proper commands
begin
# parse options, parse! removes elements from ARGV so leftovers are positional arg(s)
op.parse!(ARGV)
options[:config_file] = ARGV[0] if ARGV[0]
rescue OptionParser::InvalidOption, OptionParser::MissingArgument
puts "############### #{$!.to_s} ###############"
puts ""
puts op
exit 1
end
Then if I call it as such:
script -a
It outputs the following (expected behavior)
############### invalid option: -a ###############
Or
script --output-config
It outputs the following (expected behavior)
############### missing argument: --output-config ###############
So this is where it gets odd:
script --output
It outputs the following (not expected behavior)
############### missing argument: --output ###############
Or
script --ou
It outputs the following (not expected behavior)
############### missing argument: --ou ###############
Basically anything you pass that regex matches "output-config" is passed to the block for
x.on("--output-config PATH"....
Which is the cause of the MissingArgument vs InvalidOption behavior I'm seeing.
Am I using optparse wrong or is this a bug in the library?
####### EDIT
If I add another x.on:
x.on("--out PATH", "The filesystem location for the output config file") do |output_config|
options[:output_config2] = output_config
end
And pass either -o (one dash, ie a short form) or --o (two dashes), it does not throw an exception (I specifically am rescuing OptionParser::AmbiguousOption at the handling is not executed). Instead is executes the shortest match, which is --out. If I pass --outp, then the longer one is executed. This seems flaky to me.
####### EDIT 2
> ./my_app --output-c
############### missing argument: --output-c
The MissingArgument exception only displays the flag as passed, not as 'intended'. It clearly knows that it is matching against '--output-config' so I'd like to be able to know that so that my error message to the user is clear and explicit. Is there a way I can determine what optparser was matching against at the time that the MissingArgument exception was raised?
This is standard behavior with long options. I'm having trouble finding any supporting documentation but I've been using this feature of long options as long as I've been using long options (i.e. since a long long time ago).
You can see it easily with the GNU version of ls(1):
$ ls --h
ls: option '--h' is ambiguous
Try `ls --help' for more information.
$ ls --he
Usage: ls [OPTION]... [FILE]...
List information about the FILEs (the current directory by default).
Sort entries alphabetically if none of -cftuvSUX nor --sort.
...
$ ls --help
Usage: ls [OPTION]... [FILE]...
List information about the FILEs (the current directory by default).
Sort entries alphabetically if none of -cftuvSUX nor --sort.
...
There are multiple options that begin with --h so --h is an options error, there is only one option that starts with --he so --he is the same as --help.
If you add an --output-pancakes option, then you should get a complaint about ambiguity if you say --output but --output-c and --output-p will work.
You're not using the library incorrectly. Nor is this a bug. This is a feature.
I have a task of writing a simple Ruby script which would do the following.
Upon execution from the UNIX command line, it would present the user with a prompt at which he should be able to run certain commands, like "dir", "help" or "exit". Upon "exit" the user should return to the Unix shell.
I'm not asking for the solution; I would just like to know how this "shell" functionality can be implemented in Ruby. How do you present the user with a prompt and interpret commands.
I do not need a CLI script that takes arguments. I need something that creates a shell interface.
The type of program you require can easily be made with just a few simple constructs.
I know you're not asking for a solution, but I'll just give you a skeleton to start off and play around with:
#!/usr/bin/env ruby
def prnthelp
puts "Hello sir, what would you like to do?"
puts "1: dir"
puts "2: exit"
end
def loop
prnthelp
case gets.chomp.to_i
when 1 then puts "you chose dir!"
when 2 then puts "you chose exit!"
exit
end
loop
end
loop
Anyways, this is a simplistic example on how you could do it, but probably the book recommended in the comments is better. But this is just to get you off.
Some commands to get you started are:
somevar = gets
This gets user input. Maybe learn about some string methods to manipulate this input can do you some good. http://ruby-doc.org/core-2.0/String.html
chomp will chop off any whitespace, and to_i converts it to an integer.
Some commands to do Unix stuff:
system('ls -la') #=> outputs the output of that command
exit #=> exits the program
Anyways, if you want this kind of stuff, I think it's not a bad idea to look into http://www.codecademy.com/ basically they teach you Ruby by writing small scripts such as these. However, they maybe not be completely adapted to Unix commands, but user input and the likes are certainly handled.
Edit:
As pointed out do use this at the top of your script:
#!/usr/bin/env ruby
Edit:
Example of chomp vs. chop:
full_name = "My Name is Ravikanth\r\n"
full_name.chop! # => "My Name is Ravikanth"
Now if you run chop and there are no newline characters:
puts full_name #=> "My Name is Ravikanth"
full_name.chop! #=> "My Name is Ravikant"
versus:
puts full_name #=> "My Name is Ravikanth\r\n"
full_name.chomp! #=> "My Name is Ravikanth"
full_name.chomp! #=> "My Name is Ravikanth"
See: "Ruby Chop vs Chomp"
Here's a really basic loop:
#!/user/bin/ruby
#
while true do
print "$ "
$stdout.flush
inputs = gets.strip
puts "got your input: #{inputs}"
# Check for termination, like if they type in 'exit' or whatever...
# Run "system" on inputs like 'dir' or whatever...
end
As Stefan mentioned in a comment, this is a huge topic and there are scenarios that will make this complicated. This is, as I say, a very basic example.
Adding to the two other (valid) answers posted so far be wary of using #!/usr/bin/ruby, because ruby isn't always installed there. You can use this instead:
#!/usr/bin/env ruby
Or if you want warnings:
#!/usr/bin/env ruby -w
That way, your script will work irrespective of differences where ruby might be installed on your server and your laptop.
Edit: also, be sure to look into Thor and Rake.
http://whatisthor.com
http://rake.rubyforge.org
Use irb.
I was looking into an alternative to bash and was thinking along the same lines... but ended up choosing fish: http://fishshell.com/
Nonetheless, I was thinking of using irb and going along the lines of irbtools: https://github.com/janlelis/irbtools
Example:
> irb
Welcome to IRB. You are using ruby 1.9.3p0 (2011-10-30 revision 33570) [x86_64-linux]. Have fun ;)
>> ls #=> ["bin", "share", "opt", "lib", "var", "etc", "src"]
>>
In any case, irb is the ruby shell.
Take a look at cliqr which comes with inbuilt support for build a custom shell https://github.com/anshulverma/cliqr/
I'm working on implementing Project Euler solutions as semantic Ruby one-liners. It would be extremely useful if I could coerce Ruby to automatically puts the value of the last expression. Is there a way to do this? For example:
#!/usr/bin/env ruby -Ilib -rrubygems -reuler
1.upto(100).into {|n| (n.sum.squared - n.map(&:squared).sum)
I realize I can simply puts the line, but for other reasons (I plan to eval the file in tests, to compare against the expected output) I would like to avoid an explicit puts. Also, it allots me an extra four characters for the solution. :)
Is there anything I can do?
You might try running it under irb instead of directly under a Ruby interpreter.
It seems like the options -f --noprompt --noverbose might be suitable (.
#!/usr/bin/env irb -f --noprompt --noverbose -Ilib -rrubygems -reuler
'put your one-liner here'
The options have these meanings:
-f: do not use .irbrc (or IRBRC)
--noverbose: do not display the source lines
--noprompt: do not prefix the output (e.g. with =>)
result = calculate_result
puts result if File.exist?(__FILE__)
result of eval is last executed operation just like any other code block in ruby
is doing
puts eval(file_contents)
an option for you?
EDIT
you can make use of eval's second parameter which is variables binding
try the following:
do_not_puts = true
eval(file_contents, binding)
and in the file:
....
result = final_result
if defined?(do_not_puts)
result
else
puts(result)
end
Is it an option to change the way you run scripts?
script.rb:
$_= 1.upto(100).into {|n| (n.sum.squared - n.map(&:squared).sum)
invoke with
echo nil.txt | /usr/bin/env/ruby -Ilib -rrubygems -reuler -p script.rb, where nil.txt is a file with a single newline.
Often I find myself doing the following:
print "Input text: "
input = gets.strip
Is there a graceful way to do this in one line? Something like:
puts "Input text: #{input = gets.strip}"
The problem with this is that it waits for the input before displaying the prompt. Any ideas?
I think going with something like what Marc-Andre suggested is going to be the way to go, but why bring in a whole ton of code when you can just define a two line function at the top of whatever script you're going to use:
def prompt(*args)
print(*args)
gets
end
name = prompt "Input name: "
Check out highline:
require "highline/import"
input = ask "Input text: "
One liner hack sure. Graceful...well not exactly.
input = [(print 'Name: '), gets.rstrip][1]
I know this question is old, but I though I'd show what I use as my standard method for getting input.
require 'readline'
def input(prompt="", newline=false)
prompt += "\n" if newline
Readline.readline(prompt, true).squeeze(" ").strip
end
This is really nice because if the user adds weird spaces at the end or in the beginning, it'll remove those, and it keeps a history of what they entered in the past (Change the true to false to not have it do that.). And, if ARGV is not empty, then gets will try to read from a file in ARGV, instead of getting input. Plus, Readline is part of the Ruby standard library so you don't have to install any gems. Also, you can't move your cursor when using gets, but you can with Readline.
And, I know the method isn't one line, but it is when you call it
name = input "What is your name? "
Following #Bryn's lead:
def prompt(default, *args)
print(*args)
result = gets.strip
return result.empty? ? default : result
end
The problem with your proposed solution is that the string to be printed can't be built until the input is read, stripped, and assigned. You could separate each line with a semicolon:
$ ruby -e 'print "Input text: "; input=gets.strip; puts input'
Input text: foo
foo
I found the Inquirer gem by chance and I really like it, I find it way more neat and easy to use than Highline, though it lacks of input validation by its own.
Your example can be written like this
require 'inquirer'
inputs = Ask.input 'Input text'