Tanktalus
Tanktalus

Reputation: 22254

Setting case-insensitivity for bash completion on a command-by-command basis

Is there a way to specify that a particular command has case insensitivity, without turning on case insensitivity globally (at least for that shell)?

In my particular case, I have a small app that gives me command line access to a database of email addresses, so I type:

db get email john smith

and it returns back with John Smith's email address. So I've managed to enable completion largely inside the app: setting

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

works to allow me to tab-complete get and email. However, if I then type j<tab>, it refuses, because in the email database, it's properly capitalised. I'd like to get bash to complete this anyway. (If I use a capital J, it works.)

Failing that, I can have my --complete option change the case of its reply by matching the input, I suppose, but ideally the command line would match the database if at all possible.

Note that I have this working inside the app when using readline, it's only interfacing with bash that seems to be an issue.

Upvotes: 8

Views: 1739

Answers (2)

Jason C
Jason C

Reputation: 40335

It's a lot easier to just use grep to do all the work; then the case is preserved in the completions, and you don't have to mess with shopt or anything like that. For example:

_example_completions () {
    local choices="john JAMES Jerry Erik eMIly alex Don donald [email protected] RON"
    COMPREPLY=( $( echo "$choices" | tr " " "\n" | grep -i "^$2" ) )
}

Here, $choices is your list of words, tr is used to change the spaces between words to newlines so grep can understand them (you could omit that if your words are already newline-delimited), the -i option is case insensitive matching, and "^$2" matches the current word (bash passes it as $2) at the beginning of a line.

$ example dO<tab>
Don     donald     [email protected]

Upvotes: 2

mklement0
mklement0

Reputation: 437833

Indeed there seems to be no way to have compgen do case-insensitive matching against the word list (-W). I see the following workarounds:

Simple solution: Translate both the word list and the input token to all-lowercase first. Note: This is only an option if it's acceptable to have all completions turn into all-lowercase.

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"))   
}

Better, but more elaborate solution: Roll your own, case-insensitive matching logic:

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[@]}")
}

Upvotes: 6

Related Questions