文章摘要
文章介绍了如何为Bash和Zsh编写自定义的Tab补全功能,特别是在Mill构建工具1.0.3版本中的实现。由于Bash和Zsh的补全API不同,且Zsh支持显示补全描述而Bash不支持,作者通过努力实现了跨平台的补全功能,并提供了详细的参考示例,帮助开发者为自己的命令行工具设置类似的补全功能。
文章总结
为Bash和Zsh编写简单的Tab补全功能
在命令行工具中,Tab补全功能非常实用,但由于Bash和Zsh的补全API不同,且Zsh支持显示补全描述而Bash不支持,因此为两者设置补全功能较为复杂。本文将介绍如何为Bash和Zsh编写兼容的Tab补全功能,并添加补全描述等增强功能。
基本Tab补全
在Bash和Zsh中,Tab补全的工作原理是注册一个处理函数,当用户按下<TAB>时,该函数会被调用。函数接收当前输入的命令行单词和光标所在的单词索引,并生成可能的补全选项列表。以下是一个简单的示例:
```bash generatefoocompletions() { local idx=$1; shift local words=( "$@" ) local currentword=${words[idx]}
local array=(apple apricot banana cherry durian) for elem in "${array[@]}"; do if [[ $elem == "$current_word"* ]]; then echo "$elem"; fi done }
completefoobash() { local raw=($(generatefoocompletions "$COMPCWORD" "${COMPWORDS[@]}")) COMPREPLY=( "${raw[@]}" ) }
completefoozsh() { local -a raw raw=($(generatefoocompletions "$CURRENT" "${words[@]}")) compadd -- $raw }
if [ -n "${ZSHVERSION:-}" ]; then autoload -Uz compinit compinit compdef _completefoozsh foo elif [ -n "${BASHVERSION:-}" ]; then complete -F completefoo_bash foo fi ```
Zsh补全描述
为了让Zsh显示补全描述,可以在_generate_foo_completions中生成包含描述的长字符串,并在Zsh中使用compadd -d传递描述信息。Bash不支持补全描述,因此需要将描述部分去除。
```bash generatefoocompletions() { local idx=$1; shift local words=( "$@" ) local currentword=${words[idx]}
local array=( "apple: a common fruit" "apricot: sour fruit with a large stone" "banana: starchy and high in potassium" "cherry: small and sweet with a large pit" "durian: stinky spiky fruit" ) for elem in "${array[@]}"; do if [[ $elem == "$current_word"* ]]; then echo "$elem"; fi done }
completefoobash() { local IFS=$'\n' local raw=($(generatefoocompletions "$COMPCWORD" "${COMPWORDS[@]}")) local trimmed=() for d in "${raw[@]}"; do trimmed+=( "${d%%:*}" ); done COMPREPLY=( "${trimmed[@]}" ) }
completefoozsh() { local -a raw trimmed local IFS=$'\n' raw=($(generatefoocompletions "$CURRENT" "${words[@]}")) for d in $raw; do trimmed+=( "${d%%:*}" ); done compadd -d raw -- $trimmed } ```
在Bash中显示补全描述
虽然Bash不支持补全描述,但可以通过动态生成补全选项时附加描述信息,利用Bash只插入所有补全选项的共同前缀的特性,实现类似效果。
bash
_complete_foo_bash() {
local IFS=$'\n'
local raw=($(_generate_foo_completions "$COMP_CWORD" "${COMP_WORDS[@]}"))
local trimmed=()
if (( ${#raw[@]} == 1 )); then
trimmed=( "${raw[0]%%:*}" )
else
trimmed=( "${raw[@]}" )
fi
COMPREPLY=( "${trimmed[@]}" )
}
显示单个补全描述
为了让用户在补全完整单词时也能看到描述信息,可以在只有一个补全选项时添加一个“虚拟”补全选项,使补全变为多选项,从而触发描述显示。
bash
_complete_foo_bash() {
local IFS=$'\n'
local raw=($(_generate_foo_completions "$COMP_CWORD" "${COMP_WORDS[@]}"))
local trimmed=()
trimmed+=( "${raw[@]}" )
if (( ${#raw[@]} == 1 )); then
trimmed+=( "${raw[0]%%:*}" )
fi
COMPREPLY=( "${trimmed[@]}" )
}
总结
通过上述方法,可以为Bash和Zsh编写兼容的Tab补全功能,并实现补全描述显示等增强功能。最终的代码示例如下:
```bash generatefoocompletions() { local idx=$1; shift local words=( "$@" ) local currentword=${words[idx]}
local array=( "apple: a common fruit" "apricot: sour fruit with a large stone" "banana: starchy and high in potassium" "cherry: small and sweet with a large pit" "durian: stinky spiky fruit" ) for elem in "${array[@]}"; do if [[ $elem == "$current_word"* ]]; then echo "$elem"; fi done }
completefoobash() { local IFS=$'\n' local raw=($(generatefoocompletions "$COMPCWORD" "${COMPWORDS[@]}")) local trimmed=() trimmed+=( "${raw[@]}" ) if (( ${#raw[@]} == 1 )); then trimmed+=( "${raw[0]%%:*}" ) fi COMPREPLY=( "${trimmed[@]}" ) }
completefoozsh() { local -a raw trimmed local IFS=$'\n' raw=($(generatefoocompletions "$CURRENT" "${words[@]}")) for d in $raw; do trimmed+=( "${d%%:*}" ); done if (( ${#raw} == 1 )); then trimmed+=( "${raw[1]}" ) raw+=( "${trimmed[1]}" ) fi compadd -d raw -- $trimmed }
if [ -n "${ZSHVERSION:-}" ]; then autoload -Uz compinit compinit compdef _completefoozsh foo elif [ -n "${BASHVERSION:-}" ]; then complete -F completefoo_bash foo fi ```
通过这种方式,用户可以在Bash和Zsh中获得一致的Tab补全体验,包括显示补全描述、部分或完整单词补全,以及在补全完整单词时查看描述信息。
评论总结
评论内容总结:
对Bash脚本的替代方案讨论:
- 有人认为可以通过标准标志或库(如
argparse)来避免编写Bash脚本。- 引用:"Isn't there a standard flag which programs can implement to avoid writing this bash script?" (oezi)
- 有人建议构建一个DSL和转译器来简化这一过程。
- 引用:"Why doesn't someone (not me) just build a basic DSL and a transpiler that does this?" (camdroidw)
- 有人认为可以通过标准标志或库(如
Shell语法与自动补全的复杂性:
- 有人认为Shell语法的复杂性是LLMs(大语言模型)出现的原因之一。
- 引用:"Shell syntax is the exact reason why we've needed LLMs in the first place." (wiseowise)
- 有人对Bash自动补全的“智能”行为表示不满,认为它不应阻止文件名补全。
- 引用:"I feel that the ergonomics of bash completion took a hit as the configurations got 'smarter'..." (derriz)
- 有人认为Shell语法的复杂性是LLMs(大语言模型)出现的原因之一。
不同Shell的自动补全实现:
- 有人提到
fish通过解析man页自动生成补全文件,格式简单。- 引用:"With fish, if the program you're interested in hasn't betrayed the decades-old tradition of shipping man pages..." (homebrewer)
- 有人分享了
zsh中自定义补全的简单实现。- 引用:"Here's zsh snippet I've came up with for my own simple functions." (vbezhenar)
- 有人提到
其他Shell的补全功能:
- 有人提到
ksh中通过定义数组实现基本补全。- 引用:"Basic completion in ksh is as easy as defining an array." (sebtron)
- 有人希望
tcsh能获得更多关注。- 引用:"I wish tcsh would get more love." (xenophonf)
- 有人提到
自动补全的扩展功能:
- 有人提到在Bash/Zsh中实现JSON字段的自动补全。
- 引用:"JSON fields autocomplete right in bash/zsh." (medv)
- 有人提到在Bash/Zsh中实现JSON字段的自动补全。
总结:评论主要围绕Bash脚本的替代方案、Shell语法复杂性、不同Shell的自动补全实现以及其他Shell的补全功能展开讨论。观点多样,既有对现有工具的批评,也有对简化流程的建议。