Per Erik Strandberg /cv /kurser /blog

There is an excellent class for dealing with the boring task of treating command line arguments: OptionParser [1].

Following the tutorial made me do the following silly little program to test its features.

Features of Option Parser

The class can "automatically" generate text like options that are available:

>./parse_args --help
[...]
Options:
  --version             show program's version number and exit
  -h, --help            show this help message and exit
  -f FILE, --file=FILE  FILE is name of infile (repeated use is ok).
  -o OUT, --out=OUT     OUT is name of outfile (for non-default names)
  -n NUM                Number of items from FILE to OUT to process.
  -v                    Enable printing of status messages to stdout.
  -q                    Disable printing of status messages to stdout.

Also a number of boring special cases and errors are dealt with automagically:

>./parse_args -f
[...]
parse_args: error: -f option requires an argument

While testing it I also found some limitations, it seems non-trivial to use one switch and then apply a bunch of arguments (without knowing how many) after it. (The documentation shows an example that "should" work but that does not.)

What I would like is something like this:

>./parse_args -f a.txt b.txt c.txt


Where a.txt, b.txt and c.txt are stored as infiles. I had to make an ugly hack to make it work.

Concrete example of the Python Option Parser

Below is my silly little program. It copies all or some ("-n 10" for ten) lines in all infiles and write them to an outfile. The switch -v enable verbosity and -q disables verbosity. Again the documentation is not perfect as I see it - I'd like an exception to be thrown when combining -v and -q but I did not manage to do that at the same time as -v and -q touch the same variable.

#!/usr/bin/python                                                               

from optparse import OptionParser

usage = """Usage:    %prog [options]                                            
          or                                                                    
          %prog [filenames] [options]                                           
                                                                                
Defaults: OUT = 'out.txt'\n          -q is on (-v is thus off)                  
                                                                                
Note:     All anonymous arguments will be treated as filenames                  
                                                                                
Examples: %prog in1.txt in2.txt                                                 
          in1.txt and in2.txt wille be treated and stored in a.out              
                                                                                
          %prog -f in.txt -n 10 -o out.txt                                      
          The first 10 items in in.txt will be processed into out.txt           
"""

parser = OptionParser(usage=usage, version="%prog 0.1")
parser.add_option('-f', '--file', dest='infilenames', action='append',
                  help='FILE is name of infile (repeated use is ok).',
                  metavar='FILE')
parser.add_option('-o', '--out', dest='outfilename',
                  help='OUT is name of outfile (for non-default names)',
                  metavar='OUT')
parser.add_option('-n', dest='num', type='int',
                  help='Number of items from FILE to OUT to process.')
parser.add_option('-v', dest='verbose', action='store_true',
                  help='Enable printing of status messages to stdout.')
parser.add_option('-q', dest='verbose', action='store_false', default=False,
                  help='Disable printing of status messages to stdout.')

(options, args) = parser.parse_args()

if args and not options.infilenames:
    options.infilenames = list()

# anonymous arguments must be filenames                                         
while args:
    item = args.pop(0)
    options.infilenames.append(item)

# open outfile                                                                  
if options.outfilename:
    o = open(options.outfilename, 'w')
else:
    o = open('out.txt', 'w')

# deal with infiles                                                             
for item in options.infilenames:
    if options.verbose:
        print "  dealing with %s" % item

    ctr = 0
    f = file(item,'r')

    for line in f:
        # if -n break after a certain number of lines                           
        if options.num != None and ctr == options.num:
            break
        ctr += 1
        o.write(line)
    f.close()

    if options.verbose:
        print "  copied %d lines from %s to %s" % (ctr, f.name, o.name)

o.close()

Conclusions

I am in love - I will never again use some ugly hack to fix command line options (I have done a number of such ugly hack...) !


This page belongs in Kategori Programmering.