Parsing Command-Line Arguments
Problem
You want to make your Ruby script take command-line arguments, the way most Unix utilities and scripts do.
Solution
If you want to treat your command-line arguments as a simple list of strings, you can just iterate over the ARGV array.
Heres a Ruby version of the Unix command cat; it takes a list of files on the command line, opens each one, and prints its contents to standard output:
#!/usr/bin/ruby -w # cat.rb ARGV.each { |filename| IO.readlines(filename).each { |line| puts line } }
If you want to treat your command-line arguments as a list of files, and you plan to open each of those files and iterate over them line by line, you can use ARGF instead of eARGV. The following cat implementation is equivalent to the first one.[3]
[3] Its actually a little better, because ARGF will iterate over standard input if there are no files given in ARGV.
#!/usr/bin/ruby -w # cat_argf.rb ARGF.each { |line| puts line }
If you want to treat certain command-line arguments as switches, or as anything other than a homogenous list of strings, use the OptionParser class in the optparse library. Don write the argument parsing code yourself; there are too many edge cases to think about.
Discussion
The OptionParser class can parse any command-line arguments you e likely to need, and it includes a lot of Unix know-how that would take a long time to write yourself. All you have to do is define the set of arguments your script accepts, and write code that reacts to the presence of each argument on the command line. Here, Ill use OptionParser to write cat2.rb, a second Ruby version of cat that supports a few of the real cats command-line arguments.
The first phase is turning any command-line arguments into a data structure that I can easily consult during the actual program. The CatArguments class defined below is a hash that uses OptionParser to populate itself from a list of command-line arguments.
For each argument accepted by cat2.rb, Ive added a code block to be run as a callback. When OptionParser sees a particular argument in ARGV, it runs the corresponding code block, which sets an appropriate value in the hash:
#!/usr/bin/ruby # cat2.rb require optparse class CatArguments < Hash def initialize(args) super() self[:show_ends] = \ opts = OptionParser.new do |opts| opts.banner = "Usage: #$0 [options]" opts.on(-E, --show-ends [STRING], display [STRING] at end of each line) do |string| self[:show_ends] = string || $ end opts.on(-n, --number, umber all output lines) do self[:number_lines] = true end opts.on_tail(-h, --help, display this help and exit) do puts opts exit end end opts.parse!(args) end end arguments = CatArguments.new(ARGV)
At this point in the code, our CatArguments object contains information about which command-line arguments were passed in. If the user passed in a command-line switch -E or --show-ends, then arguments[:show_ends] contains a string to be shown at the end of each line.
Whats more, the command-line arguments handled by OptionParser have been stripped from ARGV. The only things left in ARGV can be assumed to be the names of files the user wants to concatenate. This means we can now use the ARGF shortcut to iterate over those files line by line. All we need is a little extra code to actually implement the command-line arguments:
counter = 0 eol = ARGF.each do |line| line.sub!(/$/, arguments[:show_ends]) print \%6.d % (counter += 1) if arguments[:number_lines] print line end
Heres a shell session showing off the robustness that optparse brings to even a simple script. The help message is automatically generated, multiple combined flags are handled correctly, nonexistent flags are rejected, and you can disable flag processing altogether with the -- argument. In general, it works like you expect a Unix command-line tool to work.
$ ./cat2.rb --help Usage: ./cat2.rb [options] -E, --show-ends [STRING] display STRING at end of each line -n, --number number all output lines -h, --help display this help and exit $ ./cat2.rb file1 file2 This is file one. Another line in file one. This is file two. Im a lot more interesting than file one, Ill tell you that! $ ./cat2.rb file1 -E$ -n file2 1 This is file one.$ 2 Another line in file one.$ 3 This is file two.$ 4 Im a lot more interesting than file one, Ill tell you that!$ $ ./cat2.rb --nosuchargument /usr/lib/ruby/1.8/optparse.rb:1445:in `complete: invalid option: --nosuchargument (OptionParser::InvalidOption) $ ./cat2.rb --show-ends=" STOP" -- --argument-looking-file The name of this file STOP looks just like an argument STOP for some odd reason. STOP
With a little more work, you can make OptionParser validate argument data for youparse strings as numbers, restrict option values to values from a list. The documentation for the OptionParser class has a much more complex example that shows off these advanced features.
See Also
- ri OptionParser
Категории