Passing Arguments to Scripts#
ARGV#
We already know ARGV
from the previous chapters: it is an Array with all the arguments as strings. The rules are exactly the same as in the shell scripting, for example a string with the space must be between apostrophe.
$ ruby -e 'ARGV.each {|x| puts "#{x}, #{x.class}"}' 1 text 'with space'
1, String
text, String
with space, String
ARGF#
It is very common to pass the file names as the script arguments. ARGF
provides very handy shortcut for such scripts - it is an I/O stream with the contents of all files passed in arguments. You can use it as a normal file object, so read
, readline
etc. It is important to remember that ARGF
combines all the names given as atributes; for example, giving the same file twice would will cause double output:
$ ruby -e 'puts ARGF.readlines' hello_world.rb hello_world.rb
#!/usr/bin/env ruby
puts "Hello, " + ARGV.first + "."
#!/usr/bin/env ruby
puts "Hello, " + ARGV.first + "."
If there is no arguments given, ARGF
is an I/O stream reading from the stdin
. It is quite useful when you are writing a small script, and you want to allow user to decide if to pass a file name or to pass something to the scripts stdin
.
$ echo "here be dragons" | ruby -e 'puts ARGF.read'
here be dragons
What about if we do not want to pass only the files to the script (some switches like -rf
)? It is good to know, that ARGF
reads all the files which are not in the ARGV
array. So, for all non-file arguments, just remove them from the array.
Another interesting property of ARGF
is that it removes the file names from the ARGV
after read it.
The script below is a small utility to count number of lines in a given files. When the first argument begins with a dash, it is removed from the ARGV
array using Array.shift
method. Then the scripts outputs the number of lines in all files combined.
#!/usr/bin/env ruby
unless ARGV.empty?
if ARGV.first.start_with?("-")
case ARGV.shift # shift takes the first argument and removes it from the array
when '-v', '--verbose'
verbose = true
when '--version'
puts "1.0"
exit 0 # exit script with status 0 (all OK)
end
end
end
if verbose
puts "Files #{ARGV.join(', ')} contains #{ARGF.readlines.count} lines."
else
puts "#{ARGF.readlines.count}"
end
Even being so simple, the script above is quite universal. It takes the files as an arguments or - if no arguments given - counts the number of lines in stdin
:
$ ./wc.rb /etc/hosts /etc/passwd
236
$ ./wc.rb --verbose /etc/hosts /etc/passwd
Files /etc/hosts, /etc/passwd contains 236 lines.
$ cat /etc/passwd | ./wc.rb --verbose
Files contains 86 lines.
OptionParser class#
Parsing the arguments can be tricky and complex issue, especially with the big, complicated scripts. But, as usual, there is a helper class for such operations, it is called OptionParser
. It allows to build standard Unix interface to command line scripts:
#!/usr/bin/env ruby
require 'optparse'
# default options:
options = {lines: false, verbose: false}
OptionParser.new do |opts|
# banner and separator are the usage description showed with '--help' or '-h'
opts.banner = "Usage: wc.rb [options] [files]"
opts.separator "Reads number of bytes or lines in the files"
opts.separator "Options:"
# options (switch - true/false)
opts.on("-v", "--verbose", "Verbose mode") do |v|
options[:verbose] = v
end
opts.on("-l", "--lines", "Count number of lines") do |l|
options[:lines] = l
end
end.parse!
files = ARGV.join(', ') # storing, beacuse ARGF.read* clears the ARGV
# ARGV now contains no options, only file
if options[:lines]
number = "#{ARGF.readlines.count} lines"
else
number = "#{ARGF.each_byte.count} bytes"
end
if options[:verbose]
puts "Files #{files} contains #{number}."
else
puts number
end
Script equipped with OptionParse
shows the banner
, separator
and the short description of every option when running with –help
or -h
switch. Parser on(short, long, description) {block}
method is used to define the switches and options. It passes the value of the option to the block; in the example above we have only switches (true/false), but you can pass the strings to the options by using –option STRING
syntax in the second argument:
opts.on("-f", "--file FILE", "File to process") do |f|
options[:file] = f # f becames a filename given after -f or --file
end
There is more interesting options to construct switches: you can pass the list of arguments, define the argument type (other than default String). Please refer to the documentation: ri OptionParser
.
Let’s now take a look how the OptionParser works:
$ ./wc.rb --help
Usage: wc.rb [options] [files]
Reads number of bytes or lines in the files
Options:
-v, --verbose Verbose mode
-l, --lines Count number of lines
$ ./wc.rb /etc/passwd /etc/hosts
9026 bytes
$ ./wc.rb -l /etc/passwd /etc/hosts
236 lines
$ ./wc.rb --lines --verbose /etc/passwd /etc/hosts
Files /etc/passwd, /etc/hosts contains 236 lines.
$ ./wc.rb -lv /etc/passwd /etc/hosts
Files /etc/passwd, /etc/hosts contains 236 lines.