Bashコマンドの完成をカスタマイズする方法は?

Bashコマンドの完成をカスタマイズする方法は?

では、bash組み込みコマンドを使用してコマンドパラメータ設定のカスタマイズを完了するのは非常に簡単です。completeたとえば、仮想コマンドの場合、要約は次のようになります。

foo --a | --b | --c

あなたはできます

complete -W '--a --b --c' foo

Tabを押すと表示される完了結果をカスタマイズすることもできます。complete -Eたとえばcomplete -E -W 'foo bar'。その後、空白のプロンプトでTabキーを押すだけでfoo提案されますbar

コマンド補完をカスタマイズする方法いいえ- メッセージは空ですか?たとえば、と書くと、完了するようfに完成をどのようにカスタマイズできますかfoo

(私が実際に欲しいのはlocTAB→ですlocalc。兄が私にこの質問をするように勧めましたが、彼はそれを望んでいますmplayer。)

ベストアンサー1

とりわけ、コマンドの完了はbashを介して処理されます。行の完成を読む。これは、一般的な「プログラミング可能な完了」(コマンドが認識され、上記で識別された2つの特殊なケースでのみ呼び出されます)よりわずかに低いレベルで実行されます。

修正する:bash-5.0の新しいバージョン(2019年1月)はcomplete -Iこの問題を正確に解決します。

関連するreadlineコマンドは次のとおりです。

complete (TAB)
       Attempt to perform completion on the text  before  point.   Bash
       attempts completion treating the text as a variable (if the text
       begins with $), username (if the text begins with  ~),  hostname
       (if  the  text begins with @), or command (including aliases and
       functions) in turn.  If none of these produces a match, filename
       completion is attempted.

complete-command (M-!)
       Attempt  completion  on  the text before point, treating it as a
       command name.  Command completion attempts  to  match  the  text
       against   aliases,   reserved   words,  shell  functions,  shell
       builtins, and finally executable filenames, in that order.

より一般的な方法と同様に、complete -Fこれらのいくつかはを使用して関数に渡すことができますbind -x

function _complete0 () {
    local -a _cmds
    local -A _seen
    local _path=$PATH _ii _xx _cc _cmd _short
    local _aa=( ${READLINE_LINE} )

    if [[ -f ~/.complete.d/"${_aa[0]}" && -x  ~/.complete.d/"${_aa[0]}" ]]; then
        ## user-provided hook
        _cmds=( $( ~/.complete.d/"${_aa[0]}" ) )
    elif [[ -x  ~/.complete.d/DEFAULT ]]; then
        _cmds=( $( ~/.complete.d/DEFAULT ) )
    else 
        ## compgen -c for default "command" complete 
        _cmds=( $(PATH=$_path compgen -o bashdefault -o default -c ${_aa[0]}) )  
    fi

    ## remove duplicates, cache shortest name
    _short="${_cmds[0]}"
    _cc=${#_cmds[*]} # NB removing indexes inside loop
    for (( _ii=0 ; _ii<$_cc ; _ii++ )); do
        _cmd=${_cmds[$_ii]}
        [[ -n "${_seen[$_cmd]}" ]] && unset _cmds[$_ii]
        _seen[$_cmd]+=1
        (( ${#_short} > ${#_cmd} )) && _short="$_cmd"
    done
    _cmds=( "${_cmds[@]}" )  ## recompute contiguous index

    ## find common prefix
    declare -a _prefix=()
    for (( _xx=0; _xx<${#_short}; _xx++ )); do
        _prev=${_cmds[0]}
        for (( _ii=0 ; _ii<${#_cmds[*]} ; _ii++ )); do
            _cmd=${_cmds[$_ii]}
             [[ "${_cmd:$_xx:1}" != "${_prev:$_xx:1}" ]] && break
            _prev=$_cmd
        done
        [[ $_ii -eq ${#_cmds[*]} ]] && _prefix[$_xx]="${_cmd:$_xx:1}"
    done
    printf -v _short "%s" "${_prefix[@]}"  # flatten 

    ## emulate completion list of matches
    if [[ ${#_cmds[*]} -gt 1 ]]; then
        for (( _ii=0 ; _ii<${#_cmds[*]} ; _ii++ )); do
            _cmd=${_cmds[$_ii]}
            [[ -n "${_seen[$_cmds]}" ]] && printf "%-12s " "$_cmd" 
        done | sort | fmt -w $((COLUMNS-8)) | column -tx
        # fill in shortest match (prefix)
        printf -v READLINE_LINE "%s" "$_short"
        READLINE_POINT=${#READLINE_LINE}  
    fi
    ## exactly one match
    if [[ ${#_cmds[*]} -eq 1 ]]; then
        _aa[0]="${_cmds[0]}"
        printf -v READLINE_LINE "%s " "${_aa[@]}"
        READLINE_POINT=${#READLINE_LINE}  
    else
        : # nop
    fi
}

bind -x '"\C-i":_complete0'

これにより、実行可能ファイルを生成できます~/.complete.d/。たとえば、次のコマンドを使用する場合~/.complete.d/loc:

#!/bin/bash
echo localc

これは(おおよそ)あなたが期待することを行います。

上記の関数は完璧ではありませんが、通常のbashコマンドの完了動作をある程度シミュレートします(特にsort | fmt | column一致リストを表示する疑わしい繰越し)。

しかし、completeこれは大きな問題であり、メイン関数へのバインディングを置き換えるために関数のみを使用できます(デフォルトではTAB呼び出しを使用)。

このアプローチは、カスタムコマンドを完了するためのさまざまなキーバインディングとうまく機能しますが、それ以降の完全な完了ロジック(コマンドラインの後続の単語など)は許可されません。これを行うには、コマンドラインの解析、カーソル位置の処理、およびシェルスクリプトで考慮してはならない他のトリッキーな操作が必要です。

おすすめ記事