1

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?

4
  • 1
    Aside: ${conf_files[*]} doesn't really make sense: conf_files is already a single string, not an array. (And separately, array=( $(...anything...) ) is bad practice; see BashPitfalls #50) Commented Nov 23, 2023 at 17:38
  • I'm just tinkering around the edges, though, because digging into the question would involve getting into completion logic, and I stay clear of it as a matter of course (I dislike things that happen behind my back more than I like convenience). If you can extract a narrower description of what's going on that focuses on the bash language rather than on completion, I'd be glad to take a closer look; think about using set -x to get a trace of execution to figure out exactly which operation first does something contrary to your expectations, to ask a narrower question about that one step. Commented Nov 23, 2023 at 17:40
  • It seems it must be something about if [[ $cur =~ "--".* ]]; then (that I don't see either!) ... wrap that block in set -x and set +x and confirm that variables are being expanded as you expect. Good first Q! Keep posting! Commented Nov 23, 2023 at 17:42
  • I doubt this is the problem, but regex matches don't have to match the entire string, just somewhere in the string, so [[ $cur =~ "--".* ]] will match "--" anywhere in the string, not just at the beginning. Instead, use [[ $cur =~ ^"--" ]] or a glob pattern like [[ $cur = "--"* ]] Commented Nov 23, 2023 at 17:52

1 Answer 1

1

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 && ||.

Sign up to request clarification or add additional context in comments.

1 Comment

You've found the solution, but you've made a mistake while giving me the answer, the solution was using ${cur#--} to remove the extra --

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.