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 a.bar a.foo b.bar b.foo
$ # Use the command and try auto-completion.
$ # Note that all files are displayed:
$ myfoo <TAB><TAB>
a.bar a.foo b.bar b.foo
$ # 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>
a.foo b.foo
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 myfile.foo and myfoo -u myfile.foo 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
myfoo)
# See if we are fooing or unfooing.
case "$line" in
*-f*)
xpat='*.foo'
;;
*-u*)
xpat='!*.foo'
;;
*)
xpat='*.foo'
;;
esac
;;
mybar)
xpat='!*.bar'
;;
*)
xpat='!*'
;;
esac
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:
$ . bcomp2.sh
$ # Make some files:
$ touch a b a.bar a.foo b.bar b.foo
$ # Make a directory for checking directory inclusion:
$ mkdir astuff
$ # See what we get when we want to foo something
$ myfoo -f <TAB><TAB>
a a.bar astuff/ b b.bar bcomp2.sh bcomp.sh
$ # See what we get when we want to unfoo something
$ myfoo -u <TAB><TAB>
a.foo astuff/ b.foo
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.
Today’s modular x86 servers are compute-centric, designed as a least common denominator to support a wide range of IT workloads. Those generic, virtualized IT workloads have much different resource optimization requirements than hyperscale and cloud applications. They have resulted in a “one size fits all” enterprise IT architecture that is not optimized for a specific set of IT workloads, and especially not emerging hyperscale workloads, such as web applications, big data, and object storage. In this report, you will learn how shifting the focus from traditional compute-centric IT architectures to an innovative disaggregated fabric-based architecture can optimize and scale your data center.
Sponsored by AMD
Built-in forensics, incident response, and security with Red Hat Enterprise Linux 6
Every security policy provides guidance and requirements for ensuring adequate protection of information and data, as well as high-level technical and administrative security requirements for a system in a given environment. Traditionally, providing security for a system focuses on the confidentiality of the information on it. However, protecting the data integrity and system and data availability is just as important. For example, when processing United States intelligence information, there are three attributes that require protection: confidentiality, integrity, and availability.
Learn more about catching the bad guy in this free white paper.
Sponsored by DLT Solutions
| Using Salt Stack and Vagrant for Drupal Development | May 20, 2013 |
| Making Linux and Android Get Along (It's Not as Hard as It Sounds) | May 16, 2013 |
| Drupal Is a Framework: Why Everyone Needs to Understand This | May 15, 2013 |
| Home, My Backup Data Center | May 13, 2013 |
| Non-Linux FOSS: Seashore | May 10, 2013 |
| Trying to Tame the Tablet | May 08, 2013 |
- RSS Feeds
- Making Linux and Android Get Along (It's Not as Hard as It Sounds)
- New Products
- Drupal Is a Framework: Why Everyone Needs to Understand This
- A Topic for Discussion - Open Source Feature-Richness?
- Home, My Backup Data Center
- Validate an E-Mail Address with PHP, the Right Way
- Tech Tip: Really Simple HTTP Server with Python
- New Products
- Trying to Tame the Tablet
- git-annex assistant
5 hours 37 min ago - direct cable connection
5 hours 59 min ago - Agreed on AirDroid. With my
6 hours 9 min ago - I just learned this
6 hours 13 min ago - enterprise
6 hours 44 min ago - not living upto the mobile revolution
9 hours 35 min ago - Deceptive Advertising and
10 hours 10 min ago - Let\'s declare that you have
10 hours 11 min ago - Alterations in Contest Due
10 hours 12 min ago - At a numbers mindset, your
10 hours 14 min ago
Enter to Win an Adafruit Prototyping Pi Plate Kit for Raspberry Pi

It's Raspberry Pi month at Linux Journal. Each week in May, Adafruit will be giving away a Pi-related prize to a lucky, randomly drawn LJ reader. Winners will be announced weekly.
Fill out the fields below to enter to win this week's prize-- a Prototyping Pi Plate Kit for Raspberry Pi.
Congratulations to our winners so far:
- 5-8-13, Pi Starter Pack: Jack Davis
- 5-15-13, Pi Model B 512MB RAM: Patrick Dunn
- Next winner announced on 5-21-13!
Free Webinar: Linux Backup and Recovery
Most companies incorporate backup procedures for critical data, which can be restored quickly if a loss occurs. However, fewer companies are prepared for catastrophic system failures, in which they lose all data, the entire operating system, applications, settings, patches and more, reducing their system(s) to “bare metal.” After all, before data can be restored to a system, there must be a system to restore it to.
In this one hour webinar, learn how to enhance your existing backup strategies for better disaster recovery preparedness using Storix System Backup Administrator (SBAdmin), a highly flexible bare-metal recovery solution for UNIX and Linux systems.



Comments
Tab completion like ncftp?
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,
Louise
Completion
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 ]] do w=${COMPREPLY[$i]} COMPREPLY[$i]="${w:${#word}}" let i++ done fi }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/* do complete -F _b $(basename $c) doneMitch Frazier is an Associate Editor for Linux Journal.
Thank u sir, useful one.
Thank u sir, useful one.
Autocompletion
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...
:)
TAB TAB TAB
TAB TAB TAB -- hmmm I had a really snappy response, why won't this thing complete it..png)
Mitch Frazier is an Associate Editor for Linux Journal.