Python Command Line Script Template

Despite having used Python as my scripting language of choice for several years, I still find myself stumbling across the odd ‘hidden gem’ when searching through the ‘global module index’, which is part of the documentation that comes with the language.

A fine example of this cropped up recently when I was writing some command line utilities to help with a particular project. I like to make these things as user-friendly as possible, mainly so that if someone else has to use them, they can do so without me having to write reams of documentation explaining how each one works. I also have an intense dislike for the process of re-inventing wheels, it therefore made sense to come up with some boilerplate code that I could use for each script which covered just the basics of getting command line options, providing a help feature and allowing for debug messages to be added for diagnostic purposes when testing the code.

Python comes with several modules which deal with command line option processing, each of which has its own strengths and weaknesses. The code template shown here uses the optparse module, which is able to provide all of the features that I wanted to have in my scripts, without making me re-code everything by hand for each one. I wanted each script to:

  • Provide online help for available command line options, using the accepted convention of running the script with a ‘-h’ or ‘–help’ argument.
  • Support a ‘-v’ or ‘–verbose’ argument so that there is the option to provide more detailed output when the script is run.
  • Support the use of a ‘–dry-run’ option so that a script can show what it would do, but not actually make any changes to input data or files
  • Have predefined functions for displaying status and/or debug messages

The resulting code template runs to a rather svelte 120 lines. Of these, 19 lines are comments and 24 are blank lines inserted for readability. The code below also has some extra examples of how you might, for example, specify an input file to be processed, or restrict processing to the first few lines of input data only.

You can either copy and paste the code below, or scroll down for a download link:

# Skeleton script with command line parsing
# vim: ai et sw=4 ts=4
# Copyright (C)2012 Phil Edwards <phil 'at'>
# License: This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or (at your
# option) any later version. This program is distributed in the hope that it
# will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
# Public License for more details.
# Ver    Date       Who  Changes
# -----  ---------  ---  ---------------------------------------------------
# 1.0    21SEP2012  PKE  First version

import os
import sys
import time
from optparse import OptionParser

class RunTimeOptions:
    def __init__(self):
        self.__me__ = os.path.basename(sys.argv[0])
        self.__version__ = '1.0'
        self.__copyright__ = "Copyright (C)2012 Phil Edwards <phil 'at'> All Rights Reserved"
        self.__summary__ = 'Awesome Python script written by Phil'

        self.logMsg(txt = '%s v%s %s' % (self.__me__, self.__version__, self.__summary__))
        self.logMsg(txt = 'command line arguments: [%s]' % ' '.join(sys.argv[1:]))

    def logMsg(self, **kwargs):
        txt = kwargs.get('txt', '')
        dateStamp = kwargs.get('dateStamp', True)
        msgLine = ''

        if txt != '':
            if dateStamp: msgLine += '[' + time.strftime("%Y%m%d-%H%M%S", time.localtime()) + '] '
            if not self.doUpdate: msgLine += 'NOUPDATE: '
            msgLine += txt
            print msgLine

    def debugMsg(self, **kwargs):
        txt = kwargs.get('txt', '')
        dateStamp = kwargs.get('dateStamp', True)
        pause = kwargs.get('pause', False)
        msgLine = ''

        if self.debugMode and txt != '':
            if dateStamp: msgLine += '[' + time.strftime("%Y%m%d-%H%M%S", time.localtime()) + '] '
            msgLine += 'DEBUG: ' + txt
            if pause:
                bull = raw_input(msgLine + ': Press any key to continue : ')
                print msgLine

    def getCommandLineOptions(self):
        vString = '%%prog (%s) v%s' % (self.__summary__, self.__version__)

        dString = 'A super awesome Python script. Phil wrote it, so it must be good! '
        dString += 'It does all sorts of really cool stuff, and even has some flashing '
        dString += 'lights to keep you amused.'

        eString = 'OK, maybe I lied about the flashing lights, but the rest '
        eString += 'of it is really good'

        parser = OptionParser(version = vString, description = dString, epilog = eString)

        parser.add_option('-d', '--dry-run',
            action = 'store_false', dest = 'doupdate', default = True,
            help = 'process as normal, but do not update any files')

        parser.add_option('-x', '--debug',
            action = 'store_true', dest = 'debugmode', default = False,
            help = 'emit debug messages for troubleshooting')

        parser.add_option('-i', '--input', dest = 'inputfile',
            #action = 'append', # lets you have more than one file if required
            help = 'read input data from FILE, which must exist', metavar = 'FILE')

        parser.add_option('-l', '--limit', dest = 'limit', type='int',
            help = 'limit processing to the first NUM items found', metavar = 'NUM')

        parser.add_option('-v', '--verbose',
            action = 'store_true', dest = 'verbose', default = False,
            help = 'be more verbose when processing')

        (options, args) = parser.parse_args()

        self.doUpdate = options.doupdate
        self.debugMode = options.debugmode
        self.verbose = options.verbose
        self.inputfile = options.inputfile

        self.debugMsg(txt = 'options = %s' % options)
        self.debugMsg(txt = 'args = %s' % args)

    def validateOptions(self):
        if self.inputfile is not None and not os.path.exists(self.inputfile):
            self.logMsg(txt = 'ERROR: file %s does not exist' % self.inputfile, dateStamp = False)

def main():
    my = RunTimeOptions()

    my.logMsg(txt = 'Processing started')
    my.debugMsg(txt = 'This is a debug message with no datestamp', dateStamp=False)
    my.debugMsg(txt = 'This debug message has a datestamp')
    my.debugMsg(txt = 'Debug message with datestamp and a pause', pause=True)
    my.debugMsg(txt = 'Debug message without datestamp, with a pause', dateStamp=False, pause=True)

    my.logMsg(txt = 'Processing complete')

if __name__ == '__main__':
Download a copy1453 downloads