16

The following bash completion passes an array of possible words (i.e. completions) to compgen.

basenames=("foo" "fu bar" "baz");

COMPREPLY=($(compgen -W "${basenames[*]}" -- "${COMP_WORDS[COMP_CWORD]}"))

The problem is that the whitespace in the array elements is not preserved, that is "foo bar" is treated as to elements thanks to word splitting. Is there some way to preserve the whitespace, so that 3 elements are shown, and not 4?

EDIT

basenames contains filenames, that is nearly every character (apart from / and \0) is allowed.

EDIT 2

The -W flag expects a single word, that is something like foo bar foobar. Passing multiple elements to it (this is what ${basenames[@]} would do) won't work.

EDIT 3

Changed examplary basenames array (so the foo and the foo from foo bar won't get collapsed).

Using a newline to separate the words works:

local IFS=$'\n'
COMPREPLY=($(compgen -W "$(printf "%s\n" "${basenames[@]}")" --  ${COMP_WORDS[COMP_CWORD]}"))

Using \0 doesn't:

local IFS=$'\0'
COMPREPLY=($(compgen -W "$(printf "%s\0" "${basenames[@]}")" --  ${COMP_WORDS[COMP_CWORD]}"))
mtk
  • 13,221
  • 16
  • 72
  • 112
helpermethod
  • 59,493
  • 71
  • 188
  • 276

4 Answers4

11

Why bother with compgen? Just add them to COMPREPLY manually. The following will complete the matching filenames from /some/path, handling filenames safely.

some_completion_function() {
    local files=("/some/path/$2"*)
    [[ -e ${files[0]} ]] && COMPREPLY=( "${files[@]##*/}" )
}

It's not possible to have compgen handle filenames safely.

geirha
  • 5,801
  • 1
  • 30
  • 35
  • +1 This is how I actually achieved what I wanted. Really good advice! – helpermethod Jul 14 '12 at 14:42
  • what are "$2" and "##*/" ? – oluc Mar 13 '14 at 13:01
  • ##*/ removes the head until the last '/'; $2 is the current completion word; right? – oluc Mar 13 '14 at 13:37
  • 1
    @oluc, that's right. And `"$1"` is the command being completed and `"$3"` is the previous word. – geirha Mar 13 '14 at 20:35
  • Can you explain `${files[@]##*/}` for those who aren't really familiar with bash arrays? Thanks. – Jonathon Reinhart Jul 29 '16 at 02:26
  • @JonathonReinhart It expands all elements of the array, removing the longest matching substring of the pattern (`*/`) from the start of each element. It's explained here: [Parameter Expansion](https://www.gnu.org/software/bash/manual/bashref.html#Shell-Parameter-Expansion). See also [BashFAQ 100](http://mywiki.wooledge.org/BashFAQ/100). – geirha Jul 29 '16 at 10:53
1

Perhaps this might do:

COMPREPLY=($(compgen -W "$(printf "%q " "${basenames[*]}")" -- "${COMP_WORDS[COMP_CWORD]}"))
glenn jackman
  • 238,783
  • 38
  • 220
  • 352
0

Read better solution there

Use readarray and printf

readarray -t words < /etc/passwd
compgen -W  "$(printf "'%s' " "${words[@]}")" -- "man"
# OUTPUTS: man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
  • readarray: Avoid dealing with IFS
  • printf: Escape: or @Q parameter transformation after bash 4.4 (20170 man bash search parameter@operator
Tinmarino
  • 3,693
  • 24
  • 33
-3

You should almost always use an at sign instead of an asterisk to subscript an array.

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

from man bash (or see the Bash Reference Manual):

If subscript is @ or *, the word expands to all members of name. These subscripts differ only when the word appears within double quotes. If the word is double-quoted, ${name[*]} expands to a single word with the value of each array member separated by the first character of the IFS special variable, and ${name[@]} expands each element of name to a separate word.

In other words, the at sign form "flattens" the array. The asterisk form preserves it.

Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • 4
    If I replace the * with an @, auto completion stops working completly. – helpermethod May 18 '12 at 17:52
  • 8
    `compgen -W` requires that all completions be provided in a single "word", which `[@]` doesn't do. See [my answer to a previous question](http://stackoverflow.com/questions/3348443/a-confusion-about-array-versus-array-in-the-context-of-a-bash-comple/3355375#3355375) for a detailed explanation. – Gordon Davisson May 23 '12 at 05:08
  • 12
    -1: This advice is so out of context that it's just plain incorrect. `-W` expects a *single argument* and using `@` will give you an undefined number of them. – antak Nov 24 '12 at 08:49
  • As the last few comments said this answer is **wrong**. – pynexj Jul 20 '15 at 14:45