lazy completion evaluation in bash
tl;dr: loading completions lazily can help with terminal init speed, reduce memory usage (if only slightly), and use expensive functions (eg generators) once only.
lazy evaluation comes with a lot of positives-- less memory, less parsing, less evaluation, and you only pay for what you use. So, what can we do with it in bash?
well, here's a stupid (well, requires some jumps) means of setting it up:
function __lazyfunc { if [[ -z "$(type -t $1)" || $LAZYFUNC_FORCE_REDEFINE ]]; then local fn="$1" shift eval "$fn () { unset -f $fn ; eval \"\$( $@ )\" ; $fn \$@ ; }" fi }
so what does it do?
- only define if not declared. If you rerun
. .bashrc
on this, it doesn't override the values. - (locally) take the first argument, shift it off the front
- evaluate a string defining a function
that's it
of course, there are some downsides--
- most declarations are not intended to be done like this
- it takes some editing to rewrite the available source
for example, with this, rather than just
eval "$(pandoc --bash-completion)"
it now takes
__lazyfunc _pandoc pandoc --bash-completion
and it gets worse for others. eg, nvm:
export NVM_DIR="$HOME/.nvm" __lazyfunc __nvm 'cat $NVM_DIR/bash_completion' __lazyfunc nvm 'cat $NVM_DIR/nvm.sh' complete -o default -F __nvm nvm
cargo and rustup?:
__lazyfunc _rustup rustup completions bash rustup __lazyfunc _cargo 'cat $(rustc --print sysroot)/etc/bash_completion.d/cargo' complete -F _rustup -o bashdefault -o default rustup complete -F _cargo cargo
in use
using the above settings, with a few moments of loading (and adding time
before eval call),
$ nvm a real 0m0.007s user 0m0.006s sys 0m0.001s lias ^C $ cargo a real 0m0.142s user 0m0.089s sys 0m0.042s dd ^C $ rustup real 0m0.023s user 0m0.012s sys 0m0.011s completions dump-testament man show -v component -h override target -V default --help run toolchain --verbose doc help self uninstall --version docs install set update which $ rustup ^C $ pandoc real 0m1.221s user 0m0.302s sys 0m0.202s $ pandoc $ npm real 0m0.954s user 0m0.535s sys 0m0.351s v12.2.0 real 0m2.186s user 0m1.153s sys 0m1.058s $ node real 0m0.993s user 0m0.524s sys 0m0.404s Welcome to Node.js v12.2.0. Type ".help" for more information. >
(node and npm are loaded through nvm and have their own macros, so this takes a bit longer than usual)
function node { unset -f node npm local which=`nvm which | tail -n 1` NODE_ICU_DATA="$(dirname "$(dirname "$which")")/lib/node_modules/full-icu" PATH="$PATH:$(dirname "$which")" $which $@ } function npm { >&2 node -v npm $@ } __lazyfunc _npm_completion npm completion complete -F _npm_completion npm
now, what good is it?
- on a computer with slow disk access, this greatly decreases time between starting and having access to a terminal
- on a phone (termux), this also decreases time between startup
of course, there are some downsides
- initiation of a function can add time to initial completion
- this initial setup can be annoying
now, I doubt very much that I'm the first to post about it; but it is worth the time.
investigation
as you can guess, this means it hot-swaps the functions. so, what's the difference?
$ type _cargo _cargo is a function
_cargo () { unset -f _cargo; eval "$( cat $(rustc --print sysroot)/etc/bash_completion.d/cargo )"; _cargo $@ }
$ type __nvm __nvm is a function
__nvm () { unset -f __nvm; eval "$( cat $NVM_DIR/bash_completion )"; __nvm $@ }
$ nvm alias ^C $ type __nvm __nvm is a function
__nvm () { declare previous_word; previous_word="${COMP_WORDS[COMP_CWORD - 1]}"; case "${previous_word}" in use | run | exec | ls | list | uninstall) __nvm_installed_nodes ;; alias | unalias) __nvm_alias ;; *) __nvm_commands ;; esac; return 0 }
Top comments (0)