mirror of https://github.com/n-hys/bash.git
557 lines
20 KiB
Bash
557 lines
20 KiB
Bash
## -*- sh -*-
|
|
|
|
# The psuedo-ksh autoloader.
|
|
|
|
# How to use:
|
|
# o One function per file.
|
|
# o File and function name match exactly.
|
|
# o File is located in a directory that is in FPATH.
|
|
# o This script (autoload) must be sourced in as early as possible. This
|
|
# implies that any code in this script should NOT rely on any library of local
|
|
# or self-defined functions having already been loaded.
|
|
# o autoload must be called for each function before the function can be used. If
|
|
# autoloads are in directories where there are nothing but autoloads, then
|
|
# 'autoload /path/to/files/*' suffices (but see options -a and -f).
|
|
# o The call must be made in the current environment, not a subshell.
|
|
# o The command line suffices as "current environment". If you have autoload
|
|
# calls in a script, that script must be dotted into the process.
|
|
|
|
# The first cut of this was by Bill Trost, trost@reed.bitnet.
|
|
# The second cut came from Chet Ramey, chet@ins.CWRU.Edu
|
|
# The third cut came from Mark Kennedy, mtk@ny.ubs.com. 1998/08/25
|
|
# The fourth cut came from Matthew Persico, matthew.persico@gmail.com 2017/August
|
|
|
|
autoload_calc_shimsize ()
|
|
{
|
|
echo $((AUTOLOAD_SHIM_OVERHEAD + 3 * ${#1}))
|
|
}
|
|
|
|
_autoload_split_fpath ()
|
|
{
|
|
(IFS=':'; set -- ${FPATH}; echo "$@")
|
|
}
|
|
|
|
_aload()
|
|
{
|
|
local opt OPTIND
|
|
local doexport=0
|
|
local doreload=0
|
|
local doverbose=0
|
|
local doevalshim=0
|
|
local loadthese
|
|
local optimize=0
|
|
local loaded=0
|
|
local exported=0
|
|
local optimized=0
|
|
local summary=0
|
|
local dofpath=0
|
|
while getopts xrvla:oyf opt; do
|
|
case $opt in
|
|
x) doexport=1;;
|
|
r) doreload=1;;
|
|
v) doverbose=1;;
|
|
l) doevalshim=1;;
|
|
a) loadthese=$(find $OPTARG -maxdepth 1 -type f -printf '%f ');;
|
|
o) optimize=1;;
|
|
y) summary=1;;
|
|
f) loadthese=$(find $(_autoload_split_fpath) -maxdepth 1 -type f -printf '%f ');;
|
|
*) echo "_aload: usage: _aload [-xrvlyf] [-a dir] [function ...]" >&2; return;;
|
|
esac
|
|
done
|
|
|
|
shift $(($OPTIND-1))
|
|
|
|
[ -z "$loadthese" ] && loadthese="$@"
|
|
|
|
local func
|
|
for func in $loadthese; do
|
|
local exists_fn
|
|
exists_fn=$(declare -F $func)
|
|
if [ -n "$exists_fn" ] && ((doreload==0)) && ((doevalshim==0))
|
|
then
|
|
if ((doverbose))
|
|
then
|
|
echo "autoload: function '$func' already exists"
|
|
fi
|
|
else
|
|
local andevaled=''
|
|
local andexported=''
|
|
local evalstat=0
|
|
local doshim=1
|
|
local funcfile
|
|
funcfile=$(_autoload_resolve $func)
|
|
if [[ $funcfile ]] ; then
|
|
## The file was found for $func. Process it.
|
|
|
|
if ((optimize)); then
|
|
## For the first function loaded, we will not know
|
|
## AUTOLOAD_SHIM_OVERHEAD. We can only calculate it after
|
|
## we have loaded one function.
|
|
if [[ $AUTOLOAD_SHIM_OVERHEAD ]]; then
|
|
local size=$(wc -c $funcfile| sed 's/ .*//')
|
|
local shimsize=$(autoload_calc_shimsize $func)
|
|
if (( size <= shimsize)); then
|
|
doshim=0
|
|
andevaled=', optimized'
|
|
((optimized+=1))
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if ((doevalshim)); then
|
|
doshim=0
|
|
andevaled=', evaled'
|
|
fi
|
|
|
|
## 'brand' as in branding a cow with a mark. We add a local
|
|
## variable to each function we autoload so that we can tell
|
|
## later on it is an autoloaded function without having to
|
|
## maintain some bash array or hash that cannot be passed to
|
|
## and used by subshells.
|
|
local brandtext
|
|
brandtext="eval \"\$(type $func | sed -e 1d -e 4ilocal\\ AUTOLOADED=\'$func\')\""
|
|
if ((doshim)); then
|
|
## Don't bother trying to save space by shoving all the
|
|
## eval text below onto one unreadable line; new lines will
|
|
## be added at your semicolons and any indentation below
|
|
## seems to be ignored anyway if you export the function;
|
|
## look at its BASH_FUNCTION representation.
|
|
eval $func '()
|
|
{
|
|
local IS_SHIM="$func"
|
|
local file=$(_autoload_resolve '$func')
|
|
if [[ $file ]]
|
|
then
|
|
. $file
|
|
'$brandtext'
|
|
'$func' "$@"
|
|
return $?
|
|
else
|
|
return 1;
|
|
fi
|
|
}'
|
|
else
|
|
. $funcfile
|
|
eval "$brandtext"
|
|
fi
|
|
evalstat=$?
|
|
if((evalstat==0))
|
|
then
|
|
((loaded+=1))
|
|
((doexport)) && export -f $func && andexported=', exported' && ((exported+=1))
|
|
((doverbose)) && echo "$func autoloaded${andexported}${andevaled}"
|
|
if [[ ! $AUTOLOAD_SHIM_OVERHEAD ]] && ((doshim)); then
|
|
## ...we have just loaded the first function shim into
|
|
## memory. Let's calc the AUTOLOAD_SHIM_OVERHEAD size
|
|
## to use going forward. In theory, we could check
|
|
## again here to see if we should optimize and source
|
|
## in this function, now that we now the
|
|
## AUTOLOAD_SHIM_OVERHEAD. In practice, it's not worth
|
|
## duping that code or creating a function to do so for
|
|
## one function.
|
|
AUTOLOAD_SHIM_OVERHEAD=$(type $func | grep -v -E "^$1 is a function" | sed "s/$func//g"| wc -c)
|
|
export AUTOLOAD_SHIM_OVERHEAD
|
|
fi
|
|
else
|
|
echo "$func failed to load" >&2
|
|
fi
|
|
fi
|
|
fi
|
|
done
|
|
((summary)) && echo "autoload: loaded:$loaded exported:$exported optimized:$optimized overhead:$AUTOLOAD_SHIM_OVERHEAD bytes"
|
|
}
|
|
|
|
_autoload_dump()
|
|
{
|
|
local opt OPTIND
|
|
local opt_p=''
|
|
local opt_s=''
|
|
while getopts ps opt
|
|
do
|
|
case $opt in
|
|
p ) opt_p=1;;
|
|
s ) opt_s=1;;
|
|
esac
|
|
done
|
|
|
|
shift $(($OPTIND-1))
|
|
|
|
local exported=''
|
|
local executed=''
|
|
local func
|
|
for func in $(declare | grep -E 'local\\{0,1} AUTOLOADED' | sed -e "s/.*AUTOLOADED=//" -e 's/\\//g' -e 's/[");]//g' -e "s/'//g")
|
|
do
|
|
if [ -n "$opt_p" ]; then echo -n "autoload "; fi
|
|
if [ -n "$opt_s" ]
|
|
then
|
|
exported=$(declare -F | grep -E "${func}$" | sed 's/declare -f\(x\{0,1\}\).*/\1/')
|
|
[ "$exported" = 'x' ] && exported=' exported' || exported=' not exported'
|
|
executed=$(type $func | grep 'local IS_SHIM')
|
|
[ -z "$executed" ] && executed=' executed' || executed=' not executed'
|
|
fi
|
|
echo "${func}${exported}${executed}"
|
|
done
|
|
}
|
|
|
|
_autoload_resolve()
|
|
{
|
|
if [[ ! "$FPATH" ]]; then
|
|
echo "autoload: FPATH not set or null" >&2
|
|
return
|
|
fi
|
|
|
|
local p # for 'path'. The $() commands in the for loop split the FPATH
|
|
# string into its constituents so that each one may be processed.
|
|
|
|
for p in $( _autoload_split_fpath ); do
|
|
p=${p:-.}
|
|
if [ -f $p/$1 ]; then echo $p/$1; return; fi
|
|
done
|
|
|
|
echo "autoload: $1: function source file not found" >&2
|
|
}
|
|
|
|
_autoload_edit()
|
|
{
|
|
[ -z "$EDITOR" ] && echo "Error: no EDITOR defined" && return 1
|
|
local toedit
|
|
local func
|
|
for func in "$@"
|
|
do
|
|
local file=$(_autoload_resolve $func)
|
|
if [[ $file ]]
|
|
then
|
|
toedit="$toedit $file"
|
|
else
|
|
echo "$funcname not found in FPATH funcfile. Skipping."
|
|
fi
|
|
done
|
|
|
|
[ -z "$toedit" ] && return 1
|
|
|
|
local timemarker=$(mktemp)
|
|
|
|
$EDITOR $toedit
|
|
|
|
local i
|
|
for i in $toedit
|
|
do
|
|
if [ $i -nt $timemarker ]
|
|
then
|
|
local f=$(basename $i)
|
|
echo Reloading $f
|
|
autoload -r $f
|
|
fi
|
|
done
|
|
}
|
|
|
|
_autoload_page()
|
|
{
|
|
[ -z "$PAGER" ] && echo "Error: no PAGER defined" && return 1
|
|
local topage
|
|
local func
|
|
for func in "$@"
|
|
do
|
|
local file=$(_autoload_resolve $func)
|
|
if [[ $file ]]
|
|
then
|
|
topage="$topage $file"
|
|
else
|
|
echo "$funcname not found in FPATH funcfile. Skipping."
|
|
fi
|
|
done
|
|
|
|
[ -z "$topage" ] && return 1
|
|
|
|
$PAGER $topage
|
|
}
|
|
|
|
_autoload_remove()
|
|
{
|
|
unset -f "$@"
|
|
}
|
|
|
|
_autoload_help()
|
|
{
|
|
cat <<EOH
|
|
NAME
|
|
autoload
|
|
|
|
SYNOPSIS
|
|
autoload [-ps]
|
|
autoload [-xuremloyv] [function ...]
|
|
autoload -a directory [-oyv]
|
|
autoload -f [-oyv]
|
|
autoload [-h]
|
|
|
|
autoreload [function ...]
|
|
|
|
DESCRIPTION
|
|
|
|
An implementation of the 'autoload' functionality built into other
|
|
shells, of which 'ksh' is the most prominent. It allows for a keeping
|
|
the process environment small by loading small 'shim' functions into
|
|
memory that will, on first call, load the full text of the given
|
|
function and run it. Subsequent calls to the function just run the
|
|
function.
|
|
|
|
'autoreload' is a synonym for 'autoload -r'. See below.
|
|
|
|
USAGE
|
|
|
|
o Each function to be autoloaded should be defined in a single file,
|
|
named exactly the same as the function.
|
|
|
|
o In order to avoid side effects, do NOT put code other than the
|
|
function definition in the file. Unless of course you want to do some
|
|
one-time initialization. But beware that if you reload the function
|
|
for any reason, you will rerun the initialization code. Make sure
|
|
your initialization is re-entrant. Or, better yet,
|
|
|
|
*** do NOT put code other than the function definition in the file ***
|
|
|
|
o These function definition files should be placed in a directory that
|
|
is in the FPATH environment variable. Subdirectories are NOT scanned.
|
|
|
|
o The autoload script should be sourced into the current process as
|
|
early as possible in process start up. See NOTES below for
|
|
suggestions.
|
|
|
|
o The calls to the autoload function must be made in the current
|
|
process. If your calls are in their own script, that script must be
|
|
sourced in. Command line invocations are also sufficient. (But see
|
|
'-l' below.)
|
|
|
|
o The first time the function is called, the shim function that was
|
|
created by the 'autoload' call is what is executed. This function
|
|
then goes and finds the appropriate file in FPATH, sources it in and
|
|
then calls the actual function with any arguments you just passed in
|
|
to the shim function. Subsequent calls just run the function.
|
|
|
|
OPTIONS
|
|
|
|
-a Autoload (a)ll the functions found in the given directory.
|
|
|
|
-f Autoload all the functions found in all the directories on the
|
|
FPATH.
|
|
|
|
-p Print all the autoloaded functions.
|
|
|
|
-s Print all the autoloaded functions and add their export status.
|
|
|
|
-x Export the specified functions to the environment for use in
|
|
subshells.
|
|
|
|
-u Unset the function, so it can be reloaded.
|
|
|
|
-r Reload the shims of the specified functions, even if the functions
|
|
have been already been executed. This will allow you to modify the
|
|
functions' source and have the new version executed next time the
|
|
function is called.
|
|
|
|
It would be very easy to modify a function's script, run the
|
|
function and scratch your head for a long time trying to figure out
|
|
why your changes are not being executed. That's why we provide the
|
|
'-e' flag described below for modifications.
|
|
|
|
Reloads, of course, only apply in the context of the current session
|
|
and any future subshell you start from the current session. Existing
|
|
sessions will need to have the same 'autoload -r' command run in
|
|
them.
|
|
|
|
-e Find the scripts in which the specified functions are defined and
|
|
start up \$EDITOR on those scripts. Reload the ones that were
|
|
modified when you exit \$EDITOR. (Note: If you use 'autoload -e foo'
|
|
to edit function 'foo', and then in your editor you separately load
|
|
up function 'bar', 'autoload' has no way of knowing that you edited
|
|
'bar' and will NOT reload 'bar' for you.)
|
|
|
|
Reloads, of course, only apply in the context of the current session
|
|
and any future subshell you start from the current session. Existing
|
|
sessions will need to have the same 'autoload -r' command run in
|
|
them.
|
|
|
|
-m Find the scripts in which the specified functions are defined and
|
|
run \$PAGER on them ('m' is for 'more', because 'p' (page) and 'l'
|
|
(load) are already used as options in 'autoload').
|
|
|
|
-l When autoloading a function, eval the shim immediately in order to
|
|
load the true function code. See "Using '-l'" in the NOTES below for
|
|
details.
|
|
|
|
-o Optimize. When autoloading, take the time to execute
|
|
|
|
'theCharCount=\$(wc -c \$theFuncFile)'
|
|
|
|
for each function and
|
|
|
|
if \$theCharCount < \$AUTOLOAD_SHIM_OVERHEAD
|
|
|
|
don't shim it, just eval directly.
|
|
|
|
-y Summar(y). Print the number of loaded, exported and optimized
|
|
functions.
|
|
|
|
-v Turns up the chattiness.
|
|
|
|
NOTES
|
|
|
|
o Calling 'autoload' on a function that already exists (either shimmed
|
|
or expanded) silently ignores the request to load the shim unless it
|
|
has been previously removed (-u) or you force the reload (-r).
|
|
|
|
o Changing and reloading a function that has been exported does not
|
|
require it be re-exported; the modifications will appear in
|
|
subsequent subshells.
|
|
|
|
o Using '-1'
|
|
|
|
If you are running under set -x and/or set -v, you may see that the
|
|
shim does not appear to "work"; instead of seeing the shim first and
|
|
the real code subsequently, you may see the shim evaluated multiple
|
|
times.
|
|
|
|
This may not be an error; review your code. What is most likely
|
|
happening is that you are calling the function in subshells via
|
|
backticks or $(), or in a script that is not being sourced into the
|
|
current environment. If you have not previously called the function
|
|
in question at your command line or in a script that was sourced into
|
|
the current environment, then the various subshells are going to
|
|
encounter the shim and replace with the real code before executing.
|
|
|
|
Remember, however, that environment modifications that occur in a
|
|
subshell are NOT propagated back to the calling shell or over to any
|
|
sibling shells. So, if you call an autoloaded function in a very
|
|
tight loop of very many subshells, you may want to make an 'autoload
|
|
-l' call before you start your loop. '-l' will instruct 'autoload' to
|
|
bypass the shim creation and just source in the function's file
|
|
directly. For a few calls, the overhead of repeatedly running the
|
|
shim is not expensive, but in a tight loop, it might be. Caveat
|
|
Programer.
|
|
|
|
o Although the number of functions in the environment does not change
|
|
by using 'autoload', the amount of memory they take up can be greatly
|
|
reduced, depending on the size of your functions. If you have a lot
|
|
of small functions, then it is possible that the shim text will be
|
|
larger than your actual functions, rendering the memory savings moot.
|
|
|
|
'small' in this case can be determined by calling the function
|
|
'autoload_calc_shimsize' with the name of the function to determine
|
|
its shim size.
|
|
|
|
o In order to support the -p and -s options, we need a way to determine
|
|
if a function 'func' has been autoloaded or if it was loaded
|
|
diredctly. In order to do that, we modify the function's code by
|
|
adding the text
|
|
|
|
local AUTOLOADED='func';
|
|
|
|
to the shim and to the actual function text, just after the opening
|
|
brace. Then supporting -p and -s is just a matter of grepping through
|
|
all the function text in memory. Even though grepping through the
|
|
environment may not be the most efficient way to support this, it is
|
|
the simplest to implement for -p and -s operations that are not
|
|
heavily used.
|
|
|
|
As a consquence of this (and other reasons), the AUTOLOAD* namespace
|
|
is reserved for autoloading. Make sure you check any functions that
|
|
you bring under autoload for use of variables or functions that start
|
|
with AUTOLOAD and change them.
|
|
|
|
o The easiest way to load shims for all functions on the FPATH is to run
|
|
|
|
autoload -f -x
|
|
|
|
in the profile that gets run for login shells.
|
|
|
|
When called in the profile of a login shell where no definitions
|
|
exist, -f will load all functions it can find on FPATH and -x will
|
|
export all of those functions to be available in subshells when this
|
|
is called in a login shell. Using this option will relieve you of the
|
|
need to call 'autoload' after Every Single Function Definition, nor
|
|
will you need to call it in subshells.
|
|
|
|
The only thing left to do is to load up the autoload function itself
|
|
and its helper functions. That needs to happen in your profile:
|
|
|
|
export FPATH=~/functions # or wherever you stash them
|
|
if [ -z $(declare -F autoload) ]
|
|
then
|
|
. ~/bin/autoload # or wherever you've put it
|
|
fi
|
|
|
|
The 'if' statement is used to make sure we don't reload autoload
|
|
needlessly. Sourcing in the autoload script loads the 'autoload'
|
|
function and all of its support functions. Additionally, we export
|
|
all of these functions so that they are available in subshells; you
|
|
do not have to re-source the autoload file in '.bashrc'.
|
|
|
|
o Even with all of these shenanigans, you will find cases where no
|
|
matter how hard you try, your autoloaded functions will be
|
|
unavailable to you, even if you run 'autoload -x -f'. The typical
|
|
condition for this is starting up not a subshell, but a brand new
|
|
DIFFERENT shell. And the typical example of this is git extensions.
|
|
|
|
At the time of this writing, git extensions work by taking a command
|
|
'git foo' and looking for a file 'git-foo' on the path. 'git' then
|
|
executes 'git-foo' in a new shell - it executes your command in
|
|
/bin/sh. That's not a subshell of your process. It will not get your
|
|
exported shell functions. Ballgame over.
|
|
|
|
If you find that you want your functions to be available in such
|
|
circumstances, convert them back to plain old scripts, make sure they
|
|
are 'sh' compliant and take the read/parse hit every time they are
|
|
run.
|
|
|
|
EOH
|
|
}
|
|
|
|
autoload()
|
|
{
|
|
if (( $# == 0 )) ; then _autoload_dump; return; fi
|
|
|
|
local opt OPTIND OPTARG
|
|
local passthru
|
|
local dumpopt
|
|
while getopts psuema:yxrvlohf opt
|
|
do
|
|
case $opt in
|
|
p|s) dumpopt="$dumpopt -${opt}";;
|
|
u) shift $((OPTIND-1)); _autoload_remove "$@"; return;;
|
|
e) shift $((OPTIND-1)); _autoload_edit "$@"; return;;
|
|
m) shift $((OPTIND-1)); _autoload_page "$@"; return;;
|
|
x|r|v|l|y|f|o) passthru="$passthru -$opt";;
|
|
a) passthru="$passthru -$opt $OPTARG";;
|
|
h) _autoload_help; return;;
|
|
*) echo "autoload: usage: autoload [-puUx] [function ...]" >&2; return;;
|
|
esac
|
|
done
|
|
|
|
shift $(($OPTIND-1))
|
|
if [ -n "$dumpopt" ]
|
|
then
|
|
_autoload_dump $dumpopt
|
|
else
|
|
_aload $passthru "$@"
|
|
fi
|
|
}
|
|
|
|
autoreload ()
|
|
{
|
|
autoload -r "$@"
|
|
}
|
|
|
|
## When we source in autoload, we export (but NOT autoload) the autoload
|
|
## functions so that they are available in subshells and you don't have to
|
|
## source in the autoload file in subshells.
|
|
export -f _aload \
|
|
_autoload_dump \
|
|
_autoload_edit \
|
|
_autoload_help \
|
|
_autoload_page \
|
|
_autoload_resolve \
|
|
_autoload_split_fpath \
|
|
autoload \
|
|
autoload_calc_shimsize \
|
|
autoreload
|