Here is how you can run a completion function or executable for a command and collect its results.
While these are possible ways to achieve your goal, I have found that looking at the code of the completion function seeing how it does what it does has been more successful for me so at the end I give a few examples demonstrating what I mean.
When the completions are generated by a shell function
When completions are shell functions, complete -p <your-command> outputs complete -F <shell-function> <your-command>, that means that when the shell you press TAB while the first word on the command line is your command, bash will
- Set some variables (look for
COMP_WORDS in man bash)
- Call this function
- Look in the variable
COMPREPLY expecting it to be a shell array of possible completions.
The following is an example for make. It assumes you have a Makefile in the current directory with some targets beginning with x and this will list all the targets that begin with 'x'.
#!/bin/bash
source /usr/share/bash-completion/bash_completion
source /usr/share/bash-completion/completions/make
COMP_WORDS=(make x)
COMP_CWORD=1 # The index of the word containing the cursor (the C is for cursor, not count)
COMP_LINE="make x"
COMP_POINT=${#COMP_LINE}
COMP_TYPE=9
COMP_KEY=$'\t'
COMPREPLY=()
_make
for comp in "${COMPREPLY[@]}" ; do
echo "$comp is one of the completion candidates"
done
The thing is many completion functions might call compopt to change completion options during their execution which is an obstacle to calling them outside of bash completion.
When the completions are generated by a command
If complete -p <your-command> outputs complete -C <a-command> <your-command>, then that means that bash will run an external command.
- First arg is the command
- Second arg is the word being completed
- Third arg is the word before the one being completed
- The environment variables
COMP_LINE, COMP_KEY, COMP_POINT, COMP_TYPE are also set (but not COMP_WORDS and COMP_CWORD).
Note that when completions come from a shell function, the function also gets the same three arguments but I've never seen a completion function that looks at them since completion functions can look at the COMP_WORDS array.
For this example, suppose our command is salad and its completions are generated by _salad, an executable (not a shell function).
#!/bin/bash
# assume command to be completed is salad
# and completions are generated by the executable
# ./_salad
COMP_WORDS=(salad -f ap)
COMP_CWORD=2 # The index of the word containing the cursor (the C is for cursor, not count)
COMP_LINE="salad -f ap"
COMP_POINT=${#COMP_LINE}
COMP_TYPE=9
COMP_KEY=$'\t'
oifs="$IFS"
IFS=$'\n'
candidates=($(./_salad salad ap -f))
IFS="$oifs"
for comp in "${candidates[@]}" ; do
echo "$comp is one of the completion candidates"
done
Since this is an external command, it can't tell the difference between being called by us or by bash during a real completion. However, I don't know of any command that is completed this way.
What I usually do
The first thing I would recommend is to try to understand the completion function itself and possibly steal parts of it to suit your needs.
Example getting targets from a makefile
For example, for Makefiles, looking at the file _make that comes with bash_completion, I came up with
get_makefile_targets ()
{
dir=$1
prefix=$2
#
# Completion for `make` is normally lazy loaded the first time you invoke
# completion for `make in a given shell. This provides the function _make
# and _make_target_extract_script used below
#
if [[ "$(type -t _make)" != function ]] ; then
source /usr/share/bash-completion/bash_completion
source /usr/share/bash-completion/completions/make
fi
(
cd $dir
make -npq __BASH_MAKE_COMPLETION__=1 | sed -nf <(_make_target_extract_script -- "$2")
)
}
Example scp: getting remote files
Another example, scp it completes filenames on the remote host when you do scp host:<TAB> so if that interests you, rather than triggering the completion and looking at the candidates, it may be easier to just look at how it does it.
We find that the function is _scp and looking at that function we see that it does a bunch of stuff but at some point, it has
case $cur in
...
*:*) _scp_remote_files ; return
esac
so looking at _scp_remote_files(), we see that it does some escaping stuff
and then does
path=${cur#*:}
host=${cur%%?(\\):*}
# escaping stuff
files=$(a big command)
COMPREPLY=($files)
so we could adapt this to our needs by doing
get_remote_files(){
host=$1
prefix=$2
prefix=$(escaping stuff)
a big command
}
Easier examples
A lot of times, the programs have functions that help with completion like tmux which has tmux list-commands if we want to get a list of subcommands.
Git also has this. Looking at git-completion.bash from the contrib directory, we see that it does git --list-cmds=list-main,porcelain,others,alias,... to make git itself give a list of possible commands.
We can also see that git commit --git-completion-helper produces the list of options for commit and the same thing works for other git subcommands.
Other tips
- Completions are normally lazy loaded so you may need to trigger it once before
complete -p will tell you anything. For example: complete -p make in a brand new shell will usually say that there is nothing, but if you do make <TAB> and then run complete -p make, you will get complete -F _make make. This is because for commands that have no completion, bash invokes _load_completion <cmd> which will look in various directories for a file named _<cmd> (and some variations) and source the first one it finds, then reattempt completion.
- To find the where the completion function set
shopt -s extdebug which causes declare -F <shell-function> to tell you the file where the shell function is defined. As described in point 1, you may need to trigger the completion once for the file containing that function to be sourced.
- A lot of these completion functions are loaded only in interactive shells so if you want to use them, you may need to explicitly source some files like
bash_completion and _make for the first example.
ls a*will give you a list of all files starting with 'a'.)mybuildinbash, so that tab-completion will only suggest files that exist in a directory specified by some environment variable. I would suggest reading up on programmable completion in thebashman page (it's not the clearest documentation, but give it a try), and ask again after you've made a first try at coding it.buildcommand doesn't work with files. It extracts build targets from some make file. I will define my own completion, but it needs to usebuild's completion to know what targets to present me with.