如何为带参数转换逻辑的自定义脚本实现zsh补全并复用原有补全规则
Zsh包装脚本动态补全实现
问题说明
compdef cmd1=service 可用于定义补全别名,但该方式仅适用于新旧命令参数完全一致的场景。
例如存在一个辅助脚本s,会在执行目标命令前重写传入参数,对应映射规则如下:
| 输入内容 | 实际执行命令 | |---------------+----------------------------| | s | systemctl | | s q | systemctl status | | s q foo | systemctl status foo | | s j foo | journalctl --unit foo | | s r foo | sudo systemctl restart foo |
给脚本加上PRINT_ONLY=1的开关后,执行时只会打印实际要运行的命令不会真的执行,比如执行PRINT_ONLY=1 s只会输出systemctl。
已知systemctl、journalctl、sudo均已配置好对应的zsh补全规则,无需重复实现这些命令的补全逻辑,需要为脚本s定义zsh补全,让补全系统基于转换后的实际命令触发对应补全,达到类似伪代码function _s() { get_completions $(PRINT_ONLY=1 s "$@") ; }的效果。
实现方法
核心思路是替换补全上下文中的命令、参数、光标位置信息,直接复用zsh已有的补全规则,不需要自己写任何业务参数的补全逻辑,补全函数代码如下:
_s() { # 执行脚本拿到转换后的真实命令,按zsh语法规则拆分为参数数组 local -a target_cmd target_cmd=("${(@Q)${(@z)$(PRINT_ONLY=1 s "${(@)words}[2,-1]")}}") # 替换当前补全上下文的命令、词表、光标偏移 local real_cmd=$target_cmd[1] curcontext="${curcontext%:*:*}:${real_cmd}:" words=($target_cmd) (( CURRENT = CURRENT + ${#target_cmd} - $# - 1 )) # 调用zsh默认补全入口,自动匹配真实命令的已有补全规则 _normal } # 绑定补全函数到s命令 compdef _s s
注意事项
- 代码里的
(@z)拆分标记会按照zsh的命令行解析规则拆分脚本输出的命令,自动处理引号、转义符、空格分隔的参数,不会把带空格的路径之类的参数拆错。 - 不管转换后的真实命令是systemctl、journalctl还是嵌套了sudo,只要这些命令本身已经有可用的zsh补全,这套逻辑都能直接适配,不需要额外加分支判断。
- 后续如果修改
s脚本的参数映射规则,只要保证PRINT_ONLY=1模式下输出的命令和实际执行的命令完全一致,补全逻辑不需要做任何修改,会自动同步。
内容的提问来源于stack exchange,提问作者Vladimir Panteleev
相关产品推荐
相关产品推荐

