Custom Bash Tab Completion

Bri Hatch Personal Work
bri@ifokr.org ExtraHop Networks
bri@extrahop.com

Copyright 2022, Bri Hatch, Creative Commons BY-NC-SA License

What's tab completion?

Completing program names
$ pa<tab>
pagesize      parldyn       patch
pagestuff     pass          pathchk
par.pl        passwd        pax
parl          paste

$ pas<tab>
pass        passwd      paste

$ passw<tab>
$ passwd █

$ pag<tab>
pagesize    pagestuff
$ pages█

What's tab completion? (cont)

Completing options
$ grep --l<tab>
--label=         --line-buffered  --line-number    --line-regexp

$ grep --li<tab>
$ grep --line-<tab>
--line-buffered  --line-number    --line-regexp

$ grep --line-b<tab>
$ grep --line-buffered █

What's tab completion? (cont)

Completing filenames
 $ ls foofi<tab>
 $ ls foofile.txt █
 $ ls foofile.txt bar<tab>
 $ ls foofile.txt barfile.txt █

What's tab completion? (cont)

Completing Filenames
$ scp foofi<tab>
$ scp foofile.txt █
$ scp foofile.txt bar<tab>
$ scp foofile.txt barfile.txt █
$ scp foofile.txt barfile.txt loca<tab>

What's tab completion? (cont)

Completing Intelligently!
$ scp foofi<tab>
$ scp foofile.txt █
$ scp foofile.txt bar<tab>
$ scp foofile.txt barfile.txt █
$ scp foofile.txt barfile.txt loca<tab>
$ scp foofile.txt barfile.txt localhost:█
$ scp foofile.txt barfile.txt localhost:<tab>

What's tab completion? (cont)

Completing Intelligently!
$ scp foofi<tab>
$ scp foofile.txt █
$ scp foofile.txt bar<tab>
$ scp foofile.txt barfile.txt █
$ scp foofile.txt barfile.txt loca<tab>
$ scp foofile.txt barfile.txt localhost:█
$ scp foofile.txt barfile.txt localhost:<tab>
$ scp foofile.txt barfile.txt localhost:/home/wbagg/█

Use your time wisely

Before adding completion, be sure the time savings is worth it

(There's always a relevant xkcd...)

XKCD 1205 Is It Worth The Time chart
xkcd 1205

Our First Completion

What success will look like
$ source frobnicate_completion.sh

$ frobnicate <tab>
--bar     --baz     --foo
$ frobnicate --b<tab>
--bar     --baz
$ frobnicate --ba<tab>
--bar     --baz
$ frobnicate --baz<tab>
$ frobnicate --baz █
$ frobnicate --baz <tab>
--bar     --baz     --foo
$ frobnicate --baz --f<tab>
$ frobnicate --baz --foo █

Bash Completion Overview

Overall flow
  • Create completion function(s)
  • Register functions via complete
  • Assure code is read on login (.bashrc, ~/.config/bash_completion, /usr/share/bash-completion/completions/)

Bash Completion Variables

Completion Variables
COMP_WORDS
an array of the words in the command line
COMP_CWORD
index of the current word being edited/completed
COMPREPLY
array you set with possible completion values

COMP_LINE
the complete command line string
COMP_POINT
current cursor offset in command line

Bash Completion Commands

Completion Commands
compgen
generates possible matches
compgen -W "-a --bunch --of --possible --words" -- "--what-they-said"

complete
tells bash when and how to run your function
complete -F functioname   programname

Our First Completion - success!

$ source frobnicate_completion.sh
$ cat frobnicate_completion.sh
_frobnicate () {
    COMPREPLY=(
        $(compgen -W "--foo --bar --baz" -- "${COMP_WORDS[$COMP_CWORD]}")
    )
}
complete -F _frobnicate frobnicate


$ frobnicate --b<tab>
--bar     --baz

$ frobnicate --f<tab>
$ frobnicate --foo █

...

Robust Example

$ frobnicate -h
usage: frobnicate [options]

options:
  -h                    show this help message and exit
  -v                    be verbose
  -d                    show debugging output
  --assignee ASSIGNEE   person who should be assigned the issue
  --reporter REPORTER   person who reported the issue
  --description "..."   The description of the issue

Robust Example (cont)

_frobnicate () {
    local cur prev
    local allargs possibilities
    cur="${COMP_WORDS[$COMP_CWORD]}"
    prev="${COMP_WORDS[$(( $COMP_CWORD - 1 ))]}"

    allargs=" --reporter --assignee -h -v -d --description "

    # lots of logic
    # that sets the 'possibilities'
    # variable here

    if [ -n "$possibilities" ] ; then
        COMPREPLY=(
            $(compgen -W "$possibilities" -- "$cur" )
        )
    fi
}
complete -F _frobnicate frobnicate

Robust Example (cont)

Digging in...
 allargs=" --reporter --assignee -h -v -d --description "

 if [ "$COMP_CWORD" = 1 ] ; then
     possibilities="$allargs"

 # Our previous argument is explicitly one of the 
 # options we support!
 elif [[ "$allargs" == *" $prev "* ]] ; then

     # Lots of stuff to handle each of the different options
     # as appropriate and set the "possibliities" variable

 else
     possibilities="$allargs"
 fi

Robust Example (cont)

The inner block

  # Our previous option expects an arbitrary string
  # No autocomplete is possible
  if [ "$prev" = "--description" ] ; then        
      return

  # they want help? try to get them to hit 'enter' now
  elif [ "$prev" = "-h" ] ; then
      return

Robust Example (cont)

The inner block continued

  # Our previous option was one of the options
  # that does not take any value, so all options
  # are on the table
  elif [ "$prev" = "-v" -o "$prev" = "-d" ] ; then
      possibilities="$allargs"

  # Our previous option expects a username
  elif [ "$prev" = "--reporter" -o "$prev" = "--assignee" ] ; then
      possibilities=$(getent passwd | awk -F: '$3 > 1000 {print $1}')
  fi

Thanks!

Presentation: https://www.ifokr.org/bri/presentations/seagl-2022-bash-completions/

PersonalWork
Bri Hatch
bri@ifokr.org

Bri Hatch
ExtraHop Networks
bri@extrahop.com

Copyright 2022, Bri Hatch, Creative Commons BY-NC-SA License

Bonus Slides!

Just in case....

Other complete/compopt features

compopt -o [complete option]
for this completion, use different complete defaults
complete [-abcdefgjksuv] [-o comp-option] [-A action]
      [-G globpat] [-W wordlist] [-P prefix] [-S suffix]
      [-X filterpat] [-F function] [-C command] name [name ...]

Other complete features

complete -o bashdefault
If no completion matches, fall back to bash's default completion
complete -o default
If no completion matches, fall back to readline's default completion
complete -o dirnames
If no completion matches, do directory name completion
complete -o filenames
Assume completion matches are filenames and perform filename processing (e.g. trailing slashes)
complete -o nospace
Do not append space when completing
complete -o plusdirs
Add directory matches to any completion matches

Gleaning completions

Some tools bring their own!
eval "$(kubectl completion bash)"

shtab for python generates from your parser object

$ shtab --shell=bash myprogram.main.parser

# Or in the code
import shtab
print(shtab.complete(parser, shell="bash"))