更多关于使用 Bash Complete 命令
在上周的视频中,我展示了如何使用 bashcomplete命令进行简单的用例。今天我将向您展示一些使用该命令处理更复杂场景的其他方法。
回顾一下我上周演示的简单用例,以下是其要点,或者观看视频
$ # Create a dummy command:
$ touch ~/bin/myfoo
$ chmod +x ~/bin/myfoo
$ # Create some files:
$ touch a.bar a.foo b.bar b.foo
$ # Use the command and try auto-completion.
$ # Note that all files are displayed:
$ myfoo <TAB><TAB>
a.bar a.foo b.bar b.foo
$ # Now tell bash that we only want foo files.
$ # This command tells bash args to myfoo are completed
$ # by generating a list of files and then excluding
$ # everything # that doesn't match *.foo:
$ complete -f -X '!*.foo' myfoo
$ # Tray again:
$ myfoo <TAB><TAB>
a.foo b.foo
对于需要更多控制完成方式的更复杂情况,您可以告诉 bash 调用一个函数来完成完成工作。这是一个您提供的函数,您可能会从您的.profile文件中获取。然后,函数名称作为-F选项的参数提供complete:
$ complete -F _mycomplete_ myfoo
的基本版本_mycomplete,此时它所做的并不比我们简单的命令行用法多complete以上,将如下所示
function _mycomplete_()
{
local cmd="${1##*/}"
local word=${COMP_WORDS[COMP_CWORD]}
local line=${COMP_LINE}
local xpat='!*.foo'
COMPREPLY=($(compgen -f -X "$xpat" -- "${word}"))
}
complete -F _mycomplete_ myfoo
在函数体的前三行中,我们创建了一些有用的局部变量,这里主要是为了展示可用的变量,因为其中大多数在函数中没有使用。第一个,cmd,获取正在执行的命令,如果您的完成函数可以处理多个命令,则可以使用它。第二个,word,获取正在完成的单词,如果您的完成策略根据正在展开的单词而变化,则可以使用它,这也需要它,以便仅返回匹配的值。第三个,line,获取正在完成的整个命令行。第四个变量,xpat,是我们的排除模式,与上面简单示例中使用的模式相同。查看 bash 手册页以获取其他有用的COMP_*变量。
函数中唯一真正的代码是最后一行,它设置了变量COMPREPLY,这是我们对 bash 扩展某些内容的请求的回复。此行使用compgen来生成扩展。compgen命令接受与complete相同的大多数选项,但它生成结果,而不是仅仅存储规则以供将来使用。在这里,我们告诉compgen创建一个文件列表,使用-f。然后我们告诉它排除所有与我们的排除模式匹配的文件,使用-X "$xpat"。最后,我们传入正在完成的单词,以便仅返回与它匹配的项目。
现在,让我们考虑一个稍微复杂的例子。让我们使用我们的 complete 函数来完成多个命令,并且让我们也更改它,以便它根据给定的命令行参数,为myfoo命令以不同的方式展开内容myfoo.
具体来说,让我们假设myfoo可以 foo 文件和 unfoo 文件,所以myfoo -f myfile创建myfile.foo和myfoo -u myfile.foo创建myfile。想想 -d 选项对于gzip或bunzip2。因此,在这种情况下,如果指定了-f,我们只想显示非 foo 文件,如果指定了-u,则只想显示 foo 文件。
此外,让我们添加一个更进一步的增强功能,让我们的完成函数包含目录名,以便目录名可以用于完成参数,从而允许我们导航到子目录以 foo 和 unfoo 子目录中的文件。我们的新函数将是
function _mycomplete_()
{
local cmd="${1##*/}"
local word=${COMP_WORDS[COMP_CWORD]}
local line=${COMP_LINE}
local xpat
# Check to see what command is being executed.
case "$cmd" in
myfoo)
# See if we are fooing or unfooing.
case "$line" in
*-f*)
xpat='*.foo'
;;
*-u*)
xpat='!*.foo'
;;
*)
xpat='*.foo'
;;
esac
;;
mybar)
xpat='!*.bar'
;;
*)
xpat='!*'
;;
esac
COMPREPLY=($(compgen -f -X "$xpat" -- "${word}"))
}
complete -d -X '.[^./]*' -F _mycomplete_ myfoo mybar
在这里,我们使用cmd变量来查看我们正在完成哪个命令,并根据它更改我们的模式。当处理myfoo命令时,我们使用line变量来查看命令行是否包含-f或-u选项,以确定我们应该排除还是包含 foo 文件。
为了在我们的输出中包含目录,我们只需修改complete命令,通过包含参数-d '-X' '.[^./]*'来安装我们的函数,这会生成目录列表,然后排除./和../(当前目录和父目录)。然后,目录列表被添加到调用我们的完成函数返回的结果中。我们还将第二个命令mybar添加到我们的函数处理的命令中。
现在当我们运行它时
$ # Source our completion function:
$ . bcomp2.sh
$ # Make some files:
$ touch a b a.bar a.foo b.bar b.foo
$ # Make a directory for checking directory inclusion:
$ mkdir astuff
$ # See what we get when we want to foo something
$ myfoo -f <TAB><TAB>
a a.bar astuff/ b b.bar bcomp2.sh bcomp.sh
$ # See what we get when we want to unfoo something
$ myfoo -u <TAB><TAB>
a.foo astuff/ b.foo
一旦您理解了所有这些,请查看文件/etc/profile.d/complete.bash以查看 bash 自带的默认完成项。您会在该文件中注意到,我们在这里忽略了许多复杂情况,例如当有人尝试完成诸如${ABC或$(ca之类的单词时会发生什么。在这些情况下,完成需要分别返回变量名和命令名,而不是数据文件名。