web-dev-qa-db-ja.com

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

bashでは、completeビルトインを使用してコマンド引数のカスタマイズされた補完を設定するのは簡単です。たとえば、あらすじのある架空のコマンドの場合

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

あなたはできる

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

を押したときに得られる補完をカスタマイズすることもできます Tab at emptyを使用したプロンプトcomplete -E、 例えば complete -E -W 'foo bar'。次に、空のプロンプトでタブを押すと、foobarのみが提案されます。

non-emptyプロンプトでコマンド補完をカスタマイズするにはどうすればよいですか?たとえば、fと書いた場合、fooまで補完するように補完をカスタマイズするにはどうすればよいですか?

(私が望む実際のケースはlocTABlocalc。そして、私にこれを尋ねるように促した兄は、mplayerでそれを望んでいます。)

24
derobert

コマンドの完了(および他のもの)は、bashreadline completionを介して処理されます。これは、通常の「プログラム可能な完了」(コマンドが特定された場合にのみ呼び出される、および上記で特定した2つの特殊なケース)よりも少し低いレベルで動作します。

pdate: 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で呼び出されます)。

このアプローチは、カスタムコマンド補完だけに使用される別のキーバインドでうまく機能しますが、その後の完全な補完ロジック(たとえば、コマンドラインの後半の単語)を実装しないだけです。これを行うには、コマンドラインの解析、カーソル位置の処理、およびおそらくシェルスクリプトでは考慮すべきではないその他のトリッキーなことを行う必要があります...

10
mr.spuratic

あなたのための簡単な答えは

$ cd into /etc/bash_completion.d
$ ls

基本的な出力だけ

autoconf       gpg2               ntpdate           shadow
automake       gzip               open-iscsi        smartctl
bash-builtins  iconv              openssl           sqlite3
bind-utils     iftop              Perl              ssh
brctl          ifupdown           pkg-config        strace
bzip2          info               pm-utils          subscription-manager
chkconfig      ipmitool           postfix           tar
configure      iproute2           procps            tcpdump
coreutils      iptables           python            util-linux
cpio           lsof               quota-tools       wireless-tools
crontab        lvm                redefine_filedir  xmllint
cryptsetup     lzma               rfkill            xmlwf
dd             make               rpm               xz
dhclient       man                rsync             yum.bash
e2fsprogs      mdadm              scl.bash          yum-utils.bash
findutils      module-init-tools  service
getent         net-tools          sh

目的のプログラムを自動補完してbash補完に追加するだけです

1
unixmiah

私がこれに対するあなたの必要性を理解していたかどうかわかりません...
これは、bashがfで始まる1つのコマンドしか認識していないことを意味します。
補完の基本的な考え方は、あいまいな場合は可能性を印刷することです。
そのため、PATHをこの1つのコマンドのみを含むディレクトリに設定し、すべてのbashビルトインを無効にしてこの作業を行うことができます。

とにかく、私はあなたに一種の回避策を与えることもできます:

alias _='true &&'
complete -W foo _

したがって、_ <Tab>と入力すると、_ fooまで完了し、fooが実行されます。

それでもalias f='foo'の方がはるかに簡単です。

1
m13r