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]
|
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.
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 ...
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 ...
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 ...
Add ? after the : to
indicate an optional argument:
USAGE="kl#[level:=2]st:?[life_form]"
while getopts "$USAGE" optchar ...
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.
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 ...
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.
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 ...
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 ...
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).
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 ...
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 ...
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:
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.
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.
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.
Follow options by descriptive text in between [ and ].
Use := within the descriptive text to specify a default value for an option argument.
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.
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.
Use { and } to enclose
nested, indented option descriptions.
Follow the options section with two newlines and additional
text for the short options summary.
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
| | | B.10. vi Control Mode Commands | | C. Building ksh from Source Code |
Copyright © 2003 O'Reilly & Associates. All rights reserved.
|
|