More on Using the Bash Complete Command


In the video last week I showed how to use the bash complete command for simple use cases. Today I'll show you some of the additional ways that you can use the command for more complex scenarios.

To review the simple use case I demonstrated last week here's the essence of it, or watch the video:

$ # Create a dummy command:
$ touch ~/bin/myfoo
$ chmod +x ~/bin/myfoo

$ # Create some files:
$ touch

$ # Use the command and try auto-completion.
$ # Note that all files are displayed:
$ myfoo <TAB><TAB>

$ # Now tell bash that we only want foo files.
$ # This command tells bash args to myfoo are completed
$ # by generating a list of files and then excluding
$ # everything # that doesn't match *.foo:
$ complete -f -X '!*.foo' myfoo

$ # Tray again:
$ myfoo <TAB><TAB>

For more complex cases where you need more control over how things are completed you can tell bash to call a function for doing the completion work. This is a function that you supply, that you would probably source from your .profile file. The function name is then supplied as an argument to the -F option of complete:

$ complete -F _mycomplete_ myfoo

A basic version of _mycomplete, which at this point doesn't do anything more than our simple command line usage of complete above, would be something like:

function _mycomplete_()
    local cmd="${1##*/}"
    local word=${COMP_WORDS[COMP_CWORD]}
    local line=${COMP_LINE}
    local xpat='!*.foo'

    COMPREPLY=($(compgen -f -X "$xpat" -- "${word}"))

complete -F _mycomplete_ myfoo

In the first three lines of the function body we create some useful local variables, here mainly for showing what's available since most of them aren't used in the function. The first, cmd, gets the command that's being executed, this can be used if your completion function can handle multiple commands. The second, word, gets the word that is being completed, this can be used if your completion strategy changes based on the word that's being expanded, it's also needed so that only matching values are returned. The third, line, gets the entire command line that is being completed. The fourth variable, xpat, is our exclusion pattern, the same one used in the simple example above. Check the bash man page for other useful COMP_* variables.

The only real code in the function is the last line that sets the variable COMPREPLY, which is our reply to bash's request to expand something. This line uses compgen to generate the expansion. The compgen command accepts most of the same options that complete does but it generates results rather than just storing the rules for future use. Here we tell compgen to create a list of files with -f. Then we tell it to exclude all the files that match our exclusion pattern with -X "$xpat". And finally, we pass in the word being completed so that only items that match it are returned.

Now, let's consider a slightly more complex example. Let's use our complete function to complete multiple commands and let's also change it so that it expands things for the myfoo command differently depending on the command line arguments given to myfoo.

Specifically, let's assume that myfoo can both foo things and unfoo them, so myfoo -f myfile creates and myfoo -u creates myfile. Think of the -d option to gzip or bunzip2. So, in this case we only want to show non foo files if -f has been specified, and only foo files if -u has been specified.

Further, let's add one more enhancement, let's make our completion function include directory names so that directory names can be used to complete the argument, thereby allowing us to navigate to sub-directories for fooing and unfooing files in subdirectories. Our new function would be:

function _mycomplete_()
    local cmd="${1##*/}"
    local word=${COMP_WORDS[COMP_CWORD]}
    local line=${COMP_LINE}
    local xpat

    # Check to see what command is being executed.
    case "$cmd" in
        # See if we are fooing or unfooing.
        case "$line" in

    COMPREPLY=($(compgen -f -X "$xpat" -- "${word}"))

complete -d -X '.[^./]*' -F _mycomplete_ myfoo mybar

Here, we use the cmd variable to see which command we are completing and change our pattern based on it. When handling the myfoo command, we use the line variable to see if the command line includes the -f or -u option for determining whether we should exclude or include foo files.

To include directories in our output we simply modify the complete command that installs our function by including the arguments -d -X '.[^./]*', which generates a list of directories and then excludes ./ and ../ (the current directory and the parent directory). The directory list is then added to the result returned by calling our completion funtion. We also add our second command mybar to the commands handled by our function.

Now when we run it:

$ # Source our completion function:
$ .

$ # Make some files:
$ touch a b

$ # Make a directory for checking directory inclusion:
$ mkdir astuff

$ # See what we get when we want to foo something
$ myfoo -f <TAB><TAB>
a  astuff/  b

$ # See what we get when we want to unfoo something
$ myfoo -u <TAB><TAB>  astuff/

Once you've digested all of this, check the file /etc/profile.d/complete.bash to see the default completions that come with bash. You'll notice in that file that there are numerous complications that we've ignored here, such as what happens when somebody is trying to complete a word such as ${ABC or $(ca. In these cases the completion needs to return, respectively, a variable name and a command name and not a data file name.


Mitch Frazier is an Associate Editor for Linux Journal.


Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Tab completion like ncftp?

Louise Hoffman's picture

Dear Mitch,

Awesome post.

I have been trying to write a tab completion that works just like in ncftp, which is:

$ ls
file1 file2 file3
$ cat file[TAB]
1 2 3

So I was wondering, if you could help me out? =)

Right now I have

_b() {
local word=${COMP_WORDS[COMP_CWORD]}
echo $word;
complete -F _b TEST

Is it possible to have this work for all commands, and not just TEST?

And how do I remove $word from "compgen"'s output?

Best regards,


Mitch Frazier's picture

The following should remove the "word" from the completion list. Completion functions just return a bash array and you can manipulate it to contain whatever you like:

_b() {
        local word=${COMP_WORDS[COMP_CWORD]}
        COMPREPLY=($(compgen -f -- "${word}"))
        if [[ "$word" ]]; then
                local  w
                local  i=0
                local  n=${#COMPREPLY[*]}
                while [[ $i -lt $n ]]
                        let i++

I don't see any way to get a completion to work for all commands but I suppose you could always do something like this if you really need to:

for c in /bin/* /usr/bin/* ~/bin/*
        complete -F _b $(basename $c)

Mitch Frazier is an Associate Editor for Linux Journal.

Thank u sir, useful one.

rajesh.r.s.'s picture

Thank u sir, useful one.


John Hardin's picture

You know you're using autocompletion too much when you start hitting [TAB] to try to autocomplete your username (or worse, your password) during login...



Mitch Frazier's picture

TAB TAB TAB -- hmmm I had a really snappy response, why won't this thing complete it.

Mitch Frazier is an Associate Editor for Linux Journal.

Geek Guide
The DevOps Toolbox

Tools and Technologies for Scale and Reliability
by Linux Journal Editor Bill Childers

Get your free copy today

Sponsored by IBM

8 Signs You're Beyond Cron

Scheduling Crontabs With an Enterprise Scheduler
On Demand
Moderated by Linux Journal Contributor Mike Diehl

Sign up and watch now

Sponsored by Skybot