This paper is part of my Commonplace Book; it descripes the use and purposes of a local fixlib.
A user might have any numer of fixlibs. Any one is meant as a repair site, a work-table to repair or build functions whose likely use is restricted to the current or adjacent directories. A fixlib shouldn't be on the user's PATH
.
$ aNewfunction () { ... ; }
$ declare -f aNewfunction | tee -a fixlib
The fixlib may be edited by your favorite editor. Occasionally, it is prudent to back it up. Here is the only backup you'll ever need.
$ backup_lib fixlib
The preferred home for local functions is in a library named for its intended purpose. One of my common tasks these days, being "retired" is to manage our family investments. So in a HOME/Family/invest
directory you'll find an investlib. When functions in the local fixlib achieve a certain stability ( I haven't found the need to repair or extend them), then they should be transferred to the investlib.
$ cat fixlib >> investlib # append fixlib to investlib
$ lib_tidy investlib # removes duplicate functions
$ backup_lib {fix,invest}lib # backup both libraries
$ rm -f fixlib # since it has been backed up
$ backup_sync # pushes obsolete backups down
A user may have many fixlib in their account at any time. It's more likely there's a global function which needs inspection in the course of fixing a local function So, we'll include a few functions to identify where these might be.
How does it happen, that a function may be in multiple places at once? There's noting from preventing a quick fix happening in any working directory:
declare -f someGlobalFunction | tee -a fixlib
Some time later, after hopefully upward-compatible fixes to the function, you can find out where it belongs to put it back:
whf someGlobalFunction
This is the first function we've seen here in the examples. Here are it's component parts:
ihf ()
{
: ~ function
: reports first instance of linked library names
quietly whf $1 | xargs ls -i | awk ' !p[$1] ++ { print $2 }'
}
whf ()
{
: ~ function
: reports names of function libraries where function is defined
: USES funclocn -- returns the list of active function libraries
echo $* 1>&2;
xwh "$1" $(funclocn) | sed 's/ .*//; s/:.*//' | tr A-Z a-z | uniq
}
quietly ()
{
: ~ command ... -- gobbles stderr from command
$@ 2> /dev/null
}
xwh ()
{
patn="$1";
shift;
echo $# ... $1 $2 $3 ... 1>&2;
grep "^$patn[[:blank:]][[:blank:]]*()" $*;
grep "^function $patn *#*.*$" $*
}
$ quietly foreach do_whf whf ihf xwh
whf ./fixlib /users/applemcg/.profile
ihf ./fixlib /users/applemcg/dropbox/git/backash/bin/cmdlib
xwh /users/applemcg/.profile
From this, we see that the whf function is defined in two places, the local fixlib and my home .profile. That means, if the function is updated in the fixlib, it should be returned to the .profile. We will have to decide, in a separate exercise, if there are other intances of whf out for a fix. Similarly for ihf. However xwh is only found in the .profile.
For the moment, we won't consider that any of these functions may be in another fixlib.
If we can guarantee that functions in this local fixlib aren't defined in another fixlib, then regardless of whether or not they've been changed here, then the copies here, disregarding other copies may be returned to the proper library. Here's the first example:
source fixlib
declare -f whf | tee -a /users/applemcg/.profile
After repeating the process for each function, they may be removed from the fixlib.
functions fixlib | egrep -v '^(whf|ihf|xwh)$' > .x
. fixlib; declare -f $(< .x) | tee .l
mv .l fixlib
Following the outline of the various uses of a function, here's how to capture them in their proper resting place. Recall this may change as the function becomes more widely useful.
We've already seen this but let's refesh it here for completeness:
declare -f newFunction | tee -a fixlib
The question arises: how to avoid multiple copies of the same function? We'll answer this after the outline.
Recall, there are two classes here as well: a command line utility or a function widely used as sub-function. First, the command line:
declare -f cmdLineUtility | tee -a $(which cmdlib)
In this case, I maintain a library cmdlib, stored on my PATH, which is included by a source cmdlib
in my .profile.
Alternatively, a function may be destined for a widely-used shell programming feature or as an addition to a existing publicly available tool. Since any of these are also found on my path, storing them is the same, with the exception of the library name.
programming
In practice, I actually have two of these. One called auxlib, the other known as programlib. The auxlib is more rigorously controlled, the programlib, is an alternate to the cmdlib, but assigned the same way.
declare -f report_feature | tee -a $(which programlib)
There's a pair of functions to identify where a function is used: sfuse and Set Functions USE, and fuse, just Functions USE, the latter reading from stdin
. The former, sfuse displays the results of the set command, where all the function definitions and shell varialbes are displayed.
$ declare -f sfuse fuse
sfuse ()
{
trace_call $*;
set | fuse "$1"
}
fuse ()
{
report_notargcount 1 $# pattern && return 1;
: reconcile patterns to fun_from;
p="$1";
shift;
cat ${*:--} | awk "
\$1 ~ /^:$/ || \$1 ~ /^comment$/ { next }
\$2 ~ /^\(\)$/ { f = \$1 }
/^function / { f = \$2 }
f != \"\" && ( \
/[^a-zA-Z0-9_-]$p[^a-zA-Z0-9_-]/ || \
/[^a-zA-Z0-9_-]$p$/ ) {
printf \"%s\t%s\n\", f, \$0
}
"
}
$ sfuse fuse
fun_create fuse eval | grep '()' | sed 's/ *()/ ()/; s/ *\$/$/' | awk '$4 ~ /()/'
fun_maker set | fuse eval | grep ' () ' | field 1
nuse set | fuse $1 | grep -v ' () *$' | wc -l
sfuse set | fuse "$1"
So, the later usage: sfuse fuse
shows the list of functions and the context for each instance of the fuse function. In the current scope, this shows that fuse is used by funcreate, funmaker, nuse, and sfuse itself.
What does this tell us? That fuse belongs in programlib and not cmdlib
tool interfaces
It turns out, that sfuse is primarily a cmdlib tool, since I use it primarily on the command line. But it also nicely shows an exception to the rule which introduces the final component.
$ ff fun_bash obs_names
fun_bash ()
{
report_notargcount 1 $# function name && return 1;
trace_call $*;
rdb_hdr bash context;
sfuse $1
}
obs_names ()
{
trace_call $*;
sfuse OBSOLESCENT | field 1
}
$ fun_bash sfuse
bash context
---- -------
fun_bash sfuse $1
obs_names sfuse OBSOLESCENT | field 1
The exception here is that sfuse is indeed used in other functions. How did we find these? We used one of them, funbash to display the connection to another tool, in this case, the underutilized /RDB. In this case I used it to produce a small sample table of the sfuse output, folded into an /rdb table, where the field names are bash and context, listing the shell output in the table.
A partial list of tools with wrapper libraries:
This top-level set of functions uses copies of functions found in the local fixlib in common with the current run-time version of the function: lib = which $1
. In funtoliblist, the functions are first copied to the existing library, and then, in funtopreflib are removed from the default fixlib.
fun_golib ()
{
: ~ libraryName
: functions in fixlib are returned to their home library
: and culled from the fixlib
: USES report_ , functions, fun_starter, backup_lib, and flcomm
local lib=$(which $1); shift
report_notfile $lib /dev/null && return 1
local fix=./fixlib
local tmp=./.l
: functions in BOTH in FIX and LIB, "comm -12 .."
local lst=$(flcomm -12 $fix $lib)
[[ -z "$lst" ]] && {
comment no FIXLIB functions to $lib
comment ===========================
quietly foreach do_whf $(functions $fix)
return
}
. $fix # source FIXLIB, the FIXed versions
fun_toliblist $lib $lst
comment =================
fix_whf
}
fun_toliblist ()
{
: ~ library function ...
: append functions to library, back it up
# report_notcalledby fix_golib && return 1
lib=$1; shift
tmp=./.l
shell_write $tmp declare -f $(functions $lib) $*
shell_append $tmp fun_starter $lib
mv $tmp $lib
backup_lib $lib
fun_preferlib $lib
}
fun_preferlib ()
{
: ~ library [fixlib]
: functions unique to 2nd arg are retained,
: library is NOT updated
report_notfile $1 && return 1
lib=$1; shift
frm=${2:-./fixlib}; shift
report_notfile $frm && return 2
tmp=./.l
shell_write $tmp declare -f $(flcomm -23 $frm $lib)
doit mv $tmp $frm
backup_lib $frm
}
fun_whf () { do_do whf; quietly foreach do_whf $(functions fixlib); }
fix_only ()
{
fun_whf | awk 'NF == 2 { print $1 }'
}