本文为 Bash Reference Manual第8章:Command Line Editing 第6节:Programmable Completion ,第7节:Programmable Completion Builtins 和第8节:A Programmable Completion Example 的读书笔记。
8.6 Programmable Completion
当用户尝试为命令或命令的参数进行单词补全,而该命令或参数已经使用 complete 内置命令定义了补全规格(compspec)(参见可编程补全内置命令)时,Readline 会调用可编程补全功能。
首先,Bash 会识别命令名称。如果已经为该命令定义了 compspec,则使用该 compspec 生成该单词的可能完成列表。如果命令单词是空字符串(在空行开头尝试补全),Bash 会使用任何用 -E 选项定义的 compspec 进行补全。-I 选项用于补全,表示命令单词是行上第一个非赋值单词,或者位于诸如 ';' 或 '|' 等命令分隔符之后。这通常表示命令名称的补全。
如果命令字是完整路径名,Bash 会首先搜索该完整路径名的完成规范 (compspec)。如果该完整路径名没有完成规范,Bash 会尝试查找最后一个斜杠之后部分的完成规范。如果这些搜索没有找到完成规范,或者命令字没有完成规范,Bash 会使用通过 -D 选项定义的任何完成规范作为默认值。如果没有默认完成规范,Bash 最后会对命令字进行别名展开,并尝试为成功展开后的命令字查找完成规范。
如果未找到 compspec,Bash 将执行上述默认补全(参见让 Readline 为你输入)。否则,一旦找到 compspec,Bash 将使用它来生成匹配单词的列表。
首先,Bash 执行 compspec 指定的操作。这只会返回作为正在完成的单词的前缀的匹配。当使用 -f 或 -d 选项进行文件名或目录名补全时,Bash 使用 shell 变量 FIGNORE 来过滤匹配项。有关 FIGNORE 的描述,请参见 Bash 变量。
接下来,可编程补全根据作为 -G 选项参数提供的路径名扩展模式生成匹配项。模式生成的单词不必匹配正在完成的单词。Bash 使用 FIGNORE 变量来过滤匹配项,但不使用 GLOBIGNORE shell 变量。
接下来,补全会考虑作为 -W 选项参数指定的字符串。首先使用 IFS 特殊变量中的字符作为分隔符拆分该字符串。这会遵循字符串中的 shell 引号,以提供一种机制,使单词可以包含 shell 元字符或 IFS 值中的字符。然后,每个单词会使用大括号扩展、波浪号扩展、参数和变量扩展、命令替换以及算术扩展进行展开,如上所述(参见 Shell 扩展)。结果会使用上述规则进行拆分(参见单词拆分)。展开的结果会与正在补全的单词进行前缀匹配,匹配的单词将成为可能的补全选项。
在生成这些匹配项后,Bash 会执行使用 -F 和 -C 选项指定的任何 Shell 函数或命令。当命令或函数被调用时,Bash 会按照上述描述(参见 Bash 变量)为 COMP_LINE、COMP_POINT、COMP_KEY 和 COMP_TYPE 变量分配值。如果调用的是 Shell 函数,Bash 还会设置 COMP_WORDS 和 COMP_CWORD 变量。调用函数或命令时,第一个参数(1)是正在补全其参数的命令名称,第二个参数(2)是正在补全的单词,第三个参数($3)是当前命令行中位于正在补全单词前的单词。生成的补全项不会对正在补全的单词进行筛选;函数或命令在生成匹配项时具有完全自由,它们不需要与单词的前缀匹配。
使用 -F 指定的任何函数会首先被调用。该函数可以使用任何 shell 功能,包括下面描述的 compgen 和 compopt 内建命令(参见可编程补全内建命令),以生成匹配项。它必须将可能的补全项放入 COMPREPLY 数组变量中,每个数组元素一个。
接下来,使用 -C 选项指定的任何命令将在等同于命令替换的环境中调用。它应当将补全列表输出到标准输出,每行一个补全项。必要时,反斜杠可用于转义换行符。这些将被添加到可能的补全集合中。
在生成所有可能的补全后,Bash 会将使用 -X 选项指定的任何过滤器应用到列表中的补全项。过滤器是用于路径名扩展的模式;模式中的 '&' 会被替换为正在补全的单词的文本。字面上的 '&' 可以用反斜杠转义;在尝试匹配之前,反斜杠会被移除。任何匹配该模式的补全项都会从列表中移除。前导的 '!' 用于取反模式;在这种情况下,Bash 会移除任何不匹配该模式的补全项。如果启用了 nocasematch shell 选项(参见 The Shopt Builtin 中 shopt 的说明),Bash 会在匹配时忽略字母字符的大小写。
最后,可编程补全会将使用 -P 和 -S 选项分别指定的前缀和后缀添加到每个补全项,并将结果作为可能补全列表返回给 Readline。
如果先前应用的操作未生成任何匹配项,并且在定义 compspec 时使用了 -o dirnames 选项,Bash 将尝试完成目录名称的补全。
如果在定义 compspec 时使用了 -o plusdirs 选项,Bash 将尝试完成目录名称的补全,并将任何匹配项添加到可能的补全集合中。
默认情况下,如果找到 compspec,它生成的内容会作为完整的可能补全集合返回给补全代码。默认的 Bash 补全和 Readline 的文件名补全默认功能被禁用。如果在定义 compspec 时向 complete 提供了 -o bashdefault 选项,并且 compspec 没有生成匹配项,Bash 会尝试执行其默认补全。如果 compspec 以及(如有尝试)默认 Bash 补全都没有生成匹配项,并且在定义 compspec 时向 complete 提供了 -o default 选项,可编程补全将执行 Readline 的默认补全。
提供给 complete 和 compopt 的选项可以控制 Readline 如何处理补全。例如,-o fullquote 选项告诉 Readline 将匹配项引用为文件名。有关详细信息,请参见 complete 的描述(参见可编程补全内建命令)。
当 compspec 指示它希望进行目录名补全时,可编程补全函数会强制 Readline 在补全的名称后添加斜杠(如果它们是目录的符号链接),这取决于 mark-directories Readline 变量的值,而不考虑 mark-symlinked-directories Readline 变量的设置。
对于动态修改补全有一些支持。当与使用 -D 指定的默认补全结合使用时,这最为有用。作为补全函数执行的 shell 函数可以通过返回退出状态 124 来表示应该重试补全。如果 shell 函数返回 124,并且更改了与正在尝试补全的命令关联的 compspec(在函数执行时作为第一个参数提供),可编程补全将从头重新启动,并尝试为该命令找到新的 compspec。这可以用于在尝试补全时动态构建一组补全,而不是一次性加载全部补全。
例如,假设有一个 compspec 库,每个文件对应命令名称,以下默认补全函数将动态加载补全:
bash
_completion_loader()
{
. "/etc/bash_completion.d/$1.sh" >/dev/null 2>&1 && return 124
}
complete -D -F _completion_loader -o bashdefault -o default
8.7 Programmable Completion Builtins
有三个内置命令可用于操作可编程补全功能:一个用于指定如何补全某个特定命令的参数,两个用于在补全进行时修改补全内容。
Print Completion: compgen
bash
compgen [-V varname] [option] [word]
根据选项为单词生成可能的补全匹配项,这些选项可以是 complete 内置命令接受的任何选项,但不包括 -p、-r、-D、-E 和 -I,并将匹配项写入标准输出。
💡 无论是compgen还是complete,在指定option时,建议使用-A action的形式,以提升可读性和保持一致性。尽管某些action存在对应的缩写选项,如-k对应-A keyword,-c对应-A command。
如果提供了 -V 选项,compgen 会将生成的补全项存储到索引数组变量 varname 中,而不是写入标准输出。
使用 -F 或 -C 选项时,可编程补全功能设置的各种 shell 变量虽然可以访问,但其值不会有实际用途。
生成的匹配项将以与可编程补全代码直接从具有相同标志的补全规范生成时相同的方式生成。如果指定了 word,则只显示或存储与 word 匹配的补全项。
返回值为 true,除非提供了无效选项或未生成匹配项。
Completion Arguments: complete
bash
complete [-abcdefgjksuv] [-o comp-option] [-DEI] [-A action]
[-G globpat] [-W wordlist] [-F function] [-C command]
[-X filterpat] [-P prefix] [-S suffix] name [name ...]
complete -pr [-DEI] [name ...]
指定每个名称的参数应如何完成。
如果提供了 -p 选项,或者未提供任何选项或名称,则以允许它们被重新用作输入的方式打印现有的补全规范。-r 选项会移除每个名称的补全规范,或者如果未提供名称,则移除所有补全规范。
-D 选项表示其他提供的选项和操作应适用于"默认"命令补全;即对之前未定义补全的命令进行的补全。-E 选项表示其他提供的选项和操作应适用于"空"命令补全;即在空行上进行的补全。-I 选项表示其他提供的选项和操作应适用于行中初始非赋值词的补全,或在命令分隔符(如 ';' 或 '|')之后的补全,这通常是命令名称的补全。如果提供了多个选项,则 -D 选项优先于 -E,二者都优先于 -I。如果提供了 -D、-E 或 -I 之一,则任何其他名称参数将被忽略;这些补全仅适用于选项指定的情况。
在尝试单词补全时应用这些补全规范的过程如上所述(见可编程补全)。
如果指定了其他选项,则其含义如下。-G、-W 和 -X 选项的参数(如果需要,还有 -P 和 -S 选项)应加引号,以防在调用 complete 内建命令之前被扩展。
-o comp-option
comp-option 控制 compspec 行为的多个方面,不仅仅是简单地生成补全。comp-option 可能是以下之一:
-
bashdefault
如果 compspec 没有生成匹配项,则执行默认的 Bash 补全。
-
default
如果 compspec 没有生成匹配项,则使用 Readline 的默认文件名补全。
-
dirnames
如果 compspec 没有生成匹配项,则执行目录名补全。
-
filenames
告诉 Readline compspec 生成的是文件名,以便它可以执行任何与文件名相关的处理(例如向目录名添加斜杠、引用特殊字符或抑制尾随空格)。此选项用于与使用 -F 指定的 shell 函数一起使用。
-
fullquote
告诉 Readline 对所有已完成的单词加引号,即使它们不是文件名。
-
noquote
告诉 Readline 如果已完成的单词是文件名,则不要加引号(默认会对文件名加引号)。
-
nosort
告诉 Readline 不要按字母顺序对可能的补全列表进行排序。
-
nospace
告诉 Readline 不要在行尾完成的单词后追加空格(默认会追加空格)。
-
plusdirs
在生成 compspec 定义的任何匹配项之后,尝试目录名补全,并将任何匹配项添加到其他操作的结果中。
-A action
该操作可能是以下之一,用于生成可能完成项的列表:
-
alias
别名名称。也可以用 -a 指定。
-
arrayvar
数组变量名称。
-
binding
Readline 键绑定名称(参见可绑定的 Readline 命令)。
-
builtin
Shell 内置命令的名称。也可以用 -b 指定。
-
command
命令名称。也可以用 -c 指定。
-
directory
目录名称。也可以用 -d 指定。
-
disabled
已禁用的 shell 内置命令名称。
-
enabled
已启用的 shell 内置命令名称。
-
export
已导出的 shell 变量名称。也可以用 -e 指定。
-
file
文件和目录名称,类似于 Readline 的文件名补全。也可以用 -f 指定。
-
function
Shell 函数的名称。
-
group
组名。也可以指定为 -g。
-
helptopic
由 help 内置命令接受的帮助主题(参见 Bash 内置命令)。
-
hostname
主机名,从 HOSTFILE shell 变量指定的文件中获取(参见 Bash 变量)。
-
job
作业名称,如果作业控制处于活动状态。也可以指定为 -j。
-
keyword
Shell 保留字。也可以指定为 -k。
-
running
正在运行的作业名称,如果作业控制处于活动状态。
-
service
服务名称。也可指定为 -s。
-
setopt
用于 set 内置命令的 -o 选项的有效参数(参见 Set 内置命令)。
-
shopt
shopt 内置命令接受的 shell 选项名称(参见 Bash 内置命令)。
-
signal
信号名称。
-
stopped
已停止作业的名称,如果作业控制已启用。
-
user
用户名。也可指定为 -u。
-
variable
所有 shell 变量的名称。也可指定为 -v。
-C command
命令在子 shell 环境中执行,其输出被用作可能的补全。参数传递方式与 -F 选项相同。
-F function
shell 函数 function 在当前的 shell 环境中执行。当它被执行时,第一个参数 (1) 是正在完成其参数的命令的名称,第二个参数 (2) 是正在完成的单词,第三个参数 ($3) 是正在完成的单词前面的单词,如上文所述(参见可编程补全)。当函数执行完成后,可编程补全会从 COMPREPLY 数组变量的值中获取可能的补全项。
-G globpat
展开文件名扩展模式 globpat 以生成可能的补全项。
-P prefix
在应用所有其他选项后,将 prefix 添加到每个可能补全项的开头。
-S suffix
在应用所有其他选项后,将 suffix 附加到每个可能补全项的末尾。
-W wordlist
使用 IFS 特殊变量中的字符作为分隔符拆分词表,并展开每个生成的单词。在词表中会遵循 Shell 引号,以提供一种机制,使单词可以包含 Shell 元字符或 IFS 值中的字符。可能的补全项是结果列表中匹配正在补全单词前缀的成员。
-X filterpat
filterpat 是用于文件名扩展的模式。它应用于由前面的选项和参数生成的可能完成列表中,每个匹配 filterpat 的完成项都会从列表中移除。在 filterpat 前加上 '!' 表示取反;在这种情况下,任何不匹配 filterpat 的完成项都会被移除。
除非提供了无效选项、提供了除 -p、-r、-D、-E 或 -I 以外且没有名称参数的选项、尝试移除不存在名称的补全规范,或在添加补全规范时发生错误,否则返回值为 true。
Completion Option: compopt
bash
compopt [-o option] [-DEI] [+o option] [name]
根据选项修改每个名称的完成选项,或者在未提供名称时,修改当前正在执行的完成。如果未提供选项,则显示每个名称或当前完成的完成选项。选项的可能值是上面描述的 complete 内置命令有效的那些值。
-D 选项表示其他提供的选项应应用于"默认"命令完成;-E 选项表示其他提供的选项应应用于"空"命令完成;-I 选项表示其他提供的选项应应用于行首单词的完成。这些与 complete 内置命令的处理方式相同。
如果提供了多个选项,-D 选项优先于 -E,二者都优先于 -I。
返回值为 true,除非提供了无效选项、尝试修改不存在完成规格的名称的选项,或发生输出错误。
8.8 A Programmable Completion Example
获得比默认动作 complete 和 compgen 提供的更多补全功能最常见的方法是使用一个 shell 函数,并使用 complete -F 将其绑定到特定命令。
以下函数为 cd 内置命令提供补全。这是一个相当好的示例,展示了当 shell 函数用于补全时必须做什么。该函数使用作为 $2 传递的单词来确定要补全的目录名。你也可以使用 COMP_WORDS 数组变量;当前的单词由 COMP_CWORD 变量索引。
该函数依赖于 complete 和 compgen 内置命令来完成大部分工作,仅增加了 Bash cd 在接受基本目录名称之外的功能:波浪号扩展(见波浪号扩展)、在 $CDPATH 中搜索目录,如上所述(见 Bourne Shell 内置命令)、以及对 cdable_vars shell 选项的基本支持(见 Shopt 内置命令)。_comp_cd 会修改 IFS 的值,使其仅包含换行符,以便处理包含空格和制表符的文件名------compgen 会将生成的可能补全项逐行打印出来。
可能的补全项存入 COMPREPLY 数组变量,每个数组元素对应一个补全项。可编程补全系统在函数返回时会从那里获取补全项。
bash
# A completion function for the cd builtin
# based on the cd completion function from the bash_completion package
_comp_cd()
{
local IFS=$' \t\n' # normalize IFS
local cur _skipdot _cdpath
local i j k
# Tilde expansion, which also expands tilde to full pathname
case "$2" in
\~*) eval cur="$2" ;;
*) cur=$2 ;;
esac
# no cdpath or absolute pathname -- straight directory completion
if [[ -z "${CDPATH:-}" ]] || [[ "$cur" == @(./*|../*|/*) ]]; then
# compgen prints paths one per line; could also use while loop
IFS=$'\n'
COMPREPLY=( $(compgen -d -- "$cur") )
IFS=$' \t\n'
# CDPATH+directories in the current directory if not in CDPATH
else
IFS=$'\n'
_skipdot=false
# preprocess CDPATH to convert null directory names to .
_cdpath=${CDPATH/#:/.:}
_cdpath=${_cdpath//::/:.:}
_cdpath=${_cdpath/%:/:.}
for i in ${_cdpath//:/$'\n'}; do
if [[ $i -ef . ]]; then _skipdot=true; fi
k="${#COMPREPLY[@]}"
for j in $( compgen -d -- "$i/$cur" ); do
COMPREPLY[k++]=${j#$i/} # cut off directory
done
done
$_skipdot || COMPREPLY+=( $(compgen -d -- "$cur") )
IFS=$' \t\n'
fi
# variable names if appropriate shell option set and no completions
if shopt -q cdable_vars && [[ ${#COMPREPLY[@]} -eq 0 ]]; then
COMPREPLY=( $(compgen -v -- "$cur") )
fi
return 0
}
我们使用 -F 选项来安装完成函数,以实现自动补全:
bash
# Tell readline to quote appropriate and append slashes to directories;
# use the bash default completion for other arguments
complete -o filenames -o nospace -o bashdefault -F _comp_cd cd
由于我们希望 Bash 和 Readline 帮我们处理其他一些细节,我们使用了几个选项来告诉 Bash 和 Readline 应该做什么。-o filenames 选项告诉 Readline 可能的补全项应该作为文件名处理,并进行适当的引用。这个选项还会导致 Readline 在可以确定是目录的文件名后添加斜杠(这也是为什么如果我们使用通过 CDPATH 找到的目录,可能需要扩展 _comp_cd 来添加斜杠:Readline 无法判断这些补全项是目录)。-o nospace 选项告诉 Readline 不要在目录名称后追加空格字符,以防我们想在其后继续追加内容。-o bashdefault 选项会引入其余的 "Bash 默认" 补全------Bash 添加到默认 Readline 集合中的可能补全项。这些包括命令名补全、以 '' 或 '{' 开头的变量补全、包含路径名扩展模式的补全(参见文件名扩展)等。
一旦使用 complete 安装,每次我们尝试为 cd 命令进行单词补全时,_comp_cd 都会被调用。
更多示例------包括绝大多数常用 GNU、Unix 和 Linux 命令的完整补全集合------可作为 bash_completion 项目的一部分获得。许多 GNU/Linux 发行版默认安装了该项目。该项目最初由 Ian Macdonald 编写,目前托管在 Github bash_completion项目。还有适用于其他系统(如 Solaris 和 Mac OS X)的移植版本。
bash_completion 包的较旧版本随 bash 一起分发,位于 examples/complete 子目录中。
bash
$ sudo yum install bash-completion -y
Last metadata expiration check: 3:27:23 ago on Thu 15 Jan 2026 05:05:12 AM UTC.
Dependencies resolved.
===============================================================================================================
Package Architecture Version Repository Size
===============================================================================================================
Installing:
bash-completion noarch 1:2.11-5.el9 ol9_baseos_latest 781 k
Transaction Summary
===============================================================================================================
Install 1 Package
Total download size: 781 k
Installed size: 1.0 M
Downloading Packages:
bash-completion-2.11-5.el9.noarch.rpm 1.0 MB/s | 781 kB 00:00
---------------------------------------------------------------------------------------------------------------
Total 996 kB/s | 781 kB 00:00
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing : 1/1
Installing : bash-completion-1:2.11-5.el9.noarch 1/1
Verifying : bash-completion-1:2.11-5.el9.noarch 1/1
Installed:
bash-completion-1:2.11-5.el9.noarch
Complete!
安装目录位于 /usr/share/bash-completion。