在Bash脚本中实现基于文件名的自动完成

3

有一个命令行功能我一直想要,我考虑过如何最好地实现它,但是我一无所获……

所以我想要的是,当我开始输入文件名并按下tab键时,例如:

# git add Foo<tab>
我希望它能运行find . -name "*$1*",并自动完成匹配文件的完整路径到我的命令行。 目前为止,我知道我必须编写一个函数来调用应用程序以获取所需的参数,例如git add。之后,它需要捕获Tab键事件并执行上述查找操作,如果结果有多个,则显示结果,如果只有一个,则填充结果。 我还没有弄清楚如何在一个函数内部捕获Tab键事件。 因此,基本上是伪代码:
gadd() {git add autocomplete_file_search($1)}

autocomplete_file_search(keyword) {
  if( tab-key-pressed ){
    files = find . -name "*$1*";
    if( filecount > 1 ) {
      show list;
    }
    if( files == 1 ) {
      return files
    }
  }
}

有什么想法吗?

谢谢。

4个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
2

在文件名的任意位置进行匹配相当复杂,我不确定它是否真的很有用。在文件名开头进行匹配更有意义,并且更容易实现,即使是递归的。

现在,您提到了需要使用find命令,但自bash 4.0版本以来,bash也可以递归查找文件,而且让bash执行该部分应该更有效率。要在bash中递归匹配,您需要通过运行shopt -s globstar来启用globstar shell选项,然后两个连续的星号 ** 将进行递归匹配。

接下来,鉴于您想在git存储库内递归匹配文件,我们最好有一种方法来检测我们是否实际位于git存储库中,否则,如果您在/中意外触发它,您的提示符会挂起,同时等待bash搜索整个文件系统。以下函数应该能够相对高效地确定我们是否在git存储库内。给定当前工作目录,例如/foo/bar/baz,它将查找/foo/bar/baz/.git/foo/bar/.git/ foo /.git/.git,如果找到一个,则返回true,否则返回false。

isgit() {
    local p=$PWD
    while [[ $p ]]; do
        [[ -d $p/.git ]] && return
        p=${p%/*}
    done
    return 1
}
为了简便起见,我们将创建一个名为gadd的命令来添加完成。完成函数只能应用于命令的第一个单词。例如,我们可以为git添加完成,但不能为git add添加,因此我们将创建一个新命令,将git add转换为一个单词。
gadd() {
    git add "$@"
}

现在开始编写实际的完成函数。当按下TAB键时触发函数,该函数将被调用并带有三个参数。 $1 是正在完成的命令,$2 是正在完成的命令行中当前的单词,$3 是该行上一个单词。因此,我们想要搜索的文件将通过glob匹配 **/"$2"* 来实现;所有以 "$2" 开头的文件。我们迭代这些文件名,并将它们附加到 COMPREPLY 数组中。如果 COMPREPLY 数组在函数执行完毕时只包含一个值,则该单词将被替换为该值。如果它包含多个值,请再次按 TAB 键以获取所有匹配项的列表。

shopt -s globstar
_git_add_complete() {
    local file
    isgit || return
    for file in **/"$2"*; do
        # If the glob doesn't match, we'll get the glob itself, so make sure
        # we have an existing file
        [[ -e $file ]] || continue

        # If it's a directory, add a trailing /
        [[ -d $file ]] && file+=/
        COMPREPLY+=( "$file" )
    done
}
complete -F _git_add_complete gadd
将上述三个代码块添加到您的~/.bashrc文件中,然后打开一个新的终端,进入一个git仓库并尝试使用gadd something<tab>命令。

问题是,我希望在仓库内部使用各种命令,如编辑器、ls、git等。 - chx
@chx,然后将该函数用于所有这些命令,或将其设置为默认完成。默认完成将适用于所有没有定义完成的命令,因此如果您正在使用bash-completion包,则不起作用。设置默认完成:complete -F _git_add_complete -o default -o bashdefault -D 但您可能希望将该函数重命名为更合适的名称。 - geirha
那么当使用bash-completion时,就没有干净的方法可以做到这一点吗? - chx
@chx,你需要为每个你想要“git-completable”的命令覆盖当前的完成。你可以使用complete -p检查单个命令。例如:complete -p ls。那些没有设置完成的命令将使用默认的完成;complete -p -D。我自己不使用bash-completion,所以我不确定你需要覆盖多少它的完成。 - geirha

1
你应该看一下这个bash自动补全介绍。简单来说,bash有一个配置和扩展制表符自动补全的系统。其他shell也有类似的功能,但每个shell设置方式不同。使用这个系统,你不需要亲自完成所有工作,而且向命令添加自定义参数补全也相对容易。

0

我写了git-number,这样我在指定文件给git时就不必按tab键了。

使用git-number,我可以用数字代表我想让git处理的文件名。


0

这个可以工作吗?

$ cat .bash_completion
_foo()
{
    local files
    cur=${COMP_WORDS[COMP_CWORD]}
    local files=$(for x in `find -type f`; do echo ${x}; done)
    COMPREPLY=( $( compgen -W "${files}" -- ${cur} ) )
    return 0
}
complete -F _foo foo

$ . /etc/bash_completion
$ foo ./[tab]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,