I solved these problems by using a directory based system. A
command set lives in a directory. The text of the menu that
describes the command set is in a file in that directory called
menu.dat. The function definitions for the command set live in
files with rwx permissions of 644. They have names that end in
".f" or ".sam".
The ".f" files define functions that remain available. More
specifically, the functions defined by .f files remain actively
available during the SAM session. I have placed .f files in two
directories (but they could be used anywhere). One is $sam and
the other is $sam_tool. Directory $sam contains executables
(scripts and binaries) that are fundamental elements of SAM. For
example, a shell script called "menu" lives there. It, of course,
displays the menu for the current command set when entered.
Directory $sam also contains .f files, as I said already, that define
basic SAM functions. Directory $sam_tool is similar to $sam,
except that what it contains is not fundamental to SAM (tools rather
than basic function). SAM keeps both of these directories in the
PATH variable which keeps the executables in them actively
available. Thus the functions defined by .f files in these
directories _and_ the executables in these directories both remain
actively available during the SAM session.
The ".sam" files define functions that are only available when SAM has
changed the PATH variable such that it includes the directory that
contains the .sam files. This clever bit of trickery is done so
that the functions defined by .sam files in a directory are actively
available when and only when the executables in the directory are
actively available. SAM's command "bound" is used to control the
PATH variable (and actively available functions and executables) in
this manner. Command bound, by the way, is a function defined by
the file $sam/bound.f, thus it is defined by a .f file in $sam.
There are two scripts and two functions that I need to describe.
The scripts are unfun and funfun. Both of these are created and
sometimes updated by function dofun. dofun is the only function
in $sam that is not an .f file. It sources the .f and .sam files,
so therefore cannot be one itself. Read more about this
below. Scripts unfun and funfun are similar (yet
different). unfun is used whenever the command set is changed by
bound or bye. (Actually bound runs sam_shel which runs
unfun. Similarly bye runs pop which runs unfun.) bound changes to
a new command set and bye returns to the previous command set.
unfun is invoked to wipe the function definitions before they are set
anew by function dofun. Then name "unfun" means "undo
functions". funfun is similar, but it is only invoked when SAM is
started. It thus removes inherited function definitions.
This is especially for cases when SAM is started recursively inside
itself. Its name means ".f undo functions".
The two functions I need to describe are upfun and dofun. (I've
already talked about dofun above.) upfun is a convenient function that
first runs unfun and then dofun. So doing it is responsible
for: (1) Unsetting the ".sam" functions defined by a previous use
of bound, (2) sourcing the new function definitions, and (3)
maintaining file $sam_temp_dir/unfun. (dofun can also maintain
$sam_temp_dir/funfun, but it doesn't do it in this case. It
normally writes to funfun only when SAM is started.)
The sourcing that is done by dofun is interesting. It sources
the functions that are available in a SAM session. But it is
itself a function. What sources dofun? The answer is that it is
sourced explicitly without the use of a special tool. I'll show
you. Early in the invocation of SAM this is performed:
export -f dofun
So, you see, dofun "boots" or enables the scheme. It is
what gets the scheme started, therefore it cannot be a part of the
At this point I have described the solution to problems one through
three. Problem four, the stack, remains. It is tempting to
use Bash to open a new shell as a way of pushing the environment (and
thus the command set) onto a stack. This however, prevents SAM
from being especially useful. I will illustrate. Let's say
that you wanted to write a script that operates inside the shell
created by the invocation of SAM. This script might look like
# This script "bounds" to example/backup, then it uses
# the command "gen" to do make a tar file from my root
gen $env_scratch/root.tar ~/*
It uses the command "gen" which is part of the SAM
application. Here is a listing of gen:
# This makes archive $1 containing $2, $3, ...
tar -cvMf $*
The command gen GENerates a backup file. The script
"do_backup" above uses the gen command in a specific way. A user
may want to do something like this to automate a process he uses
repeatedly. It is simpler to invoke "do_backup" than to enter the
two lines that the script contains.
But here is the point: A script like do_backup won't work if SAM
uses Bash to open a new shell when pushing the previous command set
onto a stack and changing to the new command set. If this was
done, then the line "bound $sam_distro/example/backup" would make a new
shell. The command gen would be available inside that shell, but
that wouldn't help. We would want to run gen on the next line of
the script which wouldn't go until the shell exited.
So instead I wrote two functions called push and pop. push
pushes the current environment onto a special stack that lives in
directory $sam_temp_dir. pop restores the previous environment by
using information written to $sam_temp_dir. The user can run push
and pop directly, but there is no need. The user uses commands
bound, hello and bye. bound does a push before it does its other
tasks (actually, it calls hello which calls push). A partner to
hello is bye which invokes pop.