Zsh 自动补全脚本入门
入门基础
zsh 如何补全一个命令
命令的补全方法保存在以下划线 _
开头的补全文件中,这些文件通常保存在 $fpath
变量所指定的路径下。当然,我们也通过可以在 ~/.zshrc
文件中增加如下一行代码,从而给 $fpath
新增一个搜索路径。
1 | fpath=(~/newdir $fpath) |
补全文件的第一行定义如下所示: 1
#compdef foobar
上述这行代码表示本文件定义了为 foobar
命令进行自动补全的代码。
我们还可以直接使用 compdef
(比如:在
~/.zshrc
中)命令指定将哪个方法作为自动补全的补全方法,如下所示:
1
> compdef _function foobar
除此之外,我们还可以同时为多个命令指定同一个补全方法。
1
> compdef _function foobar goocar hoobar
甚至,还可以传递参数至补全方法中。 1
> compdef '_function arg1 arg2' foobar
更多细节参见 传送门。
通用 GNU 命令补全
很多 GNU 命令都有着一套标准的方式来对选项的描述进行罗列显示(当使用
--help
选项时)。对于这些命令,我们可以使用
_gnu_generic
方法自动创建补全方法,如下所示:
1
> compdef _gnu_generic foobar
当然,也可以同时为多个命令指定 _gnu_generic
补全方法。
1
> compdef _gnu_generic foobar goocar hoodar
这行代码可以直接添加到 ~/.zshrc
中。
复制补全功能
假设我们希望一个命令 cmd1
具有与 cmd2
一样的补全功能,并且 cmd2
的自动补全功能已经定义好了,那么我们可以使用如下方式进行复制。
1
> compdef cmd1=cmd2
当我们为一个命令创建了一个 alias
时,使用这种方式可以完整复制原始命令的补全功能!
如何编写补全方法
那么,如何编写属于我们自己的补全方法呢?照葫芦画瓢是一种非常好的上手方式。我们可以阅读一些别人写的补全方法。在本机的文件系统下,我们可以通过
$fpath
环境变量去搜索已有的补全功能文件,比如:/usr/local/share/zsh/site-functions
。
我们可以看到有一个 _arguments
方法在这些补全功能文件中被大量使用。_arguments
是一个可以快速实现简单补全功能的工具方法。本质上,_arguments
方法是对内置方法 compadd
进行了封装。内置方法
compadd
是将补全单词添加至命令行,并控制补全行为的关键方法。不过,在大多数情况下,我们都不需要使用
compadd
方法,因为 zsh 提供了大量类似
_arguments
、_describe
这样的工具方法,简单易用。
对于非常简单的补全功能,_describe
方法甚至都够用了。
工具方法
本文仅罗列一部分常用的工具方法。完整的工具方法及其使用说明,参见 传送门。
本节我们将简单介绍一部分常用的工具方法,下一节我们将介绍如何使用这些工具方法。
用于整体补全功能的工具方法
方法名称 | 功能描述 |
---|---|
_alternative |
用于生成补全候选列表 |
_arguments |
用于指定如何对一个命令进行选项补全和参数补全,采用 UNIX 风格选项 |
_describe |
用来创建仅由单词、描述信息组成的简单补全(不包含
Action)。比 _arguments 方法更简单 |
_gnu_generic |
用来为能够响应 --help
选项的命令进行补全 |
_regex_arguments |
创建一个方法,能够使用正则表达式匹配命令行参数,从而执行 Action 或补全方法 |
为单个单词进行复杂补全的方法
方法名称 | 功能描述 |
---|---|
_values |
用于为任意关键词(值)及其参数进行补全,或者此类组合的逗号分隔列表 |
_combination |
用于补全值的组合,比如:hostname
和 username 的组合 |
_multi_parts |
用于对单词的多个部分进行补全,其中每个部分使用某个字符分隔,例如对部分文件路径进行补全:/u/i/sy
补全为 /usr/include/sys |
_sep_parts |
类似
_multi_parts ,不过它允许不同的补全部分使用不同的分隔符 |
_sequence |
用于封装另一个补全方法,从而对另一个补全方法生成的匹配项进行补全 |
为指定类型的对象进行补全的方法
方法名称 | 功能描述 |
---|---|
_path_files |
用于补全文件路径。有多个选项可以控制其行为 |
_files |
使用除了 -g 和
-/ 以外的所有选项调用 _path_files
方法。这些选项取决于文件模式样式设置 |
_net_interfaces |
用于补全网络接口名称 |
_users |
用于补全用户名 |
_groups |
用于补全用户组名称 |
_options |
用于补全 shell 选项的名称 |
_parameters |
用于补全 shell 参数、变量的名称(可以限定为与模式匹配的名称) |
处理已缓存补全逻辑的方法
如果我们有大量的补全,可以将它们保存在缓存文件中,以便快速加载。
方法名称 | 功能描述 |
---|---|
_cache_invalid |
设置指定的缓存标识符对应的补全逻辑是否需要重建 |
_retrieve_cache |
从缓存文件中检索补全逻辑信息 |
_store_cache |
将指定的缓存标识符对应的补全逻辑信息存储在缓存文件中 |
其他方法
方法名称 | 功能描述 |
---|---|
_message |
用于在无法补全时显示帮助信息 |
_regex_words |
用于为 _regex_arguments
命令生成调用参数。比手动编写参数更简单 |
_guard |
可以在 _arguments
的调用参数的 ACTION
部分中使用,也可以在类似的方法用来检查单词是否已补全 |
Action
类似
_arguments
、_regex_arguments
、_alternative
、_value
这样的工具方法,它都都可以有一系列的调用参数。不过这些参数都遵循了一些预定义格式(或规范)的字符串,我们称之为
参数格式,每个参数格式的最后一个参数/选项是 Action
部分。Action 表示如何补全相应的参数。Action
可以有多种类型:
方法名称 | 功能描述 |
---|---|
() |
参数是必须的,但是没有匹配项。相当于空白占位符 |
(ITEM1 ITEM2) |
可能的匹配列表 |
((ITEM1\:'DESC1' ITEM2\:'DESC2')) |
可能的匹配列表,附带描述信息。注意:Action 中的引号不能和其所在的参数格式字符串的引号相同 |
->STRING |
将 $state 设置为 STRING
并继续后续逻辑(当工具方法被调用后,可以使用 case 语句检查
$state ) |
FUNCTION |
将要调用的方法名称,该方法能够生成匹配项或调用其他
Action,如:_file 、_message |
{EVAL-STRING} |
将字符串作为 Shell
代码执行,可以生成匹配项。也可以调用工具方法并传入参数,如:_values 、_describe |
=ACTION |
在不改变补全位置节点的情况下,向补全命令行中插入一个伪单词 |
注意:并不是所有的工具方法都可以使用所有类型的
Action。比如:_regex_arguments
、_alternative
方法不能使用 ->STRING
类型。
基于 _describe
的简单补全功能
_describe
方法可用于
选项/参数的顺序和位置并不重要
的简单补全功能。我们只需要创建一个数组参数来持有选项及其描述信息,然后将这个数组参数作为一个参数传递给
_describe
。下面的例子中,创建了两个补全候选项
c
和
d
,并提供了描述信息(注意:代码应该定义在名为
_cmd
文件中,并位于 $fpath
的路劲下):
1
2
3
4#compdef cmd
local a -subcmds
subcmds=('c:description for c command' 'd:description for d command')
_describe 'command' subcmds
除此之外,我们还可以向 _describe
方法传入多个不同的列表,并使用 --
进行分隔。
1
2
3
4local -a subcmds topics
subcmds=('c:description for c command' 'd:description for d command')
topics=('e:description for e help topic' 'f:description for f help topic')
_describe 'command' subcmds -- topics
如果两个候选项的描述信息相同,_describe
会将两者合并到同一行,并确保所有候选项的描述信息列对齐。_describe
方法可以用在
_alternative
、_arguments
、_regex_arguments
的参数格式中的 ACTION
部分。在这种情况下,我们必须将其与参数放在一起,如:'TAG:DESCRIPTION:{_describe 'value' options'}
基于 _alternative
的补全功能
与 _describe
方法类似,_alternative
方法可用于 选项/参数的顺序和位置并不重要
的简单补全功能。与 _describe
方法不同,_alternative
方法并不使用固定的匹配项,而是调用其他方法来生成候选项。此外,_alternative
允许混合使用不同类型的补全候选项。
_alternative
方法的参数格式是
'TAG:DESCRIPTION:ACTION'
。其中,TAG
指定补全匹配项的类型;DESCRIPTION
表示补全候选项列表的描述信息;ACTION
则是前文所描述的一种
Action 类型(_alternative
不支持 ->STRING
和 =ACTION
类型)。 1
_alternative 'arguments:custom arg:(a b c)' 'files:filename:_files'
_alternative
方法的第一个参数中加入了三个候选项
a
、b
、c
;第二个参数调用了
_files
方法来对文件路径进行补全。
我们可以将 _alternative
方法参数分成多行,使用
\
进行换行。如下所示,为每个自定义参数添加了描述信息:
1
2
3_alternative \
'args:custom arg:((a\:"description a" b\:"description b" c\:"description c"))' \
'files:filename:_files'
如果我们想把参数传递给 _files
方法,可以像下面一下直接包含进来: 1
2
3
4_alternative \
'args:custom arg:((a\:"description a" b\:"description b" c\:"description c"))'\
'files:filename:_files -/'
如果想使用参数扩展来创建补全列表,我们需要使用双引号来引用
_alternative
方法的调用参数。如下所示为一个简单示例:
1
2
3_alternative \
"dirs:user directory:($userdirs)" \
"pids:process ID:($(ps -A | awk '{print $1}'))"
在这种情况下,第一个调用参数添加了存储在 $userdirs
变量中的单词,第二个调用参数则执行 ps -A | awk '{print $1}'
来获取一系列的 PID
以作为补全候选项。在实际开发中,我们可以直接使用已有的
_pids
方法。
我们还可以在 ACTION
中使用其他的工具方法(如:_values
)来生成实现更加复杂的补全功能,如下所示为一个简单示例:
1
2
3_alternative \
"directories:user directory:($userdirs)" \
'options:comma-separated opt: _values -s , letter a b c'
上述例子会对 $userdirs
中的项进行补全,同时也会对包含
a
、b
、c
的逗号分隔列表进行补全。注意:在 _values
方法前需要有一个初始的空格。
和 _describe
方法一样,_alternative
方法自身也可以作为
_arguments
、_regex_arguments
等方法的调用参数中的 ACTION
部分。
基于 _arguments
的补全功能
通过调用 _arguments
方法,我们就能够实现复杂的补全功能。_arguments
方法能够处理带有各种选项及常规参数的命令。与
_alternative
,_arguments
方法相似,它以格式化的字符串作为其调用参数。这些格式化参数能够指定选项及其对应的选项参数(如:-f filename
)、命令参数。
_arguments
方法的调用参数的基本参数格式为
-OPT[DESCRIPTION]
,如下所示为一个简单示例:
1
_arguments '-s[sort output]' '--l[long output]' '-l[long output]'
稍微复杂一点的 _arguments
方法的参数格式可以是
-OPT[DESCRIPTION]:MESSAGE:ACTION
。其中,MESSAGE
和 ACTION
的含义与上述 _alternative
中描述的一样。如下所示为一个简单示例: 1
_arguments '-f[input file]:filename:_files'
_arguments
方法的参数格式还可以是
N:MESSAGE:ACTION
。其中,N
表示第 N
个命令参数,MESSAGE
和 ACTION
还是和之前一样。如果省略
N
,则仅表示下一个命令参数(在已指定的参数之后)。如果在
N
的前面或后面加上冒号
:
,则表示参数是可选的。如下为一个简单的示例:
1
_arguments '-s[sort output]' '1:first arg:_net_interfaces' '::optional arg:_files' ':next arg:(a b c)'
上面这个例子中,第一个参数是网络接口名,下一个可选参数是一个文件名,最后一个参数是
a
、b
、c
其中之一,s
选项可以在任意位置进行补全。
_arguments
方法支持上述所有类型的
ACTION
。这样的话,我们可以使用 case
分支来调用不同的 Action。 1
2
3
4
5
6
7
8
9
10
11_arguments '-m[music file]:filename:->files' '-f[flags]:flag:->flags'
case "$state" in
files)
local -a music_files
music_files=( Music/**/*.{mp3,wav,flac,ogg} )
_multi_parts / music_files
;;
flags)
_values -s , 'flags' a b c d e
;;
esac
在这个例子中,music file 的路径调用 _multi_parts
方法来进行目录补全;flags 则可以调用 _value
方法,以逗号
,
分隔列表的形式进行补全。
本节,我们简单地介绍了 _arguments
方法的基本使用方法,当然,我们还可以指定互斥选项、重复选项/参数、以
+
开头而非 -
开头的选项等等。更多的使用方法,可以参见官方教程。此外,还可以参考本文末尾提到的教程。
基于
_regex_arguments
和 _regex_words
的补全功能
如果我们有一个复杂的命令行格式,它有多种可能的参数序列,那么
_regex_arguments
方法可能就是一个比较适合的补全方法。
_regex_arguments
方法会创建一个补全方法,补全方法的名字由第一个调用参数指定。因此,我们需要先调用
_regex_arguments
方法来创建补全方法,然后再调用该补全方法。如下所示: 1
2_regex_arguments _cmd OTHER_ARGS..
_cmd "$@"
上述例子中, OTHER_ARGS
用于匹配并补全命令行中的单词,其可以是多个调用参数列表。这些调用参数列表可以用
'|'
进行二选一。我们还可以使用括号来指定选择的层级,不过,括号必须使用斜杠
\
或引号 ''
进行转义,
如:\(
、\)
或
'('
、')'
。
如下所示为一个简单示例: 1
2_regex_arguments _cmd SEQ1 '|' SEQ2 \( SEQ2a '|' SEQ2b \)
_cmd "$@"
上述例子中,指定了一个与 SEQ1
或 SEQ2
(后跟
SEQ2a
或
SEQ2b
)相匹配的命令行。这本质上就是使用正则表达式描述命令行的参数。
每个调用参数列表之前必须包含一个 /PATTERN/
部分,后跟一个可选的 :TAG:DESCRIPTION:ACTION
部分。
每个 PATTERN
是一个正则表达式,用于匹配命令行上的一个单词。这些模式(pattern)会被顺序处理,直到遇到一个不匹配的模式,此时将执行对应的
Action 以得到该单词的补全项。
注意,必须要有一个模式来匹配命令自身。后面,我们将进一步介绍
PATTERN
。
_regex_arguments
方法参数中
:TAG:DESCRIPTION:ACTION
部分的含义与
_alternative
方法参数类似,不同的地方在于
_regex_arguments
中开头多了一个
:
,此外,它还允许调用所有的 Action 类型。
如下所示为一个简单示例: 1
2
3
4_regex_arguments _cmd /$'[^\0]##\0'/ \( /$'word1(a|b|c)\0'/ ':word:first word:(word1a word1b word1c)' '|'\
/$'word11(a|b|c)\0'/ ':word:first word:(word11a word11b word11c)' \( /$'word2(a|b|c)\0'/ ':word:second word:(word2a word2b word2c)'\
'|' /$'word22(a|b|c)\0'/ ':word:second word:(word22a word22b word22c)' \) \)
_cmd "$@"
在这个例子中,第一个单词可以是 word1
(后跟
a
、b
、c
中的任意一个)或
word11
(后跟 a
、b
、c
中的任意一个);如果第一个单词包含 11
,那么第二个单词可以是
word2
(后跟 a
、b
、c
中的任意一个)或文件名。
这个例子看起来太复杂了,有一种简单的方式是使用
_regex_words
方法为 _regex_arguments
创建方法调用参数。
模式
我们可能注意到上面这个示例中, /PATTERN/
看上去并不像正常的正则表达式。这里的字符串参数采用 $'foo\0'
的形式,通过这种形式将字符串中的 \0
解释为
null
字符,从而对参数内容进行单词分隔。如果我们没有在模式的末尾添加
\0
,则可能无法匹配下一个单词。如果要在模式中使用变量的内容,我们可以给它添加双引号,以便对其进行扩展,然后再在后面添加一个含有
null
字符的字符串,如:"$somevar"$'\0'
。
模式的正则表达式语法似乎与普通的正则表达式有些不同。虽然没有找到相应的说明文档,但是能够总结出以下特殊字符的用法:
字符 | 使用描述 |
---|---|
* |
通配符-任意数量的字符 |
? |
通配符-单个字符 |
# |
零个或多个前一个字符(类似于正则表达式中的
* ) |
## |
一个或多个前一个字符(类似于正则表达式中的
+ ) |
_regex_words
_regex_words
方法使得我们可以更容易地为
_regex_arguments
方法创建调用参数。_regex_words
方法的结果可以存储在一个变量中,从而能够作为
_regex_arguments
方法的调用参数。
使用 _regex_words
创建 _regex_arguments
的调用参数时,需要向其提供一个标签,其次是描述信息,接着是描述各个单词的参数格式。参数格式为
WORD:DESCRIPTION:SPEC
,其中 WORD
表示要补全的单词,DESCRIPTION
表示描述信息,SPEC
可以是 _regex_words
创建的另一个变量,用于指定当前单词之后的单词,如果当前单词之后没有其他单词,则为空白。如下所示为一个简单的例子:
1
_regex_words firstword 'The first word' 'word1a:a word:' 'word1b:b word:' 'word1c:c word'
_regex_words
方法执行的结果会被存储在
$reply
数组中,因此我们需要在 $reply
的值改变之前将其保存到其他数组中,如下所示: 1
2
3local -a firstword
_regex_words word 'The first word' 'word1a:a word:' 'word1b:b word:' 'word1c:c word'
firstword="$reply[@]"
基于此,我们可以这样调用 _regex_arguments
方法:
1
2_regex_arguments _cmd /$'[^\0]##\0'/ "${firstword[@]}"
_cmd "$@"
这里我们来为初始命令添加了一个其他的模式。
下面是一个更加复杂的例子,我们为命令行中不同的单词调用
_regex_words
方法。 1
2
3
4
5
6
7
8
9
10
11
12
13local -a firstword firstword2 secondword secondword2
_regex_words word1 'The second word' 'woo:tang clan' 'hoo:not me'
secondword=("$reply[@]")
_regex_words word2 'Another second word' 'yee:thou' 'haa:very funny!'
secondword2=("$reply[@]")
_regex_words commands 'The first word' 'foo:do foo' 'man:yeah man' 'chu:at chu'
firstword=("$reply[@]")
_regex_words word4 'Another first word' 'boo:scare somebody:$secondword' 'ga:baby noise:$secondword'\
'loo:go to the toilet:$secondword2'
firstword2=("$reply[@]")
_regex_arguments _hello /$'[^\0]##\0'/ \( "${firstword[@]}" '|' "${firstword2[@]}" \)"
_hello "$@"
在上面这个例子中,第一个单词可以是
foo
、man
、chu
、boo
、ga
、loo
其中之一。如果第一个单词是 boo
或
ga
,那么第二个单词可以是 woo
或
hoo
。如果第一个单词是 loo
那么第二个单词可以是
yee
或 haa
,其他情况下没有第二个单词。
我们可以看一下 _ip
方法的具体实现,其内部很好地运用了
_regex_words
方法。
基于
_values
、_sep_parts
、_multi_parts
的复杂补全功能
_values
、_sep_parts
、_multi_parts
方法可以单独使用,也可以在
_alternative
、_arguments
、_regex_arguments
方法的调用参数的 ACTION
中使用。
如下所示是使用空格分隔 mp3 文件列表的例子: 1
_values 'mp3 files' ~/*.mp3
如下所示是使用逗号分隔 session id 列表的例子: 1
_values -s , 'session id' "${(uonzf)$(awk '{print $1}')}"
如下所示是补全 foo@news:woo
、foo@news:laa
或 bar@news:woo
等的例子: 1
_sep_parts '(foo bar)' @ '(news ftp)' : '(woo laa)'
如下所示是一次补全 MAC 地址的例子: 1
_multi_parts : '(00:11:22:33:44:55 00:23:34:45:56:67 00:23:45:56:67:78)'
使用 compadd 直接添加补全单词
为了更准确地进行控制,我们可以使用内置的 compadd
方法直接添加补全单词。这个方法有很多不同的参数,用于控制如何显示补全以及单词补全后如何更改命令行上的文本。更多信息可以查看官方文档
传送门。这里只给出一些简单的示例。
在可能的补全列表中添加单词: 1
compadd foo bar blah
和上面例子一样,区别在于会额外显示解释信息: 1
compadd -X 'Some completions' foo bar blah
和第一个例子一样,区别在于会在自动补全的单词之前插入前缀
what_
: 1
compadd -P what_ foo bar blah
和第一个例子一样,区别在于会在自动补全的单词之后插入后缀
_todo
: 1
compadd -S _todo foo bar blah
和第一个例子一样,区别在于当在后缀之后输入空白字符时,会自动删除
_todo
后缀: 1
compadd -P _todo -q foo bar blah
将 $wordsarray
数组中的单词添加至可能的补全列表中:
1
compadd -a wordsarray
测试与调试
重载补全方法: 1
2> unfunction _func
> autoload -U _func
下面这些方法能够提供一些有用的信息。
方法 | 描述 |
---|---|
_complete_help |
当对当前光标位置进行补全时,显示相关的上下文名称、标签以及补全方法 |
_complete_debug |
执行普通的补全,会在一个临时文件重保存补全系统所执行的 shell 命令 trace 信息 |
注意事项
请记住,在包含补全方法的文件的开头应该包含 #compdef
这一行。
请注意对 _arguments
、_regex_arguments
方法的参数格式使用正确的引号类型:如果在参数格式中需要扩展参数,请使用双引号,否则请使用单引号。
检查
_arguments
、_alternative
、_regex_arguments
的参数格式中的冒号 :
数量和位置是否正确。
在使用 _regex_arguments
方法是,请记住要包含一个初始模式以匹配命令自身。
请记住在 _regex_arguments
的任何 PATTERN
参数的末尾加上一个空字符 $'\0'
。
提示
有时候我们会遇到这样的情况:子命令后面只能有一个选项,而在子命令后输入
tab 键时,zsh
会自动补全此选项。如果我们希望它在补全之前列出其描述信息,则可以向
ACTION
中添加另一个空选项(如:\:
),例如:TAR:DESCRIPTION:((opt1\:"description for opt1" \:))
。注意,这仅适用于其支持
ACTION
的工具方法(如:_arguments
、_regex_arguments
)。
其他资料
一个关于 _arguments
方法的基本教程,传送门。
一个关于 _arguments
方法的高级教程,传送门。
zshcompsys
手册,传送门。