如何自定义Bash命令完成?


24

在bash中,使用complete内置的命令设置自定义的命令参数完成很容易。例如,如果对于带有的摘要的假设命令foo --a | --b | --c,则可以执行complete -W '--a --b --c' foo

您还可以自定义,当你按下你完成Tab提示符下使用complete -E,例如,complete -E -W 'foo bar'。然后在空白提示下按Tab键将仅建议foobar

如何在空提示下自定义命令完成?例如,如果我坐在:

anthony@Zia:~$ f

如何自定义完成,使按Tab键始终完成foo

(我想要的实际情况是locTABlocalc。而促使我问这个问题的我的兄弟希望通过mplayer来解决这个问题。)


2
你有没有考虑过干脆别名loclocalc?我建议使用替代方法,因为经过一段时间的挖掘和搜索后,我还没有找到以这种方式自定义bash补全的方法。这可能是不可能的。
jw013 2014年

@ jw013是的,我看了一会儿也找不到任何东西。我一半期望有人也建议切换到zsh。至少如果zsh做到了,我可以安心地知道下一个bash版本也将如此。😀
derobert

您必须按两次TAB,它将完成命令。
弗洛伊德2014年

1
那不会打破所有其他成就吗?我的意思是,即使是可能的,您将不再能够得到locatelocalelockfile或的其他任何扩展loc。也许更好的方法是将另一个密钥映射到该特定完成。
terdon

1
您能否举一个这样的上下文示例(这将导致预期的效果loc<TAB>->localc)?
damienfrancois 2014年

Answers:


9

命令的完成(以及其他操作)是通过bash readlinecomplete处理的。此操作的级别略低于通常的“可编程完成”(仅在识别了命令以及您在上面识别出的两种特殊情况下才调用)。

更新: 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

这将(大致)完成您的期望。

上面的函数虽然不完善(特别是sort | fmt | column显示匹配列表的可疑随身携带品),但它在某种程度上模仿了正常的bash命令完成行为。

但是,与此有关的一个重要问题是它只能使用一个函数来替换对主complete函数的绑定(默认情况下使用TAB调用)。

这种方法可以与仅用于自定义命令完成的其他键绑定一起很好地工作,但是此后它根本不实现完整的完成逻辑(例如,命令行中的后续单词)。这样做将需要解析命令行,处理光标位置以及其他在Shell脚本中可能不应该考虑的棘手问题...


1

我不知道我是否了解您对此的需求...
这意味着您的bash仅知道一个以开头的命令f
完成的基本思想是:如果模棱两可,则打印可能性。
因此,您可以将您PATH的目录设置为仅包含此命令,并禁用所有bash内置函数来完成此工作。

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

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

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

但是尽管如此,alias f='foo'这将容易得多。


0

对于您来说,简单的答案是

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


2
这些文件中都有shell脚本,如果我没记错的话,它们有些复杂。而且,只有在您键入命令后,它们才会完成参数……它们不会完成命令。
derobert 2014年

我明白你的意思。当你可以看看这个文档:debian-administration.org/article/316/...
unixmiah

-3

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

which mplayer

或者,如果您确信自己知道mplayer二进制文件的路径,请在以下命令中使用它:

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

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


2
嗨,Mathew,欢迎使用Unix&Linux。我认为OP的问题比您所建议的答案要复杂一些。我认为mplayer他们已经在他们之中了$PATH,他们想要某种东西mp<tab>来产生mplayer,而不是所有以mp(例如mpage mpcd mpartition mplayer)开头的二进制文件
drs
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.