home | O'Reilly's CD bookshelfs | FreeBSD | Linux | Cisco | Cisco Exam  


Book HomeBook TitleSearch this book

B.11. Using getopts

The getopts command is extremely capable. With it, you can make your shell scripts accept long options, specify that arguments are optional or numeric, and provide descriptions of the arguments and values such that the -?, --man, --html and --nroff options work the same for your program as they do for the ksh93 built-in commands.

The price for this power is the complexity of the option description "language." Based on a description provided by Dr. Glenn Fowler of AT&T Research, we describe how the facilities evolved, how they work, and summarize how to use them in your own programs. We use the the extended getopts command in the solution to Task B-1.

Task B-1

Design the program phaser4, that combines the features of the phaser3 and tricorder programs. Make sure it is self-documenting.[158]

[158] No, the walls of my room are not covered with Star Trek posters. I outgrew that a long time ago, and besides, my wife wouldn't let me anyway. ADR.

The first step is to describe the options. This is done with a comment at the top of the script:

# usage: phaser4 [ options ] files
#   -k, --kill                  use kill setting (default)
#   -l n, --level n             set phaser level (default = 2)
#   -s, --stun                  use stun-only setting
#   -t [lf], --tricorder [lf]   tricorder mode, opt. scan for life form lf

Now the fun begins. This outline of capabilities follows the order in which features were added to getopts.

  1. Start with the getopts command as described in Chapter 6. This yields a simple option string that only allows one-letter options:

    USAGE="kl#st:"
    while getopts "$USAGE" optchar ...
    

  2. Add a textual description for the option argument. This is done by enclosing arbitrary text in between [ and ]:

    USAGE="kl#[level]st:[life_form]"
    while getopts "$USAGE" optchar ...
    

  3. Allow a default value for an option's argument. This is done by specifying := value within the description in between the brackets:

    USAGE="kl#[level:=2]st:[life_form]"
    while getopts "$USAGE" optchar ...
    

  4. Add ? after the : to indicate an optional argument:

    USAGE="kl#[level:=2]st:?[life_form]"
    while getopts "$USAGE" optchar ...
    

  5. Allow long options that start with --. This is done by using [let:long] instead of the single option letter:

    USAGE="[k:kill]"
    USAGE+="[l:level]#[level:=2]"
    USAGE+="[s:stun]"
    USAGE+="[t:tricorder]:?[life_form]"
    while getopts "$USAGE" optchar ...
    

    Here, we've split each option out into its own line, to make things easier to follow, and concatenated them together using the += assignment operator. Note that there are no newlines in the string.

  6. Within the square brackets for an option letter, allow descriptive text to follow a question mark. This text is ignored, as are any whitespace characters, including newlines:

    USAGE="[k:kill?Use kill setting (default).]"
    USAGE+="[l:level]#[level:=2?Set the phaser level.]"
    USAGE+="[s:stun?Stun-only.]"
    USAGE+="[t:tricorder?Tricorder mode.]:?[life_form]"
    while getopts "$USAGE" optchar ...
    

  7. Now it gets interesting. Unix man page style section headings come before the option description. They are distinguished from option descriptions by starting with a + inside square brackets:

    USAGE="[+NAME?phaser4 --- combined phaser and tricorder]"
    USAGE+="[+DESCRIPTION?The phaser4 program combines the operation "
    USAGE+="of the phaser3 and tricorder programs in one handy tool.]"
    USAGE+="[k:kill?Use kill setting (default).]"
    USAGE+="[l:level]#[level:=2?Set the phaser level.]"
    USAGE+="[s:stun?Stun-only.]"
    USAGE+="[t:tricorder?Tricorder mode.]:?[life_form]"
    while getopts "$USAGE" optchar ...
    

    Note that getopts automatically understands that the actual options description comes after the man page headings; there is no explicit [+OPTIONS?...] in the text of the string.

  8. Additional descriptive text for the short usage summary can be given after the options description, separated by two newlines:

    USAGE="[+NAME?phaser4 --- combined phaser and tricorder]"
    USAGE+="[+DESCRIPTION?The phaser4 program combines the operation "
    USAGE+="of the phaser3 and tricorder programs in one handy tool.]"
    USAGE+="[k:kill?Use kill setting (default).]"
    USAGE+="[l:level]#[level:=2?Set the phaser level.]"
    USAGE+="[s:stun?Stun-only.]"
    USAGE+="[t:tricorder?Tricorder mode.]:?[life_form]"
    USAGE+=$'\n\nfile ...\n\n'                     Use ANSI C string for \n character
    USAGE+="[+SEE ALSO?phaser3(1), tricorder(1)]"
    while getopts "$USAGE" optchar ...
    

  9. To indicate text to be italicized, enclose it in between pairs of \a characters. To indicate text to be emboldened, enclose it between pairs of \b:

    USAGE="[+NAME?phaser4 --- combined phaser and tricorder]"
    USAGE+="[+DESCRIPTION?The \aphaser4\a program combines the operation "
    USAGE+="of the \aphaser3\a and \atricorder\a programs in one handy tool.]" 
    USAGE+="[k:kill?Use kill setting (default).]"
    USAGE+="[l:level]#[level:=2?Set the phaser level.]"
    USAGE+="[s:stun?Stun-only.]"
    USAGE+="[t:tricorder?Tricorder mode.]:?[life_form]"
    USAGE+=$'\n\nfile ...\n\n'
    USAGE+=$'[+SEE ALSO?\aphaser3\a(1), \atricorder\a(1)]'
    while getopts "$USAGE" optchar ...
    

  10. Dynamic control of descriptive output is possible. To do this, write a function that prints whatever you want, and then enclose the function name in a pair of \f characters: \fname\f (this isn't needed for phaser4).

  11. If an option (or anything else) needs a verbose description, enclosing the text between { and } creates an indented list. This is particularly useful for describing different option values:

    USAGE="[+NAME?phaser4 --- combined phaser and tricorder]"
    USAGE+="[+DESCRIPTION?The \aphaser4\a program combines the operation "
    USAGE+="of the \aphaser3\a and \atricorder\a programs in one handy tool.]"
    USAGE+="[k:kill?Use kill setting (default).]"
    USAGE+="[l:lev*el]#[level:=2?Set the phaser level.]{   Add value descriptions
                    [level=0-2?non-lethal settings]
                    [level=3-10?lethal, use with caution]
    }"
    USAGE+="[s:stun?Stun-only.]"
    USAGE+="[t:tricorder?Tricorder mode.]:?[life_form]"
    USAGE+=$'\n\nfile ...\n\n'
    USAGE+=$'[+SEE ALSO?\aphaser3\a(1), \atricorder\a(1)]'
    while getopts "$USAGE" optchar ...
    

  12. Almost done. Text in between square brackets that begins with a minus sign provides version and identification information. Such text comes at the very beginning. The empty item indicates a version and may contain both SCCS and RCS ID strings as shown here:

    USAGE=$'[-?\n@(#)$Id: phaser4 (Starfleet Research and Development)'
    USAGE+=$' Stardate 57234.22 $\n]'
    USAGE+="[-author?J. Programmer <J.Prog@r-d.starfleet.mil.fed>]"
    USAGE+="[-copyright?Copyright (c) Stardate 57000 Starfleet.]"
    USAGE+="[-license?http://www.starfleet.mil.fed/weapons-license.xml23]"
    USAGE+="[+NAME?phaser4 --- combined phaser and tricorder]"
    USAGE+="[+DESCRIPTION?The \aphaser4\a program combines the operation "
    USAGE+="of the \aphaser3\a and \atricorder\a programs in one handy tool.]"
    USAGE+="[k:kill?Use kill setting (default).]"
    USAGE+="[l:lev*el]#[level:=2?Set the phaser level.]{
                    [level=0-2?non-lethal settings]
                    [level=3-10?lethal, use with caution]
    }"
    USAGE+="[s:stun?Stun-only.]"
    USAGE+="[t:tricorder?Tricorder mode.]:?[life_form]"
    USAGE+=$'\n\nfile ...\n\n'
    USAGE+=$'[+SEE ALSO?\aphaser3\a(1), \atricorder\a(1)]'
    while getopts "$USAGE" optchar ...
    

  13. Finally, allow escapes within the strings. ]] represents a literal ] when getopts might otherwise take it to mean a closing bracket. Similarly, ?? stands for a literal ? that might otherwise start a description.

