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
|