Hacker News 中文摘要

RSS订阅

为Bash和Zsh编写简单的Tab补全功能 -- Writing simple tab-completions for Bash and Zsh

文章摘要

文章介绍了如何为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补全体验,包括显示补全描述、部分或完整单词补全,以及在补全完整单词时查看描述信息。

评论总结

评论内容总结:

  1. 对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)
  2. 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)
  3. 不同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)
  4. 其他Shell的补全功能

    • 有人提到ksh中通过定义数组实现基本补全。
      • 引用:"Basic completion in ksh is as easy as defining an array." (sebtron)
    • 有人希望tcsh能获得更多关注。
      • 引用:"I wish tcsh would get more love." (xenophonf)
  5. 自动补全的扩展功能

    • 有人提到在Bash/Zsh中实现JSON字段的自动补全。
      • 引用:"JSON fields autocomplete right in bash/zsh." (medv)

总结:评论主要围绕Bash脚本的替代方案、Shell语法复杂性、不同Shell的自动补全实现以及其他Shell的补全功能展开讨论。观点多样,既有对现有工具的批评,也有对简化流程的建议。