Passing Arguments to Scripts

Passing Arguments to Scripts#


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


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'
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)
if verbose
  puts "Files #{ARGV.join(', ')} contains #{ARGF.readlines.count} lines."
  puts "#{ARGF.readlines.count}"

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

$ ./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} 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
  opts.on("-l", "--lines", "Count number of lines") do |l|
    options[:lines] = l

files = ARGV.join(', ') # storing, beacuse* clears the ARGV

# ARGV now contains no options, only file
if options[:lines]
  number = "#{ARGF.readlines.count} lines"
  number = "#{ARGF.each_byte.count} bytes"

if options[:verbose]
  puts "Files #{files} contains #{number}."
  puts number

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

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