Whew! That's a lot of stuff. However, seeing it in the order it was added helps it to make sense. Here is a summary of the items that go in the usage string, in the order that getopts requires:

  1. Identification strings for the version, author, license, and so on are the very first part. They are enclosed in square brackets and begin with a minus sign. The item name, such as author, follows the minus sign and ends at a question mark. Following the question mark is the associated information.

    The empty item indicates version information, and should be of the form as shown earlier; getopts strips out the special SCCS and RCS identification characters.

  2. Unix man page-style section headings and text come next. These are enclosed in square brackets and begin with a + sign. The section heading name ends at the ? character, and the descriptive text follows.

    Text separated by two successive newlines from the options description is appended to the short usage message.

  3. Option descriptions form the third section. The original short form as described in Chapter 6 is still allowed:

    • Use : for options that require arguments.

    • Use # for options that require numeric arguments.

    • Use :? and #? for options that allow arguments but don't require them.

  4. Follow options by descriptive text in between [ and ]. Use := within the descriptive text to specify a default value for an option argument.

  5. Long options are matched with a short option letter by enclosing them in square brackets, separated by a colon. This replaces the single letter form.

  6. Enclose items to be italicized between two \a characters. Enclose items to be emboldened between two \b characters. Enclose the name of a customizing function to call between two \f characters.

  7. Use { and } to enclose nested, indented option descriptions.

  8. Follow the options section with two newlines and additional text for the short options summary.

  9. Use ]] to represent a literal ] and ?? to represent a literal ?.

