1

I am experimenting with bash completion to write a general completion library in C associated with command line parsing and stumbled in a behavior for which I can find no documentation.

I am using the following scrip that I call compscript to make tests:

#!/bin/bash                                                                     

>&2 echo
>&2 echo $3
>&2 echo $2

echo testing

This script is used as an external completion command with complete -C and all it does is print to stderr its third argument ($3) which is the string before the string being completed and its second argument ($2) which is the string being completed, and then complete anything to the word "testing" (complete -C takes completion strings from the stdout of the given command).

I then associate compscript as a completion command for a dummy, non-existent program with

complete -C ./compscript dummy

Now, if I type

dummy abc tes<tab>

compscript will print "abc" and "tes" to stderr and complete "tes" as testing, as expected (to see the completion, it is better to refresh the command line with Control-L).

However, if I type

dummy abc "tes<tab>

then compscript will print abc, as expected, and tes without the opening quotation mark, complete it as testing and bash (not compscript) will automatically close the quotation mark, effectively completing "tes as "testing".

My question is where do I find the documentation and specification for this and possibly many other corner case behaviors of bash completion? This subject seems very badly documented.

EDIT

I found yet another bash completion idiosyncrasy. If the following completion is being performed:

dummy abc def=ghi<tab>

Then, if '=' is not in COMP_WORDBREAKS, then $2='def=ghi' and $3='abc', and if '=' is in COMP_WORDBREAKS, then $2='ghi' $3='='.

However, it makes no difference whether '"' is or is not in COMP_WORDBREAKS. In both cases:

dummy abc def"ghi<tab> -> $2='ghi' and $3='abc'

dummy abc def="ghi<tab> -> $2='ghi' and $3='abc' ('=' not in COMP_WORDBREAKS)

dummy abc def="ghi<tab> -> $2='ghi' and $3='=' ('=' in COMP_WORDBREAKS)

Also, in case of closed quotes, it makes no difference whether '"' is or is not in COMP_WORDBREAKS:

dummy abc def"ghi"<tab> -> $2='def"ghi"' and $3='abc'

dummy abc def="ghi"<tab> -> $2='def"ghi"' and $3='abc' ('=' not in COMP_WORDBREAKS)

dummy abc def="ghi"<tab> -> $2='"ghi"' and $3='=' ('=' in COMP_WORDBREAKS)

Everything is the same for single quotes instead of double quotes.

It is puzzling that both single and double quotes are usually present in COMP_WORDBREAKS, as it makes no difference if they are there or not. It is also hard to understand why COMP_WORDBREAKS even exists, as modifying it can break some or even all completion scripts. It would be much more sensible if word break characters were simply the bash metacharacters.

1
  • The entire programmable completion process, how to program it, complete with an example, is described in the Bash Manual. Commented Jul 10 at 15:56

1 Answer 1

0

(Update 2024-07-12:)

Using COMP_WORDS/COMP_CWORD and -W

This answer is based on the question which presents the opposite problem: how to complete a word beginning with a quote as if it didn't begin with a quote, and strip quotes:

# Contents of my_completion.bash

_my_completion()
{
    local IFS=$'\n'
    COMPREPLY=( $(compgen -W "$({ cat <<'end_comps'
special unquoted argument
alternative
end_comps
    } | grep -- "^${COMP_WORDS[COMP_CWORD]}")" -- \
        "${COMP_WORDS[COMP_CWORD]}") )
}

complete -F _my_completion my_func

This code uses the -W wordlist option (there is no direct link to the description of that option, the linked part of the GNU Bash Manual needs to be scrolled down a bit) to split the line into words using IFS instead of COMP_WORDBREAKS. By setting IFS to newline only, double quotes will be considered parts of words for the sake of populating COMP_WORDS. IFS is set locally so as not to pollute the environment outside of that function if the script is sourced.

Using this solution, after sourcing the above script

. my_completion.bash

and pressing Tab after typing, eg.:

my_func asdf"spe

or

my_func "spe

nothing will happen, while pressing Tab after

my_func spe

the command will be expanded into

my_func special unquoted argument

(Old answer follows:)

Using COMP_LINE

As you have noticed, Bash automatically closes the quotes for the completions, but only when the completion function was given the chance to offer a completion. In the section 8.6 Programmable Completion of the Bash Manual, and the section 5.2 Bash Variables, it is described that a number of environment variables is available when inside of a completion function, that describe the current state of Bash when the completion was triggered. Among them there is the COMP_LINE, which holds the entire line up to the point where Tab was pressed. You can then analyze that variable to prevent the completion. For example:

# Contents of file my_completion.bash

_my_completion()
{
        if [ -n "$(printf "\"special" |
                grep "^$(printf "%s" "$COMP_LINE" |
                        awk '{print $NF}')" )" ]; then
                :
        elif [[ special =~ ^$2 ]]; then
                COMPREPLY[0]="special unquoted expansion"
        elif [[ alternative =~ "$2" ]]; then
                COMPREPLY[0]=alternative
        fi
        return 0
}

complete -F _my_completion my_func

after executing:

. my_completion.bash

typing

my_func "spe

followed by pressing Tab, nothing will happen, while if we write

my_func spe

and press Tab, the line will transform into

my_func special unquoted expansion

Note that if the testing for unquoted string special was done like this:

        elif [[ special =~ "$2" ]]; then
                COMPREPLY[0]="special unquoted expansion"

(quoted, without ^), then typing

my_func "i

and pressing Tab would still expand to "special unquoted expansion", since the string special would be matched for $2, which is i without the quote. The manual describes this:

the second argument ($2) is the word being completed

emphasis on word.

3
  • Thank you. It is very important to know if the word being completed is preceded by a quotation mark and which one of the two possible quotation marks, as this will determine which characters will need escaping in the word. This can only be done by inspecting COMP_LINE. I find still almost impossible to apprehend all these questions from bash documentation. For example, abc"defghi" is a single word. However, if abc"def is presented to bash completion, $2 will be only def. If one wants to complete abc"def to abc"defghi", a very complicated analysis of COMP_LINE must be performed. Commented Jul 12 at 13:51
  • $2 holding only def in abc"def is not confusing when you consider the variable COMP_WORDBREAKS and inspect it. In Bash 5.2.26, executing printf "%s" "$COMP_WORDBREAKS" | od -t a gives 0000000 sp ht nl " ' @ > < = ; | & ( :, where the first three characters are space (sp), tab (ht) and newline (nl). Commented Jul 12 at 17:12
  • I updated the answer by adding a solution based on a related (but in a way reverse from this one) question, using -W option and COMP_WORDS/COMP_CWORD. Commented Jul 12 at 17:44

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .