8.6 可编程补全(Programmable Completion)
当试图对命令的参数进行补全时,如果事先对于该命令已使用内建命令 complete(参见 8.7《可编程补全内建命令》)定义了补全规范(compspec),则会调用可编程补全功能。
首先,识别出命令名。如果 compspec 已为该命令定义了,则 compspec 用于产生该单词的可行补全列表。如果命令单词是空字符串(在空行开头试图进行补全),则会使用带有“-E”选项的 complete 定义的 compspec。如果命令单词是一个完整路径名,首先会对完整路径名的 compspec 进行搜索。如果没有找到完整路径名的 compspec,则会试图去寻找带有最终反斜线部分的 compsepc。如果这些搜索都没有找到一个 compspec,则会使用带有“-D”选项的 complete 定义的 compspec 作为默认规范。如果没有默认 compspec,Bash 会试图在命令单词上进行别名扩展作为最后的手段,并且试图从任何成功扩展中找出用于命令单词的 compspec。
一旦找到一个 compspec,其会被用于产生匹配单词列表。如果没有找到 compsepc,则会执行上面描述的默认 Bash 补全(参见 8.4.6《让 Readline 替你输入》)。
首先会使用由 compspec 指定的动作。只返回由被补全单词作为前缀的所有匹配项。当在文件名或目录名补全时使用了选项“-f”或“-d”,会使用 shell 变量 FIGNORE 来过滤匹配项。关于 FIGNORE 的描述可参考 5.2《Bash 变量》。
任何由“-G”选项指定的文件名扩展样式的补全会在下一次产生。由样式产生的单词没有必要与要补全的单词匹配。shell 变量 GLOBIGNORE 不会用于过滤匹配项,但会使用变量 FIGNORE。
接下来,由“-W”选项参数所指定的字符串才会被考虑。首先使用特殊变量 IFS 中的字符作为分隔符将字符串分割。在字符串中可以使用 shell 引用,这样是提供了一种机制使其中的单词可以包含元字符或变量 IFS 中的字符。之后可以使用大括号扩展、波浪号扩展、参数和变量扩展、命令替换和算术扩展(参见 3.5《Shell 扩展》)对分割后的每一个单词进行扩展操作。使用前面介绍过的单词分割(参见 3.5.7《单词分割》)规则对结果进行分割。扩展结果依据要补全的单词进行前缀匹配,并且匹配上的单词会变为可行补全。
在这些匹配项产生之后,由“-F”和“-C”所指定的函数和命令才会被调用。当命令或函数调用时,会依据之前 5.2《Bash 变量》的描述对变量 COMP_LINE、COMP_POINT、COMP_KEY 和 COMP_TYPE 进行赋值。如果正在调用一个 shell 函数,也会设置变量 COMP_WORDS 和 COMP_CWORD 的值。当调用函数或命令时,在当前行,第一个参数($1)是命令参数正在进行补全的命令名,第二个参数($2)是正在补全的单词,和第三个参数($3)是在先前正常在补全的单词后面的单词。不会对依据要补全的单词所产生的补全项执行过滤;在所产生的匹配项中命令和函数会自主完成补全。
由“-F”指定的函数首先被调用。函数可使用一些 shell 的特殊功能,包括后面章节 8.7《可编程补全内建命令》中的内建命令 compgen 和 compopt 来生成匹配项。其必定将可行补全放入数组变量 COMPREPLY 中,每个数组元素对应一个可行补全。
接下来会在一个等同于命令替换的环境中调用由“-C”选项指定的命令。其会在标准输出中打印补全列表,每个补全占用一行。如果需要的话,反斜线可用于转义一个换行。
在所有可行补全列表产生之后,由“-X”选项指定的过滤会应用于该列表。该过滤类似于使用文件名扩展的样式;在样式中的与符号“&”替换为被补全单词文本。可以使用反斜线转义“&”字符;反斜线会在尝试匹配前移除。任何与该样式匹配的补全都会从列表中移除。以叹号“!”开头的样式会被求反;在这种情况下会移除任何没有匹配到样式的补全。如果开启了 shell 选项“nocasematch”(参见 4.3.2《内建命令 shopt》中关于 shopt 的描述),将会执行字母大小写不敏感的匹配。
最后,所有由“-P”和“-S”指定的前缀和后缀会添加到补全列表中的每一个成员中,并且结果会作为可行补全列表返回给 Readline 补全代码。
如果之前所应用的动作都没有产生任何匹配项,并且在 compspec 定义时向 complete 命令提供了“-o dirnames”的选项,则会进行目录名补全尝试。
在 compspec 定义时向 complete 命令提供了“-o plusdirs”选项,则会进行目录名补全尝试,并且会将所产生的匹配项添加到其它动作所产生的结果中。
默认情况下,如果找到一个 compspec,无论其所产生的是什么,都会被作为完整的可行补全集合返回给补全代码。不会尝试进行默认 Bash 补全,并且 Readline 的默认文件名补全也是关闭的。如果在 compspec 定义时向 complete 命令提供了“-o bashdefault”选项的话,则如果 compspec 没有产生任何的匹配项,就会尝试执行默认 Bash 补全。如果在 compspec 定义时向 complete 命令提供“-o default”选项的话,如果 compspec 也没有产生任何匹配项(并且也试图执行了默认 Bash 补全),则会执行 Readline 的默认补全。
当一个 compspec 指出需要使用目录名补全时,可编程补全函数会强制 Readline 在软链接到目录名的被补全名后面追加一个斜线,其遵从 Readline 变量“mark-directories”的值,而不管 Readline 变量“mark-symlinked-directories”的设置。
这里有一些对于动态修改补全的支持。当其用在由“-D”指定的带有默认补全混合使用中是非常有用的。其可以用于在作为补全处理句柄执行的 shell 函数在返回退出状态为 124 时应该进行补全重试。如果一个 shell 函数返回 124,并且改变了关联到尝试进行补全的命令(当函数执行时作为第一个参数给出的)的 compspec,可编程补全会从开头重新执行,为该命令尝试寻找一个新的 compspec。这样允许为补全尝试动态建立补全集合,而不是一次加载全部。
例如,假设这里有一个 compspec 库,每一个 compspec 都保存在相应命令名的文件中,下面默认补全函数会动态加载补全:
_completion_loader()
{
. "/etc/bash_completion.d/$1.sh" >/dev/null 2>&1 && return 124
}
complete -D -F _completion_loader -o bashdefault -o default