Oct 2007
Most system oriented scripters and programmers tend to rely upon the classic Unix model of options and/or arguments; conversely, many programs and scripts use a subcommand structure that is similar to language processing (or lexical checks). Consider that APIs generally use a lexical style interface - in a sense the natural language idea makes sense if the script or program is going to be called by another program.
The example script is ultra simplified to make the example easier to
follow and done in the bash shell for the same reason.
The script creates a directory that must either be:
GID bit set.
If a shared group is not needed; a default will be assumed.
The basic main pseudo-code looks like:
get arguments
parse arguments
setting a group?
yes
make sure the group arg is set
group arg is set
create directory
set group perms
group arg is not set
error no group
no
assume it needs GID set
create directory using native user group
set GID
The assumption is if any of the actions fail - there will be an exception handler.
From the pseudo-code there are several functions that can be extrapolated:
Extraordinarily simple for the example; but enough to get one thinking...
Excepting the main function; there are at least two sub
functions that need to be written before the main is even
started.
Systhread of course has the tradition of using the same bomb()
routine over and over - why change? :
#---------------------------------------------------------------------
# bomb - bomb and bail
#
# requires: An error message.
# returns : 1
#---------------------------------------------------------------------
bomb()
{
cat >&2 <<ERRORMESSAGE
ERROR: $@
*** ${PROG} aborted ***
ERRORMESSAGE
kill ${TOPPID} # in case we were invoked from a subshell
exit 1
}
#---------------------------------------------------------------------
# create_directory - create the directory using specifications
#
# requires: directory name, group name, gid flag
# returns : $?
#---------------------------------------------------------------------
create_directory()
{
dirname=$1
groupname=$2
gidflag=$2
mkdir -p $dirname ||
bomb "Cannot create ${dirname} for some reason"
chgrp -R $groupname ||
bomb "Cannot set ${dirname} to Unix group ${groupname}"
[ "$gidflag" -gt 0 ] &&
chmod 2774 $dirname
}
Examine the shell code below:
#---------------------------------------------------------------------
# Main -
# - Default DIRNAME, GRPNAME, SETGID
# - parse
# - call create_directory
# - exit
#---------------------------------------------------------------------
DIRNAME="nothing" # default is a ficticous group
GRPNAME="users" # default is users group
SETGID=0 # default is false
while [ "$#" -gt "0" ]
do
opt="${1//-}"
opt="${1//-}"
opt=$(echo "${opt}" | cut -c 1 2>/dev/null)
case $opt in
d) shift; DIRNAME=$1;; # Assign a real directory
g) shift; GRPNAME=$1;; # Assign a group name if needed
s) SETGID=1;; # GID flag becomes set
u) usage;;
*) usage; exit 1;;
esac
shift
done
[ ${DIRNAME} = "nothing" ] &&
bomb "No directory specified"
create_directory $DIRNAME $GRPNAME $SETGID
exit 0
Example operation, create /depts/pusers owned by the group prod with setgid:
ezdir -d /dept/pusers -g prod -s
Note how the script will accept any combination matching
-*A**... where A is any alphanumeric to match into the
case statement such as -d, --d, -dir, --dir, directory
and so on will trigger the d) case in. The script is
off to a good start, it already is very flexible and has the capability
to act naturally. Using the first example operation, here is a more
natural example:
ezdir dir /dept/pusers group prod setgid
The above will end up behaving the same way as the first example.
Some might think ezdir dir pusers group prod setgid is too
cumbersome.
By using fall through logic the options can be dropped and simple keyword order
takes over:
while [ "$#" -gt "0" ]
do
opt="${1//-}"
opt="${1//-}"
opt=$(echo "${opt}" | cut -c 1 2>/dev/null)
case $opt in
d) shift; DIRNAME=$1;; # Assign a real directory
g) shift; GRPNAME=$1;; # Assign a group name if needed
s) SETGID=1;; # GID flag becomes set
u) usage;;
*)
DIRNAME=$1
GRPNAME=$2
[ ${GRPNAME} = "setgid" ] &&
GRPNAME="users"
;;
esac
shift
done
Now the script can operate in one of two modes, options and args or straight up keywords:
ezdir -g prod -d /dept/pusers -s
... or ...
ezdir /dept/pusers prod setgid
... and using the default group ...
ezdir /dept/pusers setgid
Of course making sure usage is explained correctly is the important part of the script:
usage()
{
cat <<_usage_
Usage: ${progname} [options arg][keywords ... ]
Usage: ${progname} [-d|--dir dirname][-g|--group groupname][-s|--set]
Usage: ${progname} [dir dirname][group groupname][setgid]
Usage: ${progname} [dirname groupname][setgid]
Options:
-d|--dir|dir dirname Set Directory name.
-g|--group|group groupname Set groupname.
-s|--set|setgid Set the GID bit on the directory.
Examples:
Create directory using defaults and no setgid -
${progname} -d /shar/foo
${progname} /shar/foo
Create directory using groupname with setgid -
${progname} -d /shar/foo -g bars -s
${progname} /shar/foo bars setgid
_usage_
}
#!/bin/bash
# Script -------------------------------------------------------------
# ezdir - Create a directory using default or specified group
# and/or setgid.
#---------------------------------------------------------------------
progname=${0##*/}
#---------------------------------------------------------------------
# bomb - bomb and bail
#
# requires: An error message.
# returns : 1
#---------------------------------------------------------------------
bomb()
{
cat >&2 <<ERRORMESSAGE
ERROR: $@
*** ${PROG} aborted ***
ERRORMESSAGE
kill ${TOPPID} # in case we were invoked from a subshell
exit 1
}
#---------------------------------------------------------------------
# create_directory - create the directory using specifications
#
# requires: directory name, group name, gid flag
# returns : $?
#---------------------------------------------------------------------
create_directory()
{
dirname=$1
groupname=$2
gidflag=$2
mkdir -p $dirname ||
bomb "Cannot create ${dirname} for some reason"
chgrp -R $groupname ||
bomb "Cannot set ${dirname} to Unix group ${groupname}"
[ "$gidflag" -gt 0 ] &&
chmod 2774 $dirname
}
#---------------------------------------------------------------------
# Usage - Simple usage echo
#---------------------------------------------------------------------
usage()
{
cat <<_usage_
Usage: ${progname} [options arg][keywords ... ]
Usage: ${progname} [-d|--dir dirname][-g|--group groupname][-s|--set]
Usage: ${progname} [dir dirname][group groupname][setgid]
Usage: ${progname} [dirname groupname][setgid]
Options:
-d|--dir|dir dirname Set Directory name.
-g|--group|group groupname Set groupname.
-s|--set|setgid Set the GID bit on the directory.
Examples:
Create directory using defaults and no setgid -
${progname} -d /shar/foo
${progname} /shar/foo
Create directory using groupname with setgid -
${progname} -d /shar/foo -g bars -s
${progname} /shar/foo bars setgid
_usage_
}
#---------------------------------------------------------------------
# Main -
# - Default DIRNAME, GRPNAME, SETGID
# - parse
# - call create_directory
# - exit
#---------------------------------------------------------------------
DIRNAME="nothing" # default is a ficticous group
GRPNAME="users" # default is users group
SETGID=0 # default is false
while [ "$#" -gt "0" ]
do
opt="${1//-}"
opt="${1//-}"
opt=$(echo "${opt}" | cut -c 1 2>/dev/null)
case $opt in
d) shift; DIRNAME=$1;; # Assign a real directory
g) shift; GRPNAME=$1;; # Assign a group name if needed
s) SETGID=1;; # GID flag becomes set
u) usage;;
*)
DIRNAME=$1
GRPNAME=$2
[ ${GRPNAME} = "setgid" ] &&
GRPNAME="users"
esac
shift
done
[ ${DIRNAME} = "nothing" ] &&
bomb "No directory specified"
create_directory $DIRNAME $GRPNAME $SETGID
exit 0
Mixing and matching command line argument styles isn't too difficult. In the provided example, the operations are pretty simple. It is easy to see how additional methods like using a command-sub_command structure could be used or even lexicals.
(based on last 2 months log reports)