Quantcast
Channel: Bashタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 2832

Bash用の補完スクリプトを実際に作ってみる(4種)

$
0
0

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

https://www.nano-editor.org/

ターミナルで動くテキストエディタです。
このコマンドの使い方は次のようになっています。

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

https://github.com/mattn/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

これで完成です。


Viewing all articles
Browse latest Browse all 2832

Latest Images

Trending Articles