Saturday, April 16, 2011

Writing your own Bash Completion Function

Bash programmable completion is a powerful feature which allows you to specify how arguments to commands should be completed. You do this using the complete command. For example, you can set completion up so that when you type the unzip command and hit the TAB key, it only shows completions for files ending with the .zip extension. Similarly, the completion for the ssh command would display hosts taken from your known_hosts file.

In this post, I will describe how you can write a custom completion function for a command foo. Bash will execute this function when foo [TAB][TAB] is typed at the prompt and will display possible completions.

Bash uses the following variables for completion:

  • COMPREPLY: an array containing possible completions as a result of your function
  • COMP_WORDS: an array containing individual command arguments typed so far
  • COMP_CWORD: the index of the command argument containing the current cursor position
  • COMP_LINE: the current command line
Therefore, if you want the current argument that you are trying to complete, you would index into the words array using: ${COMP_WORDS[COMP_CWORD]}.

So, how do you build the result array COMPREPLY? The easiest way is to use the compgen command. You can supply a list of words to compgen and a partial word, and it will show you all words that match it. Let's try it out:

sharfah@starship:~> compgen -W "mars twix twirl" tw
twix
twirl

Now we have everything we need to write our completion function:

_foo()
{
    local cur=${COMP_WORDS[COMP_CWORD]}
    COMPREPLY=( $(compgen -W "alpha beta bar baz" -- $cur) )
}
complete -F _foo foo
Save this. Mine is in ~/.bash_completion.d/foo

Demo

sharfah@starship:~> . ~/.bash_completion.d/foo
sharfah@starship:~> foo ba[TAB][TAB]
bar
baz

A Bigger Example
Here is a meatier example of Bash completion. It shows how to complete Autosys commands such as sendevent and autorep. It completes command options, events which can be sent to jobs and job names which are obtained from a file. In some cases, the completions depend on the previous argument e.g. if the previous argument is -J then we know that we have to complete job names.

# a file containing job names
export AUTOSYS_JOBFILE=~/.autosysjobs

# complete autosys jobs using the job file
_autosysjobs()
{
  local cur=${COMP_WORDS[COMP_CWORD]}
  [ ! -z ${AUTOSERV} ] && \
    COMPREPLY=( $( compgen -W "$(cat ${AUTOSYS_JOBFILE}_${AUTOSERV})" -- $cur ) )
  return 0
}

# complete sendevent
_sendevent()
{
  local cur=${COMP_WORDS[COMP_CWORD]}
  local prev=${COMP_WORDS[COMP_CWORD-1]}

  case "$prev" in
   -S)
     COMPREPLY=( $( compgen -W "$(cat $AUTOSYS_HOSTFILE)" -- $cur ) )
     return 0
     ;;
   -E)
     COMPREPLY=( $( compgen -W "STARTJOB KILLJOB DELETEJOB \
                    FORCE_STARTJOB JOB_ON_ICE JOB_OFF_ICE \
                    JOB_ON_HOLD JOB_OFF_HOLD CHANGE_STATUS \
                    STOP_DEMON CHANGE_PRIORITY COMMENT \
                    ALARM SET_GLOBAL SEND_SIGNAL" -- $cur ) )
     return 0
     ;;
   -s)
     COMPREPLY=( $( compgen -W "RUNNING STARTING SUCCESS \
                    FAILURE INACTIVE TERMINATED" -- $cur ) )
     return 0
     ;;
   -J)
     _autosysjobs
     ;;
  esac

  # completing an option
  if [[ "$cur" == -* ]]; then
          COMPREPLY=( $( compgen -W "-E -S -A -J -s -P \
                       -M -q -G -C -U -T -K" -- $cur ) )
  fi
}
complete -F _sendevent sendevent

# complete autorep
_autorep()
{
  local cur=${COMP_WORDS[COMP_CWORD]}
  local prev=${COMP_WORDS[COMP_CWORD-1]}

  case "$prev" in
   -J)
      _autosysjobs
     ;;
  esac

  # completing an option
  if [[ "$cur" == -* ]]; then
          COMPREPLY=( $( compgen -W "-J -d -s -q -o \
                    -w -r -L -z -G -M -D" -- $cur ) )
  fi
}
complete -F _autorep autorep
Related Posts:
My Bash Profile - Part III: Completions

6 comments:

  1. Thanks for your post mate, it was enough to get me going writing my own complete functions!

    ReplyDelete
  2. Finally someone explains it a way a human being can understand it!

    ReplyDelete
  3. This was great! I created a similar example on runnable so anyone who is interested can play around with it quickly http://runnable.com/Uug-FAUPXc4hAADF/autocomplete-for-bash

    ReplyDelete
  4. Simply amazing. Well written!

    ReplyDelete
  5. finally i understand how to make cli apps more useful with autocompletion. Thanks.

    ReplyDelete
  6. It took me so long to find a straight forward explanation of this. Thanks for your write up.

    ReplyDelete

Note: Only a member of this blog may post a comment.