Linux: Complete original command argument with bash-completion

The bash-completion is a package for completing command argument. This article will describe how to implement bash-completion script for original command.

1 bash-completion

The bash-completion is a package for completing command argument. Each command package provides bash-completion's bash script (There are some command package which does not provides).

For example, find command provides bash-completion script. This will show candidate list for argument. An argument will be written to bash when there is one candidate.

$ find / -type
b  c  d  f  l  p  s

bash-completion script is loaded with . or source.

$ . <bash-completion>

bash-completion script in the following directory will be loaded when starting bash.

/etc/bash_completion.d/
/usr/share/bash-completion/completions/

2 bash-completion script

The __example_bash_completion implements completion for example command.

_have example && \
__example_bash_completion()
{
  # Create argument list with checking previous argument.
  # Create COMPREPLY from argument list.
} && \
complete -F __example_bash_completion example
  • The _have which is bash-completion's builtin function will check if example command is exists or not. If example command is exists, __example_bash_completion function will be defined, and the complete which is bash-completion's builtin function will be done.
  • The complete maps __example_bash_completion function and example command.
  • The __example_bash_completion function implements completion for example command with assigning candidates to COMPREPLY array.
  • The completion function name should have underscore prefix. This is for distinguish fompletion function name and command name. If completion function does not have underscore prefix, completion function will be show when command completion.
  • The 3rd argument of completion function is previous argument of command.The 2nd argument of completion function is current argument of command.
$ example         # ${3} is example, ${2} is empty.
$ exapmle arg     # ${3} is example, ${2} is arg.
$ exapmle arg1 ar # ${3} is arg1, ${2} is ar.

3 Example command which has ordered argument

Define the following command which has ordered argument.

$ food <food> <method>
  • <food> is "bacon" or "egg".
  • If <food> is "bacon", <method> is unused.
  • If <food> is "egg", <method> is "boiled" or "fried".

The all pattern of command is the following.

$ food bacon
$ food egg boiled
$ food egg fried

The bash-completion is the following.

_have food && \
__food_bash_completion()
{
  # Create argument list with checking previous argument.
  local args
  case "${3}" in
    food) args="bacon egg";;
    egg) args="boiled fried";;
    *) return;;
  esac

  # Create COMPREPLY from argument list.
  if [ -z "${2}" ]; then
    COMPREPLY=(${args})
  else
    local arg compreply=""
    # Append forward matched string with ${2}.
    for arg in ${args}; do
      [ "${arg#${2}}" != "${arg}" ] && compreply="${arg} ${compreply}"
    done
    COMPREPLY=(${compreply})
  fi
} && \
complete -F __food_bash_completion food
  • The [ -z "${2}" ] false case is, for example, when typing TAB key to "food b", Remove candidate which does not have "b" prefix.

4 Example command which has unordered argument

Define the following command which has unordered argument.

$ drink <drink> [--with <add>]
$ drink --with <add> <drink>
  • <drink> is "coffee" or "tea".
  • <add> is "sugar" or "milk".
  • Either <drink> or –with can be first.

The all pattern of command is the following.

$ drink coffee
$ drink coffee --with sugar
$ drink coffee --with milk
$ drink --with sugar coffee
$ drink --with milk coffee
$ drink tea
$ drink tea --with sugar
$ drink tea --with milk
$ drink --with sugar tea
$ drink --with milk tea

The bash-completion is the following.

_have drink && \
__drink_bash_completion()
{
  # Assigned variable by _init_completion.
  #   cur    Current argument.
  #   prev   Previous argument.
  #   words  Argument array.
  #   cword  Argument array size.
  local cur prev words cword
  _init_completion || return

  # Flag for checking if argument is used or not.
  local used_drink=0 used_with=0

  # Check used argument.
  local word
  for word in ${words[@]}; do
    case ${word} in
      coffee|tea) used_drink=1;;
      --with) used_with=1;;
    esac
  done

  # Create argument list with checking previous argument.
  local args
  case "${prev}" in
    --with)
      args="sugar milk"
      ;;
    *)
      # Append unused argument.
      [ ${used_drink} -eq 0 ] && args="coffee tea"
      [ ${used_with} -eq 0 ] && args="${args} --with"
      ;;
  esac

  # Create COMPREPLY from argument list.
  if [ -z "${cur}" ]; then
    COMPREPLY=(${args})
  else
    local arg compreply=""

    # Append matched string with cur.
    for arg in ${args}; do
      [ "${arg#${cur}}" != "${arg}" ] && compreply="${arg} ${compreply}"
    done
    COMPREPLY=(${compreply})
  fi
} && \
complete -F __drink_bash_completion drink
  • The _init_completion which is bash-completion's builtin function will get argument list which have been set already. The variables cur, prev, words and cword must be defined before calling _init_completion because these variables are used in _init_completion.
  • The cur is the same with the 2nd argument of completion function. The prev is the same with the 3rd argument of completion function.
  • The words have argument list which have been set already. The cwords is size of this array.
  • Using the words can check if <drink> argument is used or not, and check if –with argument is used or not.