I'd like to get the following behavior from my custom completion
Given
$ mkdir foo
$ touch foo faz/bar faz/baz
I'd like to get this
$ foo -u <tab><tab> =>
foo faz/
$ foo -u fa<tab><tab> =>
foo -u faz/
$ foo -u faz/<tab><tab> =>
bar baz
I assumed that compgen -f f
would output foo faz/
, but it outputs foo faz
which doesn't help me much.
Do I need to post-process the output or is there some magic combination of options to compgen that work?
I ran into the same problem. Here's the workaround I'm using:
-o default
, e.g.,complete -o default -F _my_completion
.COMPREPLY=()
and let Readline take over (that's what the-o default
does).There's a potential problem with this -- you might be using
COMPREPLY=()
to deny completion where it's not appropriate, and now that won't work anymore. In Bash 4.0 and above, you can work around this usingcompopt
as follows:compopt +o default
. This disables Readline filename completion whenCOMPREPLY
is empty.compopt -o default; COMPREPLY=()
. In other words, enable Readline filename completion only when you need it.I haven't figured out a full workaround for pre-4.0 Bash, but I have something that works well enough for me. I can describe this if anyone really cares, but hopefully these older Bash versions will soon fall out of common use anyway.
If you want to be able to continue using tab after the directory was completed; here's the complete solution:
first add a trailing slash to the answer. Then run the
compopt -o nospace
command to remove the trailing space. Now if you have onlyfoo/bar
in your directory, two tabs will suffice to type them!Previous answers require bash 4, or aren't composable with other compgen-based completions for the same command. The following solution doesn't require compopt (so it works with bash 3.2) and it is composable (for example you can easily add additional filtering to only match certain file extensions). Skip to the bottom if impatient.
You can test compgen by running it directly on the command line:
where
$cur
is the word you would've typed so far during interactive tab-completion.If I'm in a directory that has a file
afile.py
and a sub-directoryadir
, andcur=a
then the above command shows the following:Note that
compgen -f
shows files and directories.compgen -d
shows only directories:Adding
-S /
will add a trailing slash to every result:Now, we can try listing all files and all directories:
Note that there's nothing stopping you from calling compgen more than once! In your completion script you would use it like this:
Unfortunately, this still doesn't give us the behaviour that we want, because if you type
ad<TAB>
you have two possible completions:adir
andadir/
. Soad<TAB>
will complete toadir
, at which point you still have to type in the/
to disambiguate.What we need now is a function that will return all files but no directories. Here it is:
Let's break it down:
grep -f file1 file2
means show the lines in file2 that match any of the patterns in file1.-F
means the patterns in file1 must match exactly (as a substring); they aren't regular expressions.-v
means invert the match: Only show lines that aren't in file1.<(...)
is bash process substitution. It allows you to run any command in the places where a file is expected.So we're telling grep: here's a list of files -- remove any that match this list of directories.
I've added beginning and end markers with compgen's
-P ^
and-S '$'
because grep's-F
does substring matching and we don't want to remove a filename likea-file-with-adir-in-the-middle
just because its middle part matched a directory's name. Once we have the list of files we remove those markers with sed.Now we can write a function that does what we want:
You use it like this:
Note that
-o nospace
means you don't get a space after a directory's/
. For normal files we added a space at the end with sed.One nice thing about having this in a separate function is that it's easy to test! For example here's an automated test:
This behaviour is influenced by the readline variable
mark-directories
(and the relatedmark-symlinked-directories
). I believe the variable should be set on forcomplete
to print a trailing slash on directory names (the default in bash v3.00.16). Seemingly the related behaviour ofcompgen
doesn't append a slash to directory names :-\Set the value of
mark-directories
alternately toon
andoff
then retry your test:-To make the change permanent for future invocations of
bash
, add the following to your INPUTRC file, commonly~/.inputrc
:-The tip to set readline variables in the current shell was found here: https://unix.stackexchange.com/a/27545. I didn't determine how to test the current value of a readline variable.
Additional Ideas
Perhaps only academic interest...
Create a list of directory names only and append a slash:-
This worked for me: