如何自定义 Bash 命令补全?

如何自定义 Bash 命令补全?

在 中,使用内置命令bash设置命令参数的自定义完成非常容易。complete例如,对于一个假设的命令,其概要为

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

你可以做

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

您还可以自定义按下时获得的完成结果Tab您还可以自定义按下某个空的提示使用complete -E,例如complete -E -W 'foo bar'。然后,在空提示符下按 Tab 键将仅建议foobar

如何自定义命令完成- 空提示?例如,如果我写f,我如何自定义完成以使其完成到foo

(我想要的实际情况是locTABlocalc。我的兄弟促使我问这个问题,他想要它mplayer。)

答案1

命令的完成(以及其他事情)是通过 bash 处理的读行完成。它的运行级别比通常的“可编程完成”(仅在识别命令以及上面识别的两种特殊情况时才调用)稍低。

更新:新版本的 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 调用)。

这种方法可以很好地与仅用于自定义命令完成的不同键绑定配合使用,但它根本无法实现此后的完整完成逻辑(例如命令行中的后续单词)。这样做需要解析命令行、处理光标位置以及其他可能不应该在 shell 脚本中考虑的棘手事情...

答案2

我不知道我是否理解你对此的需要......
这意味着你的 bash 只知道一个以f.
完成的基本思想是:如果不明确,则打印可能性。
因此,您可以将您的PATH目录设置为仅包含此命令的目录,并禁用所有 bash 内置命令来完成这项工作。

无论如何,我还可以给你一种解决方法:

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

因此如果你输入_ <Tab>它将完成_ foo执行foo

但无论如何,这alias f='foo'会容易得多。

答案3

对你来说简单的答案是

$ 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 完成

答案4

运行以下命令查找 mplayer 二进制文件的安装位置:

which mplayer

或者在以下命令中使用 mplayer 二进制文件的路径(如果您已经知道):

ln -s /path/to/mplayer /bin/mplayer

理想情况下,您键入的任何内容都会在变量中指定的所有目录中搜索$PATH

相关内容