1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 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
Programmer.
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 consequence 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
|