Search code examples
bashbash-completion

Bash command completion with custom function, regex on current completed word not working?


I'm writing a custom completion function for a bash script (this script is for activating deactivating nginx configuration files). I'm trying to have a different behavior if the completed word starts with --, is empty, or starts with something else. Typically, if I write command --<TAB>, I should have --help --someoptions suggested, whereas if I type command <TAB> then I should have the content of a specific directory, matching a specific regex, shown as completion suggestions.

Here is the code of my completion function:

#!/bin/bash

source "./common"

_comp_nginx_conf(){
    local IFS=$' '
    local cur long_help conf_files new_help
    cur=
    long_help=( "help" "test" "testmode" "deactivate" "activate" )
    
    # for correct completion, check if test mode is in the options
    for arg in "${COMP_WORDS[@]}"; do
    if [[ "$arg" == "--test" ||  "$arg" == "-t" ]]; then
        TEST_MODE=true
    fi
    done
    
    [[ $TEST_MODE ]] && cd "$TEST_PATH" &> /dev/null || cd "$PROD_PATH" &> /dev/null
    
    conf_files=$(ls -b *(.conf|.conf.incative) 2> /dev/null)
    new_help=()
    COMPREPLY=()
    cur="$2"
    
    if [[ $cur =~ "--".* ]]; then
    
    COMPREPLY=( $(compgen -W "${long_help[*]}" -P "--" -- $cur) )
    
    else
    
    COMPREPLY=( $(compgen -W "${conf_files[*]}" -- $cur) )
    fi
    
    return 0
} &&
    complete -F _comp_nginx_conf nginx-conf

The issue here is, when I type command --<TAB> it should offer --help --testmode --deactivate --activate and when I type command <TAB> it should offer test.conf test2.conf.inactive and all the conf files in $TEST_PATH, but instead it still offers --someoptions. I don't understand why I get this result, so this is where I need help, how to achieve the behavior I want and why don't I get it with the code I wrote?


Solution

  • I think I find your function too complicated. *(...) is an extended glob. For the -P -- you have to ${cur%--} remove the dashes, otherwise they won't match. And I do not get the "TEST_MODE" - why would you want confuse users by changing directory in completion function! I use set -x for testing. Also function definition always succeeds, why && of it.

    _comp_nginx_conf() {
        local cur
        cur=$2
        if [[ $cur == -* ]]; then
            COMPREPLY=($(compgen -W "--help --test --testmode --deactivate --activate" -- $cur))
        else
            COMPREPLY=($(
                compgen -G '*.conf' -- $cur
                compgen -G '*.conf.incative' -- $cur
            ))
        fi
    }
    complete -F _comp_nginx_conf nginx-conf
    

    Overall, why care at all, compgen will just return nothing:

    COMPREPLY=($(
         compgen -W "--help --test --testmode --deactivate --activate" -- $cur
         compgen -G '*.conf' -- $cur
         compgen -G '*.conf.incative' -- $cur
    ))
    

    If you want to complete files from some directory, change the directory in the subshell. I would be very confused if my PWD would change when executing completion.

    local dir IFS=" "
    case " ${COMP_WORDS[*]} " in
    *" --test "*|*" -t "*) dir=$TEST_PATH; ;;
    *) dir=$PROD_PATH; ;;
    esac
    COMPREPLY=($(
         cd "$dir" 2>/dev/null
         compgen -G '*.conf' -- $cur
         compgen -G '*.conf.incative' -- $cur
    ))
    

    Check your scripts with shellcheck. Prefer if over && ||.