]> git.ipfire.org Git - thirdparty/bash.git/blob - examples/functions/autoload.v4
bash-5.1 distribution sources and documentation
[thirdparty/bash.git] / examples / functions / autoload.v4
1 ## -*- sh -*-
2
3 # The psuedo-ksh autoloader.
4
5 # How to use:
6 # o One function per file.
7 # o File and function name match exactly.
8 # o File is located in a directory that is in FPATH.
9 # o This script (autoload) must be sourced in as early as possible. This
10 # implies that any code in this script should NOT rely on any library of local
11 # or self-defined functions having already been loaded.
12 # o autoload must be called for each function before the function can be used. If
13 # autoloads are in directories where there are nothing but autoloads, then
14 # 'autoload /path/to/files/*' suffices (but see options -a and -f).
15 # o The call must be made in the current environment, not a subshell.
16 # o The command line suffices as "current environment". If you have autoload
17 # calls in a script, that script must be dotted into the process.
18
19 # The first cut of this was by Bill Trost, trost@reed.bitnet.
20 # The second cut came from Chet Ramey, chet@ins.CWRU.Edu
21 # The third cut came from Mark Kennedy, mtk@ny.ubs.com. 1998/08/25
22 # The fourth cut came from Matthew Persico, matthew.persico@gmail.com 2017/August
23
24 autoload_calc_shimsize ()
25 {
26 echo $((AUTOLOAD_SHIM_OVERHEAD + 3 * ${#1}))
27 }
28
29 _autoload_split_fpath ()
30 {
31 (IFS=':'; set -- ${FPATH}; echo "$@")
32 }
33
34 _aload()
35 {
36 local opt OPTIND
37 local doexport=0
38 local doreload=0
39 local doverbose=0
40 local doevalshim=0
41 local loadthese
42 local optimize=0
43 local loaded=0
44 local exported=0
45 local optimized=0
46 local summary=0
47 local dofpath=0
48 while getopts xrvla:oyf opt; do
49 case $opt in
50 x) doexport=1;;
51 r) doreload=1;;
52 v) doverbose=1;;
53 l) doevalshim=1;;
54 a) loadthese=$(find $OPTARG -maxdepth 1 -type f -printf '%f ');;
55 o) optimize=1;;
56 y) summary=1;;
57 f) loadthese=$(find $(_autoload_split_fpath) -maxdepth 1 -type f -printf '%f ');;
58 *) echo "_aload: usage: _aload [-xrvlyf] [-a dir] [function ...]" >&2; return;;
59 esac
60 done
61
62 shift $(($OPTIND-1))
63
64 [ -z "$loadthese" ] && loadthese="$@"
65
66 local func
67 for func in $loadthese; do
68 local exists_fn
69 exists_fn=$(declare -F $func)
70 if [ -n "$exists_fn" ] && ((doreload==0)) && ((doevalshim==0))
71 then
72 if ((doverbose))
73 then
74 echo "autoload: function '$func' already exists"
75 fi
76 else
77 local andevaled=''
78 local andexported=''
79 local evalstat=0
80 local doshim=1
81 local funcfile
82 funcfile=$(_autoload_resolve $func)
83 if [[ $funcfile ]] ; then
84 ## The file was found for $func. Process it.
85
86 if ((optimize)); then
87 ## For the first function loaded, we will not know
88 ## AUTOLOAD_SHIM_OVERHEAD. We can only calculate it after
89 ## we have loaded one function.
90 if [[ $AUTOLOAD_SHIM_OVERHEAD ]]; then
91 local size=$(wc -c $funcfile| sed 's/ .*//')
92 local shimsize=$(autoload_calc_shimsize $func)
93 if (( size <= shimsize)); then
94 doshim=0
95 andevaled=', optimized'
96 ((optimized+=1))
97 fi
98 fi
99 fi
100
101 if ((doevalshim)); then
102 doshim=0
103 andevaled=', evaled'
104 fi
105
106 ## 'brand' as in branding a cow with a mark. We add a local
107 ## variable to each function we autoload so that we can tell
108 ## later on it is an autoloaded function without having to
109 ## maintain some bash array or hash that cannot be passed to
110 ## and used by subshells.
111 local brandtext
112 brandtext="eval \"\$(type $func | sed -e 1d -e 4ilocal\\ AUTOLOADED=\'$func\')\""
113 if ((doshim)); then
114 ## Don't bother trying to save space by shoving all the
115 ## eval text below onto one unreadable line; new lines will
116 ## be added at your semicolons and any indentation below
117 ## seems to be ignored anyway if you export the function;
118 ## look at its BASH_FUNCTION representation.
119 eval $func '()
120 {
121 local IS_SHIM="$func"
122 local file=$(_autoload_resolve '$func')
123 if [[ $file ]]
124 then
125 . $file
126 '$brandtext'
127 '$func' "$@"
128 return $?
129 else
130 return 1;
131 fi
132 }'
133 else
134 . $funcfile
135 eval "$brandtext"
136 fi
137 evalstat=$?
138 if((evalstat==0))
139 then
140 ((loaded+=1))
141 ((doexport)) && export -f $func && andexported=', exported' && ((exported+=1))
142 ((doverbose)) && echo "$func autoloaded${andexported}${andevaled}"
143 if [[ ! $AUTOLOAD_SHIM_OVERHEAD ]] && ((doshim)); then
144 ## ...we have just loaded the first function shim into
145 ## memory. Let's calc the AUTOLOAD_SHIM_OVERHEAD size
146 ## to use going forward. In theory, we could check
147 ## again here to see if we should optimize and source
148 ## in this function, now that we now the
149 ## AUTOLOAD_SHIM_OVERHEAD. In practice, it's not worth
150 ## duping that code or creating a function to do so for
151 ## one function.
152 AUTOLOAD_SHIM_OVERHEAD=$(type $func | grep -v -E "^$1 is a function" | sed "s/$func//g"| wc -c)
153 export AUTOLOAD_SHIM_OVERHEAD
154 fi
155 else
156 echo "$func failed to load" >&2
157 fi
158 fi
159 fi
160 done
161 ((summary)) && echo "autoload: loaded:$loaded exported:$exported optimized:$optimized overhead:$AUTOLOAD_SHIM_OVERHEAD bytes"
162 }
163
164 _autoload_dump()
165 {
166 local opt OPTIND
167 local opt_p=''
168 local opt_s=''
169 while getopts ps opt
170 do
171 case $opt in
172 p ) opt_p=1;;
173 s ) opt_s=1;;
174 esac
175 done
176
177 shift $(($OPTIND-1))
178
179 local exported=''
180 local executed=''
181 local func
182 for func in $(declare | grep -E 'local\\{0,1} AUTOLOADED' | sed -e "s/.*AUTOLOADED=//" -e 's/\\//g' -e 's/[");]//g' -e "s/'//g")
183 do
184 if [ -n "$opt_p" ]; then echo -n "autoload "; fi
185 if [ -n "$opt_s" ]
186 then
187 exported=$(declare -F | grep -E "${func}$" | sed 's/declare -f\(x\{0,1\}\).*/\1/')
188 [ "$exported" = 'x' ] && exported=' exported' || exported=' not exported'
189 executed=$(type $func | grep 'local IS_SHIM')
190 [ -z "$executed" ] && executed=' executed' || executed=' not executed'
191 fi
192 echo "${func}${exported}${executed}"
193 done
194 }
195
196 _autoload_resolve()
197 {
198 if [[ ! "$FPATH" ]]; then
199 echo "autoload: FPATH not set or null" >&2
200 return
201 fi
202
203 local p # for 'path'. The $() commands in the for loop split the FPATH
204 # string into its constituents so that each one may be processed.
205
206 for p in $( _autoload_split_fpath ); do
207 p=${p:-.}
208 if [ -f $p/$1 ]; then echo $p/$1; return; fi
209 done
210
211 echo "autoload: $1: function source file not found" >&2
212 }
213
214 _autoload_edit()
215 {
216 [ -z "$EDITOR" ] && echo "Error: no EDITOR defined" && return 1
217 local toedit
218 local func
219 for func in "$@"
220 do
221 local file=$(_autoload_resolve $func)
222 if [[ $file ]]
223 then
224 toedit="$toedit $file"
225 else
226 echo "$funcname not found in FPATH funcfile. Skipping."
227 fi
228 done
229
230 [ -z "$toedit" ] && return 1
231
232 local timemarker=$(mktemp)
233
234 $EDITOR $toedit
235
236 local i
237 for i in $toedit
238 do
239 if [ $i -nt $timemarker ]
240 then
241 local f=$(basename $i)
242 echo Reloading $f
243 autoload -r $f
244 fi
245 done
246 }
247
248 _autoload_page()
249 {
250 [ -z "$PAGER" ] && echo "Error: no PAGER defined" && return 1
251 local topage
252 local func
253 for func in "$@"
254 do
255 local file=$(_autoload_resolve $func)
256 if [[ $file ]]
257 then
258 topage="$topage $file"
259 else
260 echo "$funcname not found in FPATH funcfile. Skipping."
261 fi
262 done
263
264 [ -z "$topage" ] && return 1
265
266 $PAGER $topage
267 }
268
269 _autoload_remove()
270 {
271 unset -f "$@"
272 }
273
274 _autoload_help()
275 {
276 cat <<EOH
277 NAME
278 autoload
279
280 SYNOPSIS
281 autoload [-ps]
282 autoload [-xuremloyv] [function ...]
283 autoload -a directory [-oyv]
284 autoload -f [-oyv]
285 autoload [-h]
286
287 autoreload [function ...]
288
289 DESCRIPTION
290
291 An implementation of the 'autoload' functionality built into other
292 shells, of which 'ksh' is the most prominent. It allows for a keeping
293 the process environment small by loading small 'shim' functions into
294 memory that will, on first call, load the full text of the given
295 function and run it. Subsequent calls to the function just run the
296 function.
297
298 'autoreload' is a synonym for 'autoload -r'. See below.
299
300 USAGE
301
302 o Each function to be autoloaded should be defined in a single file,
303 named exactly the same as the function.
304
305 o In order to avoid side effects, do NOT put code other than the
306 function definition in the file. Unless of course you want to do some
307 one-time initialization. But beware that if you reload the function
308 for any reason, you will rerun the initialization code. Make sure
309 your initialization is re-entrant. Or, better yet,
310
311 *** do NOT put code other than the function definition in the file ***
312
313 o These function definition files should be placed in a directory that
314 is in the FPATH environment variable. Subdirectories are NOT scanned.
315
316 o The autoload script should be sourced into the current process as
317 early as possible in process start up. See NOTES below for
318 suggestions.
319
320 o The calls to the autoload function must be made in the current
321 process. If your calls are in their own script, that script must be
322 sourced in. Command line invocations are also sufficient. (But see
323 '-l' below.)
324
325 o The first time the function is called, the shim function that was
326 created by the 'autoload' call is what is executed. This function
327 then goes and finds the appropriate file in FPATH, sources it in and
328 then calls the actual function with any arguments you just passed in
329 to the shim function. Subsequent calls just run the function.
330
331 OPTIONS
332
333 -a Autoload (a)ll the functions found in the given directory.
334
335 -f Autoload all the functions found in all the directories on the
336 FPATH.
337
338 -p Print all the autoloaded functions.
339
340 -s Print all the autoloaded functions and add their export status.
341
342 -x Export the specified functions to the environment for use in
343 subshells.
344
345 -u Unset the function, so it can be reloaded.
346
347 -r Reload the shims of the specified functions, even if the functions
348 have been already been executed. This will allow you to modify the
349 functions' source and have the new version executed next time the
350 function is called.
351
352 It would be very easy to modify a function's script, run the
353 function and scratch your head for a long time trying to figure out
354 why your changes are not being executed. That's why we provide the
355 '-e' flag described below for modifications.
356
357 Reloads, of course, only apply in the context of the current session
358 and any future subshell you start from the current session. Existing
359 sessions will need to have the same 'autoload -r' command run in
360 them.
361
362 -e Find the scripts in which the specified functions are defined and
363 start up \$EDITOR on those scripts. Reload the ones that were
364 modified when you exit \$EDITOR. (Note: If you use 'autoload -e foo'
365 to edit function 'foo', and then in your editor you separately load
366 up function 'bar', 'autoload' has no way of knowing that you edited
367 'bar' and will NOT reload 'bar' for you.)
368
369 Reloads, of course, only apply in the context of the current session
370 and any future subshell you start from the current session. Existing
371 sessions will need to have the same 'autoload -r' command run in
372 them.
373
374 -m Find the scripts in which the specified functions are defined and
375 run \$PAGER on them ('m' is for 'more', because 'p' (page) and 'l'
376 (load) are already used as options in 'autoload').
377
378 -l When autoloading a function, eval the shim immediately in order to
379 load the true function code. See "Using '-l'" in the NOTES below for
380 details.
381
382 -o Optimize. When autoloading, take the time to execute
383
384 'theCharCount=\$(wc -c \$theFuncFile)'
385
386 for each function and
387
388 if \$theCharCount < \$AUTOLOAD_SHIM_OVERHEAD
389
390 don't shim it, just eval directly.
391
392 -y Summar(y). Print the number of loaded, exported and optimized
393 functions.
394
395 -v Turns up the chattiness.
396
397 NOTES
398
399 o Calling 'autoload' on a function that already exists (either shimmed
400 or expanded) silently ignores the request to load the shim unless it
401 has been previously removed (-u) or you force the reload (-r).
402
403 o Changing and reloading a function that has been exported does not
404 require it be re-exported; the modifications will appear in
405 subsequent subshells.
406
407 o Using '-1'
408
409 If you are running under set -x and/or set -v, you may see that the
410 shim does not appear to "work"; instead of seeing the shim first and
411 the real code subsequently, you may see the shim evaluated multiple
412 times.
413
414 This may not be an error; review your code. What is most likely
415 happening is that you are calling the function in subshells via
416 backticks or $(), or in a script that is not being sourced into the
417 current environment. If you have not previously called the function
418 in question at your command line or in a script that was sourced into
419 the current environment, then the various subshells are going to
420 encounter the shim and replace with the real code before executing.
421
422 Remember, however, that environment modifications that occur in a
423 subshell are NOT propagated back to the calling shell or over to any
424 sibling shells. So, if you call an autoloaded function in a very
425 tight loop of very many subshells, you may want to make an 'autoload
426 -l' call before you start your loop. '-l' will instruct 'autoload' to
427 bypass the shim creation and just source in the function's file
428 directly. For a few calls, the overhead of repeatedly running the
429 shim is not expensive, but in a tight loop, it might be. Caveat
430 Programer.
431
432 o Although the number of functions in the environment does not change
433 by using 'autoload', the amount of memory they take up can be greatly
434 reduced, depending on the size of your functions. If you have a lot
435 of small functions, then it is possible that the shim text will be
436 larger than your actual functions, rendering the memory savings moot.
437
438 'small' in this case can be determined by calling the function
439 'autoload_calc_shimsize' with the name of the function to determine
440 its shim size.
441
442 o In order to support the -p and -s options, we need a way to determine
443 if a function 'func' has been autoloaded or if it was loaded
444 diredctly. In order to do that, we modify the function's code by
445 adding the text
446
447 local AUTOLOADED='func';
448
449 to the shim and to the actual function text, just after the opening
450 brace. Then supporting -p and -s is just a matter of grepping through
451 all the function text in memory. Even though grepping through the
452 environment may not be the most efficient way to support this, it is
453 the simplest to implement for -p and -s operations that are not
454 heavily used.
455
456 As a consquence of this (and other reasons), the AUTOLOAD* namespace
457 is reserved for autoloading. Make sure you check any functions that
458 you bring under autoload for use of variables or functions that start
459 with AUTOLOAD and change them.
460
461 o The easiest way to load shims for all functions on the FPATH is to run
462
463 autoload -f -x
464
465 in the profile that gets run for login shells.
466
467 When called in the profile of a login shell where no definitions
468 exist, -f will load all functions it can find on FPATH and -x will
469 export all of those functions to be available in subshells when this
470 is called in a login shell. Using this option will relieve you of the
471 need to call 'autoload' after Every Single Function Definition, nor
472 will you need to call it in subshells.
473
474 The only thing left to do is to load up the autoload function itself
475 and its helper functions. That needs to happen in your profile:
476
477 export FPATH=~/functions # or wherever you stash them
478 if [ -z $(declare -F autoload) ]
479 then
480 . ~/bin/autoload # or wherever you've put it
481 fi
482
483 The 'if' statement is used to make sure we don't reload autoload
484 needlessly. Sourcing in the autoload script loads the 'autoload'
485 function and all of its support functions. Additionally, we export
486 all of these functions so that they are available in subshells; you
487 do not have to re-source the autoload file in '.bashrc'.
488
489 o Even with all of these shenanigans, you will find cases where no
490 matter how hard you try, your autoloaded functions will be
491 unavailable to you, even if you run 'autoload -x -f'. The typical
492 condition for this is starting up not a subshell, but a brand new
493 DIFFERENT shell. And the typical example of this is git extensions.
494
495 At the time of this writing, git extensions work by taking a command
496 'git foo' and looking for a file 'git-foo' on the path. 'git' then
497 executes 'git-foo' in a new shell - it executes your command in
498 /bin/sh. That's not a subshell of your process. It will not get your
499 exported shell functions. Ballgame over.
500
501 If you find that you want your functions to be available in such
502 circumstances, convert them back to plain old scripts, make sure they
503 are 'sh' compliant and take the read/parse hit every time they are
504 run.
505
506 EOH
507 }
508
509 autoload()
510 {
511 if (( $# == 0 )) ; then _autoload_dump; return; fi
512
513 local opt OPTIND OPTARG
514 local passthru
515 local dumpopt
516 while getopts psuema:yxrvlohf opt
517 do
518 case $opt in
519 p|s) dumpopt="$dumpopt -${opt}";;
520 u) shift $((OPTIND-1)); _autoload_remove "$@"; return;;
521 e) shift $((OPTIND-1)); _autoload_edit "$@"; return;;
522 m) shift $((OPTIND-1)); _autoload_page "$@"; return;;
523 x|r|v|l|y|f|o) passthru="$passthru -$opt";;
524 a) passthru="$passthru -$opt $OPTARG";;
525 h) _autoload_help; return;;
526 *) echo "autoload: usage: autoload [-puUx] [function ...]" >&2; return;;
527 esac
528 done
529
530 shift $(($OPTIND-1))
531 if [ -n "$dumpopt" ]
532 then
533 _autoload_dump $dumpopt
534 else
535 _aload $passthru "$@"
536 fi
537 }
538
539 autoreload ()
540 {
541 autoload -r "$@"
542 }
543
544 ## When we source in autoload, we export (but NOT autoload) the autoload
545 ## functions so that they are available in subshells and you don't have to
546 ## source in the autoload file in subshells.
547 export -f _aload \
548 _autoload_dump \
549 _autoload_edit \
550 _autoload_help \
551 _autoload_page \
552 _autoload_resolve \
553 _autoload_split_fpath \
554 autoload \
555 autoload_calc_shimsize \
556 autoreload