DEV Community

ZaneHannanAU
ZaneHannanAU

Posted on

bash - lazy completion evaluation.

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)