diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 06:17:24 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 06:17:24 +0000 |
commit | 9d8085074991d5c0a42d6fc96a2d1a3ee918aad1 (patch) | |
tree | c85bca1e6c11eb872edfc64c524d20f2b7e3307b /examples/functions/autoload | |
parent | Initial commit. (diff) | |
download | bash-upstream.tar.xz bash-upstream.zip |
Adding upstream version 5.1.upstream/5.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | examples/functions/autoload | 111 | ||||
-rw-r--r-- | examples/functions/autoload.v2 | 192 | ||||
-rw-r--r-- | examples/functions/autoload.v3 | 125 | ||||
-rw-r--r-- | examples/functions/autoload.v4 | 556 | ||||
-rw-r--r-- | examples/functions/autoload.v4.t | 184 |
5 files changed, 1168 insertions, 0 deletions
diff --git a/examples/functions/autoload b/examples/functions/autoload new file mode 100644 index 0000000..cb3a673 --- /dev/null +++ b/examples/functions/autoload @@ -0,0 +1,111 @@ +# +# An almost ksh-compatible `autoload'. A function declared as `autoload' will +# be read in from a file the same name as the function found by searching the +# $FPATH (which works the same as $PATH), then that definition will be run. +# +# To do this without source support, we define a dummy function that, when +# executed, will load the file (thereby re-defining the function), then +# execute that newly-redefined function with the original arguments. +# +# It's not identical to ksh because ksh apparently does lazy evaluation +# and looks for the file to load from only when the function is referenced. +# This one requires that the file exist when the function is declared as +# `autoload'. +# +# usage: autoload func [func...] +# +# The first cut of this was by Bill Trost, trost@reed.bitnet +# +# Chet Ramey +# chet@ins.CWRU.Edu + +# +# Declare a function ($1) to be autoloaded from a file ($2) when it is first +# called. This defines a `temporary' function that will `.' the file +# containing the real function definition, then execute that new definition with +# the arguments given to this `fake' function. The autoload function defined +# by the file and the file itself *must* be named identically. +# + +aload() +{ + eval $1 '() { . '$2' ; '$1' "$@" ; return $? ; }' +} + +# +# Search $FPATH for a file the same name as the function given as $1, and +# autoload the function from that file. There is no default $FPATH. +# + +autoload() +{ + # + # Save the list of functions; we're going to blow away the arguments + # in a second. If any of the names contain white space, TFB. + # + + local args="$*" + + # + # This should, I think, list the functions marked as autoload and not + # yet defined, but we don't have enough information to do that here. + # + if [ $# -eq 0 ] ; then + echo "usage: autoload function [function...]" >&2 + return 1 + fi + + # + # If there is no $FPATH, there is no work to be done + # + + if [ -z "$FPATH" ] ; then + echo autoload: FPATH not set or null >&2 + return 1 + fi + + # + # This treats FPATH exactly like PATH: a null field anywhere in the + # FPATH is treated the same as the current directory. + # + # The path splitting command is taken from Kernighan and Pike + # + +# fp=$(echo $FPATH | sed 's/^:/.:/ +# s/::/:.:/g +# s/:$/:./ +# s/:/ /g') + + # replaced with builtin mechanisms 2001 Oct 10 + + fp=${FPATH/#:/.:} + fp=${fp//::/:.:} + fp=${fp/%:/:.} + fp=${fp//:/ } + + for FUNC in $args ; do + # + # We're blowing away the arguments to autoload here... + # We have to; there are no arrays (well, there are, but + # this doesn't use them yet). + # + set -- $fp + + while [ $# -ne 0 ] ; do + if [ -f $1/$FUNC ] ; then + break # found it! + fi + shift + done + + if [ $# -eq 0 ] ; then + echo "$FUNC: autoload function not found" >&2 + continue + fi + +# echo auto-loading $FUNC from $1/$FUNC + aload $FUNC $1/$FUNC + done + + return 0 +} diff --git a/examples/functions/autoload.v2 b/examples/functions/autoload.v2 new file mode 100644 index 0000000..e29c695 --- /dev/null +++ b/examples/functions/autoload.v2 @@ -0,0 +1,192 @@ +# +# An almost ksh-compatible `autoload'. A function declared as `autoload' will +# be read in from a file the same name as the function found by searching the +# $FPATH (which works the same as $PATH), then that definition will be run. +# +# To do this without source support, we define a dummy function that, when +# executed, will load the file (thereby re-defining the function), then +# execute that newly-redefined function with the original arguments. +# +# It's not identical to ksh because ksh apparently does lazy evaluation +# and looks for the file to load from only when the function is referenced. +# This one requires that the file exist when the function is declared as +# `autoload'. +# +# usage: autoload [-pu] [func ...] +# +# options: +# -p print in a format that can be reused as input +# -u unset each function and remove it from the autoload list +# +# The first cut of this was by Bill Trost, trost@reed.edu +# +# Chet Ramey +# chet@ins.CWRU.Edu + +unset _AUTOLOADS +_aindex=0 + +# +# Declare a function ($1) to be autoloaded from a file ($2) when it is first +# called. This defines a `temporary' function that will `.' the file +# containing the real function definition, then execute that new definition with +# the arguments given to this `fake' function. The autoload function defined +# by the file and the file itself *must* be named identically. +# + +_aload() +{ + eval $1 '() { . '$2' ; '$1' "$@" ; return $? ; }' + _autoload_addlist "$1" +} + +_autoload_addlist() +{ + local i=0 + + while (( i < $_aindex )); do + case "${_AUTOLOADS[i]}" in + "$1") return 1 ;; + esac + (( i += 1 )) + done + _AUTOLOADS[_aindex]="$1" + (( _aindex += 1 )) + return 0 +} + +_autoload_dump() +{ + local func + + for func in ${_AUTOLOADS[@]}; do + [ -n "$1" ] && echo -n "autoload " + echo "$func" + done +} + +# Remove $1 from the list of autoloaded functions +_autoload_remove_one() +{ + local i=0 nnl=0 + local -a nlist + + while (( i < _aindex )); do + case "${_AUTOLOADS[i]}" in + "$1") ;; + *) nlist[nnl]="${_AUTOLOADS[i]}" ; (( nnl += 1 ));; + esac + (( i += 1 )) + done + unset _AUTOLOADS _aindex + eval _AUTOLOADS=( ${nlist[@]} ) + _aindex=$nnl +} + +# Remove all function arguments from the list of autoloaded functions +_autoload_remove() +{ + local func i es=0 + + # first unset the autoloaded functions + for func; do + i=0 + while (( i < _aindex )); do + case "${_AUTOLOADS[i]}" in + "$func") unset -f $func ; break ;; + esac + (( i += 1 )) + done + if (( i == _aindex )); then + echo "autoload: $func: not an autoloaded function" >&2 + es=1 + fi + done + + # then rebuild the list of autoloaded functions + for func ; do + _autoload_remove_one "$func" + done + + return $es +} + +# +# Search $FPATH for a file the same name as the function given as $1, and +# autoload the function from that file. There is no default $FPATH. +# + +autoload() +{ + local -a fp + local _autoload_unset nfp i + + if (( $# == 0 )) ; then + _autoload_dump + return 0 + fi + + OPTIND=1 + while getopts pu opt + do + case "$opt" in + p) _autoload_dump printable; return 0;; + u) _autoload_unset=y ;; + *) echo "autoload: usage: autoload [-pu] [function ...]" >&2 + return 1 ;; + esac + done + + shift $(( $OPTIND - 1 )) + + if [ -n "$_autoload_unset" ]; then + _autoload_remove "$@" + return $? + fi + + # + # If there is no $FPATH, there is no work to be done + # + + if [ -z "$FPATH" ] ; then + echo "autoload: FPATH not set or null" >&2 + return 1 + fi + + # + # This treats FPATH exactly like PATH: a null field anywhere in the + # FPATH is treated the same as the current directory. + # + # This turns $FPATH into an array, substituting `.' for `' + # + eval fp=( $( + IFS=':' + set -- ${FPATH} + for p in "$@" ; do echo -n "${p:-.} "; done + ) + ) + + nfp=${#fp[@]} + + for FUNC ; do + i=0; + while (( i < nfp )) ; do + if [ -f ${fp[i]}/$FUNC ] ; then + break # found it! + fi + (( i += 1 )) + done + + if (( i == nfp )) ; then + echo "autoload: $FUNC: autoload function not found" >&2 + es=1 + continue + fi + +# echo auto-loading $FUNC from ${fp[i]}/$FUNC + _aload $FUNC ${fp[i]}/$FUNC + es=0 + done + + return $es +} diff --git a/examples/functions/autoload.v3 b/examples/functions/autoload.v3 new file mode 100644 index 0000000..b1e5dfe --- /dev/null +++ b/examples/functions/autoload.v3 @@ -0,0 +1,125 @@ +#From: Mark Kennedy <mark.t.kennedy@gmail.com> (<mtk@ny.ubs.com>) +#Message-ID: <35E2B899.63A02DF5@ny.ubs.com> +#Date: Tue, 25 Aug 1998 09:14:01 -0400 +#To: chet@nike.ins.cwru.edu +#Subject: a newer version of the ksh-style 'autoload' + +#enclosed you'll find 'autoload.v3', a version of the autoloader +#that emulates the ksh semantics of delaying the resolution (and loading) of the function +#until its first use. i took the liberty of simplifying the code a bit although it still uses the +#same functional breakdown. i recently went through the exercise of converting +#my ksh-based environment to bash (a very, very pleasant experience) +#and this popped out. + +# the psuedo-ksh autoloader. + +# 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 + +unset _AUTOLOADS + +_aload() +{ + local func + for func; do + eval $func '() + { + local f=$(_autoload_resolve '$func') + if [[ $f ]]; then + . $f + '$func' "$@" + return $? + else + return 1 + fi + }' + _autoload_addlist $func + done +} + +_autoload_addlist() +{ + local func + + for func in ${_AUTOLOADS[@]}; do + [[ $func = "$1" ]] && return + done + + _AUTOLOADS[${#_AUTOLOADS[@]}]=$1 +} + +_autoload_dump() +{ + local func + + for func in ${_AUTOLOADS[@]}; do + [[ $1 ]] && echo -n "autoload " + echo $func + done +} + +_autoload_remove_one() +{ + local func + local -a NEW_AUTOLOADS + + for func in ${_AUTOLOADS[@]}; do + [[ $func != "$1" ]] && NEW_AUTOLOADS[${#NEW_AUTOLOADS[@]}]=$func + done + + _AUTOLOADS=( ${NEW_AUTOLOADS[@]} ) +} + +_autoload_remove() +{ + local victim func + + for victim; do + for func in ${_AUTOLOADS[@]}; do + [[ $victim = "$func" ]] && unset -f $func && continue 2 + done + echo "autoload: $func: not an autoloaded function" >&2 + done + + for func; do + _autoload_remove_one $func + done +} + +_autoload_resolve() +{ + if [[ ! "$FPATH" ]]; then + echo "autoload: FPATH not set or null" >&2 + return + fi + + local p + + for p in $( (IFS=':'; set -- ${FPATH}; echo "$@") ); do + p=${p:-.} + if [ -f $p/$1 ]; then echo $p/$1; return; fi + done + + echo "autoload: $1: function source file not found" >&2 +} + +autoload() +{ + if (( $# == 0 )) ; then _autoload_dump; return; fi + + local opt OPTIND + + while getopts pu opt + do + case $opt in + p) _autoload_dump printable; return;; + u) shift $((OPTIND-1)); _autoload_remove "$@"; return;; + *) echo "autoload: usage: autoload [-pu] [function ...]" >&2; return;; + esac + done + + shift $(($OPTIND-1)) + + _aload "$@" +} diff --git a/examples/functions/autoload.v4 b/examples/functions/autoload.v4 new file mode 100644 index 0000000..7f60563 --- /dev/null +++ b/examples/functions/autoload.v4 @@ -0,0 +1,556 @@ +## -*- 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 diff --git a/examples/functions/autoload.v4.t b/examples/functions/autoload.v4.t new file mode 100644 index 0000000..6d35d14 --- /dev/null +++ b/examples/functions/autoload.v4.t @@ -0,0 +1,184 @@ +#!/bin/bash + +workdir=$(mktemp -d) + +cp autoload $workdir + +cd $workdir +pwd + +. ./autoload + +funclist='ALTEST_func1 ALTEST_funcexport ALTEST_funcu' +for funcname in $funclist; do + cat <<EOFFUNC > $funcname +$funcname () +{ +echo this is $funcname + +} +EOFFUNC + +done + +export FPATH=$workdir + +autoload ALTEST_func1 ALTEST_funcu +autoload -x ALTEST_funcexport + +ok=0 +failed=0 + +for funcname in $funclist; do + + testname="$funcname loaded" + got=$(type $funcname 2>&1) + if [[ $got =~ "$funcname: not found" ]]; then + echo "## Failed $testname" + ((failed+=1)) + else + echo "ok - $testname" + ((ok+=1)) + + testname="$funcname is a shim" + if [[ ! $got =~ "IS_SHIM" ]]; then + echo "## Failed $testname" + ((failed+=1)) + else + echo "ok - $testname" + ((ok+=1)) + + testname="$funcname shim executed" + $funcname > /dev/null + got=$(type $funcname 2>&1) + if [[ $got =~ "IS_SHIM" ]]; then + echo "## Failed $testname" + ((failed+=1)) + else + echo "ok - $testname" + ((ok+=1)) + fi + fi + fi +done + +funcname=ALTEST_func1 +testname="$funcname shim reloaded" +autoload -r $funcname +got=$(type $funcname 2>&1) +if [[ ! $got =~ "IS_SHIM" ]]; then + echo "## Failed $testname" + ((failed+=1)) +else + echo "ok - $testname" + ((ok+=1)) +fi + +funcname=ALTEST_funcu +testname="$funcname shim unloaded" +autoload -u $funcname +got=$(type $funcname 2>&1) +if [[ ! $got =~ "$funcname: not found" ]]; then + echo "## Failed $testname" + ((failed+=1)) +else + echo "ok - $testname" + ((ok+=1)) +fi + +testname="autoload -p" +got=$(autoload -p | grep ALTEST) +if [[ ! $got =~ "autoload ALTEST_func1" ]] || \ + [[ ! $got =~ "autoload ALTEST_funcexport" ]] ; then +echo "## Failed $testname" + ((failed+=1)) +else + echo "ok - $testname" + ((ok+=1)) +fi + +testname="autoload -s" +echo "Executing $testname, could take a long time..." +got=$(autoload -s | grep ALTEST) +if [[ ! $got =~ "ALTEST_func1 not exported not executed" ]] || \ + [[ ! $got =~ "ALTEST_funcexport exported executed" ]] ; then + echo "## Failed $testname" + echo "## got: $got" + ((failed+=1)) +else + echo "ok - $testname" + ((ok+=1)) +fi + +testname="autoload -r -a $FPATH" +autoload -r -a $FPATH +localfailed=0 +localok=0 +for funcname in $funclist; do + got=$(type $funcname 2>&1) + if [[ $got =~ "$funcname: not found" ]]; then + echo "## Failed $testname - $funcname" + ((localfailed+=1)) + else + ((localok+=1)) + if [[ ! $got =~ "IS_SHIM" ]]; then + ((localfailed+=1)) + else + ((localok+=1)) + fi + fi +done +if ((localfailed==0)); then + echo "ok - $testname" + ((ok+=1)) +else + ((failed+=1)) +fi + +testname="autoload -u $funclist" +autoload -u $funclist +localfailed=0 +localok=0 +for funcname in $funclist; do + got=$(type $funcname 2>&1) + if [[ ! $got =~ "$funcname: not found" ]]; then + echo "## Failed $testname - $funcname" + ((localfailed+=1)) + else + ((localok+=1)) + fi +done +if ((localfailed==0)); then + echo "ok - $testname" + ((ok+=1)) +else + ((failed+=1)) +fi + +testname="autoload -r -f" +autoload -r -f +localfailed=0 +localok=0 +for funcname in $funclist; do + got=$(type $funcname 2>&1) + if [[ $got =~ "$funcname: not found" ]]; then + echo "## Failed $testname - $funcname" + ((localfailed+=1)) + else + ((localok+=1)) + if [[ ! $got =~ "IS_SHIM" ]]; then + ((localfailed+=1)) + else + ((localok+=1)) + fi + fi +done +if ((localfailed==0)); then + echo "ok - $testname" + ((ok+=1)) +else + ((failed+=1)) +fi + +echo $ok passed, $failed failed +exit $failed |