Linux: bash-completionで独自コマンドの引数を補完する

bash-completionはbash上でコマンドに対する引数の補完処理を実装するパッケージです。この記事では独自に作成したコマンドに対する引数の補完処理を実装します。

1 bash-completion

各コマンドに対する引数の補完処理を実装するパッケージです。各コマンドのパッケージでbash-completionスクリプトを提供しています(パッケージによっては提供されないものもあります)。

例えば、findコマンドの-type引数の後でTABキーを押すと-typeに対する引数一覧が表示されます。候補がひとつだけの場合は自動的に入力されます。

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

bash-completionスクリプトは.やsourceで読み込みます。

$ . <bash-completion>

以下のディレクトリに格納することでbash起動時に自動的に読み込まれます。

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

2 bash-completionのスクリプト

ここではexampleコマンドに対する補完処理を__example_bash_completion関数で実装します。

_have example && \
__example_bash_completion()
{
  # Create argument list with checking previous argument.
  # Create COMPREPLY from argument list.
} && \
complete -F __example_bash_completion example
  • bash-completionの組込み関数_haveでexampleコマンドが存在するかどうかを確認します。存在する場合は__example_bash_completion関数が定義され、bash-completionの組込み関数completeが実行されます。
  • bash-completionの組込み関数completeで定義した__example_bash_completion関数をbash-completionへ組み込みます。ここでコマンドと補完処理用関数の対応付けがされます。
  • __example_bash_completion関数でexampleコマンドに対する補完処理を実装します。関数の目的はCOMPREPLY配列に補完候補の文字列を設定することです。
  • 補完処理用関数の名前はアンダースコアで始まるようにします。これはコマンド名と補完処理用関数を区別できるようにする為です。アンダースコアで始めない場合はコマンド名の補完処理中に補完処理用関数が表示されてしまいます。
  • 補完処理用関数の第3引数はコマンドに対する一つ前の引数です。第2引数はコマンドに対する現在の引数です。例えば以下のようになります。
$ 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 引数の順番が決まっているコマンドのbash-completionスクリプト

以下のような引数の順番が決まっているコマンドを定義します。

$ food <food> <method>
  • <food>にはbaconかeggを指定します。
  • baconの場合は<method>を指定しません。
  • eggの場合は<method>でboiledかfriedを指定します。

コマンド引数の全パターンは以下の通りです。

$ food bacon
$ food egg boiled
$ food egg fried

このコマンドに対するbash-completionスクリプトは以下のようになります。

_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
  • [ -z "${2}" ]がfalseのケースは、例えば"food b"の時にTABキーを押すことで、先頭にbを持つ引数に補完候補を絞る為に使われます。

4 引数の順番が決まっていないコマンドのbash-completionスクリプト

以下のような引数の順番が決まっていないコマンドを定義します。

$ drink <drink> [--with <add>]
$ drink --with <add> <drink>
  • <drink>にはcoffeeかteaを指定します。
  • <add>にはsugarかmilkを指定します。
  • <drink>と–withはどちらが先でも構いません。

コマンド引数の全パターンは以下の通りです。

$ 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

このコマンドに対するbash-completionスクリプトは以下のようになります。

_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
  • bash-completionの組込み関数_init_completionで、これまで指定された引数のリストを取得します。_init_completion呼び出し前にcur、prev、words、cwordという変数を定義しておく必要があります。これらの変数は_init_completionの内部で値が代入されます。
  • curは補完処理用関数の第2引数と同様です。prevは補完処理用関数の第3引数と同様です。
  • wordsはこれまでに設定された引数が格納されています。cwordはその要素の数です。
  • wordsを使うことで、<drink>引数がすで指定されているかどうか、–with引数がすでに指定されているかどうかを確認することができます。