【问题标题】:Setting case-insensitivity for bash completion on a command-by-command basis在逐个命令的基础上为 bash 完成设置不区分大小写
【发布时间】:2012-05-09 23:40:41
【问题描述】:

有没有办法指定特定命令不区分大小写,而无需全局启用不区分大小写(至少对于该 shell)?

在我的特殊情况下,我有一个小应用程序,可以让我通过命令行访问电子邮件地址数据库,所以我输入:

db get email john smith

它会返回 John Smith 的电子邮件地址。所以我设法在应用程序内部启用了完成功能:设置

COMPREPLY=($(compgen -W "$(db --complete $COMP_CWORD "$COMP_WORDS[@]"}")" -- ${COMP_WORDS[COMP_CWORD]}))

允许我使用制表符完成getemail。但是,如果我随后键入j<tab>,它会拒绝,因为在电子邮件数据库中,它已正确大写。无论如何,我想让 bash 完成这项工作。 (如果我使用大写字母J,它可以工作。)

如果不这样做,我想我可以让我的--complete 选项通过匹配输入来更改其回复的大小写,但理想情况下,命令行会尽可能匹配数据库。

请注意,当我使用 readline 时,我在应用程序中进行了这项工作,它只是与 bash 交互,这似乎是一个问题。

【问题讨论】:

    标签: bash case-insensitive tab-completion


    【解决方案1】:

    确实似乎没有办法让compgen 对单词列表(-W)进行不区分大小写的匹配。 我看到以下解决方法:

    简单的解决方案:首先将单词列表和输入标记都翻译成全小写。 注意:这是一个选项,如果所有补全都变成全小写是可以接受的。

    complete_lower() {
    
        local token=${COMP_WORDS[$COMP_CWORD]}
        local words=$( db --complete $COMP_CWORD "${COMP_WORDS[@]}" )
    
        # Translate both the word list and the token to all-lowercase.
        local wordsLower=$( printf %s "$words" | tr [:upper:] [:lower:] )
        local tokenLower=$( printf %s "$token" | tr [:upper:] [:lower:] )
    
        COMPREPLY=($(compgen -W "$wordsLower" -- "$tokenLower"))   
    }
    

    更好但更精细的解决方案:滚动您自己的、不区分大小写的匹配逻辑:

    complete_custommatch() {
    
        local token=${COMP_WORDS[$COMP_CWORD]}
        local words=$( db --complete $COMP_CWORD "${COMP_WORDS[@]}" )
    
        # Turn case-insensitive matching temporarily on, if necessary.
        local nocasematchWasOff=0
        shopt nocasematch >/dev/null || nocasematchWasOff=1
        (( nocasematchWasOff )) && shopt -s nocasematch
    
        # Loop over words in list and search for case-insensitive prefix match.
        local w matches=()
        for w in $words; do
            if [[ "$w" == "$token"* ]]; then matches+=("$w"); fi
        done
    
        # Restore state of 'nocasematch' option, if necessary.
        (( nocasematchWasOff )) && shopt -u nocasematch
    
        COMPREPLY=("${matches[@]}")
    }
    

    【讨论】:

    • 上述解决方案似乎并不完全正确,因为它只返回第一个匹配项而不是所有匹配项。删除“休息”应该可以解决这个问题。另一个问题是,这在根本没有 nocasematch 选项的旧 bash 版本上不能正常工作。我不知道什么是最好的解决方案,但我在脚本中使用了 local SHOPT=$( shopt -p ) local IGNORE_CASE=0 [[ "${SHOPT#*shopt -u nocasematch}" == " $SHOPT" ]] || IGNORE_CASE=1
    • 谢谢,@Neuron:我已更新代码以返回所有匹配项。至于 nocasematch 选项不可用(您知道它是在哪个版本中引入的吗?):可以使用您的测试,然后在没有选项的情况下应用 `tr [:upper:] [:lower :]` 技术在for 循环的主体中。执行选项可用性测试的更简洁的方法是:local haveNoCaseMatch=0 \n [[ -z "$(shopt -q nocasematch 2>&1)" ]] && haveNoCaseMatch=1
    • 真的。引用是为了抨击安全套对性的意义......当你知道你可以不使用它们就可以逃脱时,这会更令人兴奋......当你错了,你并不总是马上知道它......
    • 你可以使用一点 bash 魔法:“${w,,}”和“${token,,}”,而不是弄乱shopt。 :)
    • @AlexeyVoinov:谢谢;是的,如果您使用 bash 4+(例如,OSX 仍在 bash 3.x 上),这是一个选项。
    【解决方案2】:

    使用grep 完成所有工作要容易得多;那么案例会保留在完成中,您不必弄乱shopt 或类似的东西。例如:

    _example_completions () {
        local choices="john JAMES Jerry Erik eMIly alex Don donald donny@example.com RON"
        COMPREPLY=( $( echo "$choices" | tr " " "\n" | grep -i "^$2" ) )
    }
    

    这里,$choices 是您的单词列表,tr 用于将单词之间的空格更改为换行符,以便 grep 可以理解它们(如果您的单词已经是换行符,则可以省略), -i 选项是不区分大小写的匹配,"^$2" 匹配行首的当前单词(bash 将其传递为 $2)。

    $ example dO<tab>
    Don     donald     donny@example.com
    

    【讨论】:

      猜你喜欢
      • 2014-09-25
      • 2018-11-04
      • 1970-01-01
      • 2018-01-25
      • 2012-06-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多