- 1. Bash用の補完スクリプトの作り方
- 2. Bash用の補完スクリプトを実際に作ってみる(4種)
Bashの補完スクリプトの作り方をおさえたところで、
早速、4種のコマンドについての補完スクリプトを作成してみましょう。
vil
https://github.com/kusabashira/vil
Vim scriptでテキストを処理するコマンドです。
このコマンドの使い方は次のようになっています。
$ vil [OPTION]... {script-only-if-no-other-script} [input-file]...
Options:
-n, --quiet, --silent
suppress automatic printing of buffer
-e script, --expression=script
add the script to the commands to be executed
-f script-file, --file=script-file
add the contents of script-file to the commands to be executed
--help
display this help and exit
--version
display version information and exit
オプションと入力ファイルのパスを受け取る、よくある形のコマンドですね。
補完の基本方針としては、引数が-で始まっていればオプションを、そうでなければファイルパスを補完候補とするのがよさそうです。
例外として、-f、--fileの後はファイルパスを補完してほしいので、そのときだけ処理を変えるようにしたほうがよいでしょうか。
まず、ベースとして_init_completionを呼び出すだけのもの書きます。
このコマンドはロングオプションを取るため-sオプションを指定しておきます。
ファイル名はvil.bash
とし、ファイルを作成したらsource vil.bash
として読み込むようにします。
_vil(){local cur prev words cword split
_init_completion -s||return}complete-F _vil vil
これでcur、prev、words、cword、splitにアクセスできるようになりました。
次に引数が-で始まっていればオプションを、そうでなければファイルパスを補完するようにします。
_vil(){local cur prev words cword split
_init_completion -s||return
case$curin
-*)COMPREPLY=($(compgen-W'$(_parse_help vil)'--"$cur"));;*)
_filedir
;;esac}complete-F _vil vil
これでファイルパスとオプションが補完されるようになりました。
ただ、これでは=で終わるオプションを補完したときに、=の後ろにスペースがついてしまいます。
そのため、補完候補の末尾が=で終わっていたときは、compopt -o nospace
を使って、スペースがつかないようにします。
_vil(){local cur prev words cword split
_init_completion -s||return
case$curin
-*)COMPREPLY=($(compgen-W'$(_parse_help vil)'--"$cur"))[[$COMPREPLY==*=]]&& compopt -o nospace
;;*)
_filedir
;;esac}complete-F _vil vil
これで=で終わるオプションの後ろにスペースがつかないようになりました。
最後に、-f、--fileの後のファイルパスが補完されるようにします。
_vil(){local cur prev words cword split
_init_completion -s||return
case$previn-f|--file)
_filedir
return;;esac$split&&return
case$curin
-*)COMPREPLY=($(compgen-W'$(_parse_help vil)'--"$cur"))[[$COMPREPLY==*=]]&& compopt -o nospace
;;*)
_filedir
;;esac}complete-F _vil vil
これで完成です。
もっと詰めれば、-fの続きはファイルパスになるだとか、--の後の引数にオプションは来ないだとかを考慮してもいいですが、
bash-completionの公式でもそこまでは作り込んでいないのでこれで十分な品質です。
また、今回に関しては次のようにしてしまっても問題無いと思います。
complete-F _longopt vil
nano
ターミナルで動くテキストエディタです。
このコマンドの使い方は次のようになっています。
Usage: nano [OPTIONS] [[+LINE[,COLUMN]] FILE]...
To place the cursor on a specific line of a file, put the line number with
a '+' before the filename. The column number can be added after a comma.
When a filename is '-', nano reads data from standard input.
Option GNU long option Meaning
-A --smarthome Enable smart home key
-B --backup Save backups of existing files
-C <dir> --backupdir=<dir> Directory for saving unique backup files
-D --boldtext Use bold instead of reverse video text
-E --tabstospaces Convert typed tabs to spaces
-F --multibuffer Read a file into a new buffer by default
-G --locking Use (vim-style) lock files
-H --historylog Log & read search/replace string history
-I --ignorercfiles Don't look at nanorc files
-J <number> --guidestripe=<number> Show a guiding bar at this column
-K --rawsequences Fix numeric keypad key confusion problem
-L --nonewlines Don't add an automatic newline
-M --trimblanks Trim tail spaces when hard-wrapping
-N --noconvert Don't convert files from DOS/Mac format
-P --positionlog Log & read location of cursor position
-Q <regex> --quotestr=<regex> Regular expression to match quoting
-R --restricted Restricted mode
-T <#cols> --tabsize=<#cols> Set width of a tab to #cols columns
-U --quickblank Do quick statusbar blanking
-V --version Print version information and exit
-W --wordbounds Detect word boundaries more accurately
-X <str> --wordchars=<str> Which other characters are word parts
-Y <name> --syntax=<name> Syntax definition to use for coloring
-Z --zap Let Bsp and Del erase a marked region
-a --atblanks When soft-wrapping, do it at whitespace
-b --breaklonglines Automatically hard-wrap overlong lines
-c --constantshow Constantly show cursor position
-d --rebinddelete Fix Backspace/Delete confusion problem
-e --emptyline Keep the line below the title bar empty
-g --showcursor Show cursor in file browser & help text
-h --help Show this help text and exit
-i --autoindent Automatically indent new lines
-j --jumpyscrolling Scroll per half-screen, not per line
-k --cutfromcursor Cut from cursor to end of line
-l --linenumbers Show line numbers in front of the text
-m --mouse Enable the use of the mouse
-n --noread Do not read the file (only write it)
-o <dir> --operatingdir=<dir> Set operating directory
-p --preserve Preserve XON (^Q) and XOFF (^S) keys
-r <#cols> --fill=<#cols> Set width for hard-wrap and justify
-s <prog> --speller=<prog> Enable alternate speller
-t --tempfile Auto save on exit, don't prompt
-u --unix Save a file by default in Unix format
-v --view View mode (read-only)
-w --nowrap Don't hard-wrap long lines [default]
-x --nohelp Don't show the two help lines
-y --afterends Make Ctrl+Right stop at word ends
-z --suspend Enable suspension
-$ --softwrap Enable soft line wrapping
結構な分量がありますね。
しかし、オプションと入力ファイルのパスを受け取る、よくある形のコマンドであることに変わりはありません。
今回も補完の基本方針としては、引数が-で始まっていればオプションを、そうでなければファイルパスを補完候補とするのがよさそうです。
例外として、-C、--backupdirの後はディレクトリパスを、-o、--operatingdirの後はディレクトリパスを、-s、--spellerの後はコマンド名を補完してほしいので、そのときだけ処理を変えるようにしたほうがよいでしょうか。
あとは、--syntaxの後に来るシンタックスも補完できるといいのですが、シンタックス一覧を取得する手段が見当たらないので、その点は妥協します。
まず、ベースとして_init_completionを呼び出すだけのもの書きます。
このコマンドはロングオプションを取るため-sオプションを指定しておきます。
ファイル名はnano.bash
とし、ファイルを作成したらsource note.bash
として読み込むようにします。
_nano(){local cur prev words cword split
_init_completion -s||return}complete-F _nano nano
これでcur、prev、words、cword、splitにアクセスできるようになりました。
次に引数が-で始まっていればオプションを、そうでなければファイルパスを補完するようにします。
_nano(){local cur prev words cword split
_init_completion -s||return
case$curin
-*)COMPREPLY=($(compgen-W'--smarthome --backup --backupdir= --boldtext --tabstospaces --multibuffer --locking --historylog --ignorercfiles --guidestripe= --rawsequences --nonewlines --trimblanks --noconvert --positionlog --quotestr= --restricted --tabsize= --quickblank --version --wordbounds --wordchars= --syntax= --zap --atblanks --breaklonglines --constantshow --rebinddelete --emptyline --showcursor --help --autoindent --jumpyscrolling --cutfromcursor --linenumbers --mouse --noread --operatingdir= --preserve --fill= --speller= --tempfile --unix --view --nowrap --nohelp --afterends --suspend --softwrap'--"$cur"))[[$COMPREPLY==*=]]&& compopt -o nospace
;;*)
_filedir
;;esac}complete-F _nano nano
これでファイルパスとオプションが補完されるようになりました。
今回は残念ながら_parse_helpだけではオプション一覧を抽出できませんでした。そういうこともあります。
最後に-C、--backupdirの後はディレクトリパスを、-o、--operatingdirの後はディレクトリパスを、-s、--spellerの後はコマンド名を補完するようにします。
_nano(){local cur prev words cword split
_init_completion -s||return
case$previn-C|--backupdir)
_filedir -dreturn;;-o|--operatingdir)
_filedir -dreturn;;-s|--speller)COMPREPLY=($(compgen-Acommand--"$cur"))return;;esac$split&&return
case$curin
-*)COMPREPLY=($(compgen-W'--smarthome --backup --backupdir= --boldtext --tabstospaces --multibuffer --locking --historylog --ignorercfiles --guidestripe= --rawsequences --nonewlines --trimblanks --noconvert --positionlog --quotestr= --restricted --tabsize= --quickblank --version --wordbounds --wordchars= --syntax= --zap --atblanks --breaklonglines --constantshow --rebinddelete --emptyline --showcursor --help --autoindent --jumpyscrolling --cutfromcursor --linenumbers --mouse --noread --operatingdir= --preserve --fill= --speller= --tempfile --unix --view --nowrap --nohelp --afterends --suspend --softwrap'--"$cur"))[[$COMPREPLY==*=]]&& compopt -o nospace
;;*)
_filedir
;;esac}complete-F _nano nano
これで完成です。
vack
https://github.com/kusabashira/vack
Vimのプラグインを管理するコマンドです。
このコマンドの使い方は次のようになっています。
usage: vack <command> [...]
manage Vim packages.
commands:
i|install [-os] <repository>... # install the packages
u|update [-a] <package>... # update the packages
r|remove <package>... # remove the packages
l|list [-aos] # list installed packages
p|path [-a] <package>... # show install directory of the package
e|enable <package>... # move the packages to start
d|disable <package>... # move the packages to opt
I|init # create the package directory
h|help # show this help message
environment-variables:
VACKPATH # the package directory (default: $HOME/.vim/pack/vack)
サブコマンドを取り、サブコマンドごとに違った引数を取る、これもよくある形のコマンドですね。
補完の基本方針としては、第一引数はサブコマンドを、第二引数以降は引数が-で始まっていればサブコマンドごとのオプションを、そうでなければサブコマンドごとの引数を補完候補とするのがよさそうです。
installの引数はインストール元のリポジトリなので補完できないですが、update、remove、pathはインストール済みのプラグイン、enableはopt配下にあるプラグイン、disableはstart配下にあるプラグインを引数に取るので、これらは引数の補完ができそうです。
まず、ベースとして_init_completionを呼び出すだけのもの書きます。
このコマンドはロングオプションを取らず、:を区切り文字として使う引数を取らないので、オプションは何も指定しません。
ファイル名はvack.bash
とし、ファイルを作成したらsource vack.bash
として読み込むようにします。
_vack(){local cur prev words cword split
_init_completion ||return}complete-F _vack vack
これでcur、prev、words、cword、splitにアクセスできるようになりました。
次に第一引数のときにサブコマンドを補完するようにします。
_vack(){local cur prev words cword split
_init_completion ||return
case$cwordin
1)COMPREPLY=($(compgen-W'install update remove list path enable disable init help'--"$cur"));;esac}complete-F _vack vack
これで第一引数のサブコマンドが補完されるようになりました。
次に第二引数以降をサブコマンドの引数として、引数が-で始まっていればサブコマンドごとのオプションを、そうでなければサブコマンドごとの引数を補完するようにします。
今回はロングオプションが無いですが、代わりにショートオプションを補完するようにします。
_vack(){local cur prev words cword split
_init_completion ||return
case$cwordin
1)COMPREPLY=($(compgen-W'install update remove list path enable disable init help'--"$cur"));;*)case${words[1]}in
i|install)case$curin
-*)COMPREPLY=($(compgen-W'-o -s --'--"$cur"));;*)COMPREPLY=()esac;;
u|update)case$curin
-)COMPREPLY=($(compgen-W'-a --'--"$cur"));;*)COMPREPLY=($(compgen-W'$(vack list)'--"$cur"));;esac;;
r|remove)case$curin
-)COMPREPLY=($(compgen-W'--'--"$cur"));;*)COMPREPLY=($(compgen-W'$(vack list)'--"$cur"));;esac;;
l|list)case$curin
-)COMPREPLY=($(compgen-W'-a -o -s --'--"$cur"));;*)COMPREPLY=();;esac;;
p|path)case$curin
-)COMPREPLY=($(compgen-W'-a --'--"$cur"));;*)COMPREPLY=($(compgen-W'$(vack list)'--"$cur"));;esac;;
e|enable)case$curin
-)COMPREPLY=($(compgen-W'--'--"$cur"));;*)COMPREPLY=($(compgen-W'$(vack list -o)'--"$cur"));;esac;;
d|disable)case$curin
-)COMPREPLY=($(compgen-W'--'--"$cur"));;*)COMPREPLY=($(compgen-W'$(vack list -s)'--"$cur"));;esac;;
I|init)COMPREPLY=();;
h|help)COMPREPLY=();;esac;;esac}complete-F _vack vack
これでサブコマンドの引数が補完されるようになりました。
しかし、vack listの出力をそのまま補完候補としている箇所でIFSの制御を行っていないので、もしvack listの出力に空白が混じっていれば正しく補完がされません。
そのため、IFSを都度制御することによって、空白を含む補完候補を扱えるようにします。
_vack(){local cur prev words cword split
_init_completion ||return
local defaultIFS=$' \t\n'local IFS=$defaultIFScase$cwordin
1)COMPREPLY=($(compgen-W'install update remove list path enable disable init help'--"$cur"));;*)case${words[1]}in
i|install)case$curin
-*)COMPREPLY=($(compgen-W'-o -s --'--"$cur"));;*)COMPREPLY=()esac;;
u|update)case$curin
-)COMPREPLY=($(compgen-W'-a --'--"$cur"));;*)IFS=$'\n';COMPREPLY=($(compgen-W'$(vack list)'--"$cur"));IFS=$defaultIFS;;esac;;
r|remove)case$curin
-)COMPREPLY=($(compgen-W'--'--"$cur"));;*)IFS=$'\n';COMPREPLY=($(compgen-W'$(vack list)'--"$cur"));IFS=$defaultIFS;;esac;;
l|list)case$curin
-)COMPREPLY=($(compgen-W'-a -o -s --'--"$cur"));;*)COMPREPLY=();;esac;;
p|path)case$curin
-)COMPREPLY=($(compgen-W'-a --'--"$cur"));;*)IFS=$'\n';COMPREPLY=($(compgen-W'$(vack list)'--"$cur"));IFS=$defaultIFS;;esac;;
e|enable)case$curin
-)COMPREPLY=($(compgen-W'--'--"$cur"));;*)IFS=$'\n';COMPREPLY=($(compgen-W'$(vack list -o)'--"$cur"));IFS=$defaultIFS;;esac;;
d|disable)case$curin
-)COMPREPLY=($(compgen-W'--'--"$cur"));;*)IFS=$'\n';COMPREPLY=($(compgen-W'$(vack list -s)'--"$cur"));IFS=$defaultIFS;;esac;;
I|init)COMPREPLY=();;
h|help)COMPREPLY=();;esac;;esac}complete-F _vack vack
これで完成です。
memo
メモを管理するコマンドです。
このコマンドの使い方は次のようになっています。
NAME:
memo - Memo Life For You
USAGE:
memo [global options] command [command options] [arguments...]
VERSION:
0.0.13
COMMANDS:
new, n create memo
list, l list memo
edit, e edit memo
cat, v view memo
delete, d delete memo
grep, g grep memo
config, c configure
serve, s start http server
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--help, -h show help (default: false)
--version, -v print the version (default: false)
------------------------------------------------------------------
NAME:
memo new - create memo
USAGE:
memo new [command options] [arguments...]
OPTIONS:
--help, -h show help (default: false)
------------------------------------------------------------------
NAME:
memo list - list memo
USAGE:
memo list [command options] [arguments...]
OPTIONS:
--fullpath show file path (default: false)
--format string print the result using a Go template string
--help, -h show help (default: false)
------------------------------------------------------------------
NAME:
memo edit - edit memo
USAGE:
memo edit [command options] [arguments...]
OPTIONS:
--help, -h show help (default: false)
------------------------------------------------------------------
NAME:
memo cat - view memo
USAGE:
memo cat [command options] [arguments...]
OPTIONS:
--help, -h show help (default: false)
------------------------------------------------------------------
NAME:
memo delete - delete memo
USAGE:
memo delete [command options] [arguments...]
OPTIONS:
--help, -h show help (default: false)
------------------------------------------------------------------
NAME:
memo grep - grep memo
USAGE:
memo grep [command options] [arguments...]
OPTIONS:
--help, -h show help (default: false)
------------------------------------------------------------------
NAME:
memo config - configure
USAGE:
memo config [command options] [arguments...]
OPTIONS:
--cat cat the file (default: false)
--help, -h show help (default: false)
------------------------------------------------------------------
NAME:
memo serve - start http server
USAGE:
memo serve [command options] [arguments...]
OPTIONS:
--addr value server address (default: ":8080")
--help, -h show help (default: false)
------------------------------------------------------------------
NAME:
- Shows a list of commands or help for one command
USAGE:
[command options] [command]
OPTIONS:
--help, -h show help (default: false)
サブコマンドを取り、サブコマンドもオプションを取る、複合的なコマンドになっています。
分量がありますが、複雑な引数は取らないので一つ一つ見ていけば大丈夫です。
補完の基本方針としては、第一引数はサブコマンドを、第二引数以降は引数が-で始まっていればサブコマンドごとのオプションを、そうでなければサブコマンドごとの引数を補完候補とするのがよさそうです。
まず、ベースとして_init_completionを呼び出すだけのもの書きます。
このコマンドはロングオプションを取るため-sオプションを指定しておきます。
ファイル名はmemo.bash
とし、ファイルを作成したらsource memo.bash
として読み込むようにします。
_memo(){local cur prev words cword split
_init_completion -s||return}complete-F _memo memo
これでcur、prev、words、cword、splitにアクセスできるようになりました。
次に第一引数のときに引数が-で始まっていればオプションを、そうでなければサブコマンド補完するようにします。
_memo(){local cur prev words cword split
_init_completion -s||return
case$cwordin
1)case$curin
-*)COMPREPLY=($(compgen-W'--help --version'--"$cur"));;*)COMPREPLY=($(compgen-W'new list edit cat delete grep config serve help'--"$cur"));;esac;;esac}complete-F _memo memo
これで第一引数が補完されるようになりました。
最後に、第二引数以降としてサブコマンドごとの引数を-で始まっていればサブコマンドごとのオプションを、そうでなければサブコマンドごとの引数を補完するようにします。
_memo(){local cur prev words cword split
_init_completion -s||return
local defaultIFS=$' \t\n'local IFS=$defaultIFScase$cwordin
1)case$curin
-*)COMPREPLY=($(compgen-W'--help --version'--"$cur"));;*)COMPREPLY=($(compgen-W'new list edit cat delete grep config serve help'--"$cur"));;esac;;*)case${words[1]}in
n|new)case$curin
-*)COMPREPLY=($(compgen-W'--help'--"$cur"));;*)COMPREPLY=();;esac;;
l|list)case$curin
-*)COMPREPLY=($(compgen-W'--fullpath --format= --help'--"$cur"));[[$COMPREPLY==*=]]&& compopt -o nospace ;;*)COMPREPLY=();;esac;;
e|edit)case$curin
-*)COMPREPLY=($(compgen-W'--help'--"$cur"));;*)IFS=$'\n';COMPREPLY=($(compgen-W'$(memo list)'--"$cur"));IFS=$defaultIFS;;esac;;
v|cat)case$curin
-*)COMPREPLY=($(compgen-W'--help'--"$cur"));;*)IFS=$'\n';COMPREPLY=($(compgen-W'$(memo list)'--"$cur"));IFS=$defaultIFS;;esac;;
d|delete)case$curin
-*)COMPREPLY=($(compgen-W'--help'--"$cur"));;*)IFS=$'\n';COMPREPLY=($(compgen-W'$(memo list)'--"$cur"));IFS=$defaultIFS;;esac;;
g|grep)case$curin
-*)COMPREPLY=($(compgen-W'--help'--"$cur"));;*)COMPREPLY=();;esac;;
c|config)case$curin
-*)COMPREPLY=($(compgen-W'--cat --help'--"$cur"));;*)COMPREPLY=();;esac;;
s|serve)case$curin
-*)COMPREPLY=($(compgen-W'--addr= --help'--"$cur"));[[$COMPREPLY==*=]]&& compopt -o nospace ;;*)COMPREPLY=();;esac;;
h|help)case$curin
-*)COMPREPLY=($(compgen-W'--help'--"$cur"));;*)COMPREPLY=();;esac;;esac;;esac}complete-F _memo memo
これで完成です。