complete -o filename can't be used with filenames relative to another directory to handle space vs. trailing /
Affects | Status | Importance | Assigned to | Milestone | |
---|---|---|---|---|---|
bash-completion (Ubuntu) |
Opinion
|
Undecided
|
Unassigned |
Bug Description
I am trying to ignore certain directory names when using bash completion. For example to ignore "backup" directories I use
$ cat setup.sh
_compTest() {
local curdir cur words val name
cur=
baseFolder="."
curdir=$PWD
cd "$baseFolder"
words=( $(compgen -f "$cur") )
COMPREPLY=()
for val in "${words[@]}" ; do
if [[ $name == "backup" ]] ; then
fi
done
cd "$curdir"
}
complete -o filenames -F _compTest aaa
After sourcing this:
$ . setup.sh
I can type
$ aaa <tab><tab>
and it works fine. However if I use another baseFolder, for example setting
baseFolder=
in the above script (setup.sh) (where $HOME/base is different from the current directory) the completion list is no longer what I would expect. That is:
* slashes are missing at the end of directory names, and
* a space is added after each completed directory name, instead of a slash ("/")
$ lsb_release -rd
Description: Ubuntu 14.04.1 LTS
Release: 14.04
$ apt-cache policy bash-completion
bash-completion:
Installed: 1:2.1-4
Candidate: 1:2.1-4
Version table:
*** 1:2.1-4 0
500 http://
100 /var/lib/
This isn't bash-completion's bug. If anything, it's bash's bug, if you're sure that COMPREPLY is being mishandled.
More likely, it's a bug in your own script, since the usual programmable completion code jumps through some hoops do to the Right Thing for files and directories. Your script has huge problems with spaces in filenames, because they get quoted in compgen's output, and never unquoted before you pass them to COMPREPLY.
Incidentally, it would run WAY faster if you used a glob match instead of forking basename. See the hints in /usr/share/ doc/bash- completion/ README. gz
Also, pushd / popd would be easier than saving CWD. And safer still do do the whole thing inside a ( subshell ) to never affect the directory context of the user's shell. Even pushd/popd modify the user's $OLDPWD.
So you're trying to complete the current wod as a filename in a fixed directory, regardless of the current PWD, right?
The problem might be that you're using complete -o filenames. The results of your completion WON'T be filenames when your function returns, unless they're absolute paths. The space at the end instead of / is because you didn't use -o nospace, and bash didn't identify the word as a filename, because in the context of the shell when your function returns, stat("filename") gets ENOENT. bash only looks at COMPREPLY once you're done building it, not with the directory context you had while you were building it.
If you backspace and put in a /, it will complete files in subdirectories of wherever you were.
Anyway, as far as I can tell, everything is working as documented, your script is just using it wrong. :(
BTW, you can get the idental result to your script with MUCH less code and overhead from "${COMP_ WORDS[$ COMP_CWORD] }" ~/"tmp"
_compTest() {
local curdir cur words val name
cur=
baseFolder=
COMPREPLY=( $(cd "$baseFolder"; compgen -X '?(*/)backup' -f "$cur") )
}
complete -o filenames -F _compTest aaa
$( command substitution ) is already a subshell, so you can cd in there without affecting $OLWPWD or anything else in the user's shell.
Of course, this still doesn't handle filenames with anything in their name, even spaces.
To do that, borrow code from bash-completion's _filedir function:
_compTest() { COMP_WORDS[ $COMP_CWORD] }"
local IFS=$'\n'
local cur="${
local quoted x
baseFolder= ~/"tmp" readline_ by_ref "$cur" quoted
_quote_
# COMPREPLY=( $(cd "$baseFolder"; compgen -X '?(*/)backup' -f "$cur") )
# IDK why _filedir does directories first.
COMPREPLY+ =( "$tmp" )
x=$( cd "$baseFolder"; compgen -d -X '?(*/)backup' -- "$quoted" ) &&
while read -r tmp; do
done <<< "$x"
x=$( cd "$baseFolder"; compgen -f -X '?(*/)backup' -- "$quoted" ) &&
COMPREPLY+ =( "$tmp" )
while read -r tmp; do
done <<< "$x"
}
complete -o filenames -F _compTest aaa
Still doesn't know to put / after directory names, but does handle any filenames except those containing a newline. If you want the / for filenames thing, you'd have to write it yourself without depending on complete -o filenames to do it for you. I don't think that's possible for words that don't exist in the filesys...