Here is the skeletal version of phaser4:

#! /bin/ksh

# usage: phaser4 [ options ] files
#   -k, --kill                  use kill setting (default)
#   -l n, --level n             set phaser level (default = 2)
#   -s, --stun                  use stun-only setting
#   -t [lf], --tricorder [lf]   tricorder mode, opt. scan for life form lf

USAGE=$'[-?\n@(#)$Id: phaser4 (Starfleet Research and Development)'
USAGE+=$' Stardate 57234.22 $\n]'
USAGE+="[-author?J. Programmer <J.Prog@r-d.starfleet.mil.fed>]"
USAGE+="[-copyright?Copyright (c) Stardate 57000 Starfleet.]"
USAGE+="[-license?http://www.starfleet.mil.fed/weapons-license.xml23]"
USAGE+="[+NAME?phaser4 --- combined phaser and tricorder]"
USAGE+="[+DESCRIPTION?The \aphaser4\a program combines the operation "
USAGE+="of the \aphaser3\a and \atricorder\a programs in one handy tool.]"
USAGE+="[k:kill?Use kill setting (default).]"
USAGE+="[l:lev*el]#[level:=2?Set the phaser level.]{
                [0-2?non-lethal settings]
                [3-10?lethal, use with caution]
}"
USAGE+="[s:stun?Stun-only.]"
USAGE+="[t:tricorder?Tricorder mode.]:?[life_form]"
USAGE+=$'\n\nfile ...\n\n'
USAGE+=$'[+SEE ALSO?\aphaser3\a(1), \atricorder\a(1)]'

kill=1 stun=0 level=2   # defaults
tricorder=0 phaser=1
life_form=
while getopts "$USAGE" optchar ; do
    case $optchar in
    k)  kill=1 stun=0 ;;
    s)  kill=0 stun=1 ;;
    l)  level=$OPTARG
        if ((level < 0)) ; then level=0 ; fi
        if ((level > 10)) ; then level=10 ; fi
        ;;
    t)  phaser=0 tricorder=1
        life_form=${OPTARG:-"general_unknown"}
        ;;
    esac
done

print kill=$kill
print stun=$stun
print level=$level
print phaser=$phaser
print tricorder=$tricorder
print life_form=$life_form

Here is the output from phaser4 --man:

NAME
  phaser4 --- combined phaser and tricorder

SYNOPSIS
  phaser4 [ options ] file ...

DESCRIPTION
  The phaser4 program combines the operation of the phaser3 and tricorder
  programs in one handy tool.

OPTIONS
  -k, --kill      Use kill setting (default).
  -l, --level=level
                  Set the phaser level.
                    level=0-2
                          non-lethal settings
                    level=3-10
                          lethal, use with caution
                  The default value is 2.
  -s, --stun      Stun-only.
  -t, --tricorder[=life form]
                  Tricorder mode. The option value may be omitted.

SEE ALSO
  phaser3(1), tricorder(1)

IMPLEMENTATION
  version         phaser4 (Starfleet Research and Development) Stardate
                  57234.22
  author          J. Programmer <J.Prog@r-d.starfleet.mil.fed>
  copyright       Copyright (c) Stardate 57000 Starfleet.
  license         http://www.starfleet.mil.fed/weapons-license.xml23


Library Navigation Links

Copyright © 2003 O'Reilly & Associates. All rights reserved.