
Every developer should have at least a basic mastery of their command line. I’d argue that part of that mastery is to develop a set of customizations they take with them wherever they go. These customizations should make things you do all the time faster. Obviously there are diminshing returns here1.
Some of my customizations include bash aliases for my most used git commands. ga is
short for git add, gap is short for git add -p, etc. I also like to have a
syntax highlighted cat and less available to me using Pygments.
Today I added something I’ve wanted for a long time: a cd that works relative to
the directory I do most of my development work in. Now I can run cdx subdir_of_x
and it will take me to ~/x/subdir_of_x.
You may be wondering “Why use precious kilobytes of storage and hours of time writing this up!?” And you’d be corect. By itself, this is not worthy of a blog post. I, however, also added autocompletion for the contents of that directory, and that is worth writing about.
What follows is a short tutorial on bash autocompletion and a tiny bit of bash programming information. It assumes you have a working knowledge of programming, and at least passing familiarity with your terminal.
The Function
In and of itself, this a very easy function2 to write. In your ~/.bashrc add:
function cdx {
cd "$HOME/x/$1"
}
My first test looked like this:
cdx my_code <TAB> _base
No autocomplete, just big gaping tabs in the middle of my command. What’s an enterprising developer3 to do? Spend the morning figuring it out, of course.
The Code
First, the good stuff. All together, when placed in your ~/.bashrc and sourceed,
the following works for hypothetical directory x in your home directory using the new
command cdx:
function cdx {
cd "$HOME/x/$1"
}
function _cdx {
local cur opts
opts="$(ls -1 ~/x)"
cur="${COMP_WORDS[COMP_CWORD]}"
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
}
complete -F _cdx cdx
To make it work:
- Insert this in
~/.bashrc. - Replace all instances of “x” with a directory from your
$HOMEdirectory. - Call
source ~/.bashrc.
You may want to rename cdx with a more intuitive name (for Apsis code, for example,
I’d use cda and _cda).
The Explanation
Now to break the _cdx function down. When it gets obvious feel free to skip to the end, I’ve
attempted to order this from most specific to broadest bash knowledge. (In order of my current
knowledge of bash). I’ve left the most basic stuff (like explaining ls) out.
complete -F _cdx cdxis how you let bash know to attempt an autocomplete. In English it’s:When I run the cdx command please use the function _cdx to autocomplete the command.
compgen -W "${opts}" -- ${cur}is the function that searches an array of possibilities (-W) for the text after--. It’s pretty rigid: no fuzzy searching, no autocorrecting. It just returns “things from the array that start with what you typed.” You are free to replace it with whatever you want as long as it too returns an array (space separated string) of the options. In English:Please give me all the words in the string ‘opts’ that start with what’s in ‘cur’.
COMPREPLYis basically the return value of this function. Whatever array you return here will be presented to you on the command line as options for autocompletion. In English:Here’s what I want to pick from when autocompleting with the current inputs.
cur="${COMP_WORDS[COMP_CWORD]}"is settingcurto the last word on the command line.COMP_WORDSandCOMP_CWORDare the inputs to this function. (You can actually make them real bash function parameters if you want to, but they’re already acceptably named). They are special bash variables that are only available in completion functions.COMP_WORDSis the array of strings currently entered on the command line.COMP_CWORDSis the current length of theCOMP_WORDSarray. In English:Set the variable ‘cur’ to the last word on the command line.
opts="$(ls -1 ~/x)"is where we set the available options by creating an array (space separated string) from the output ofls -1 ~/x. This gets used in thecompgencommand. In English:Set “opts” to the output of “ls -1 ~/x” as the list of potential options to autocomplete from.
local cur optsis a declaration of the cur and opts variables. By usinglocalwe avoid cluttering the global variable namespace. In English:I’m going to use the cur and opts variables. You can forget them when this function ends.
return 0quits the function and sets the bash exit code.0is success, all other values are failures. This is useful in other bash scripts when you want to use it with&&||. Here it simply informs bash that the completion script was successful. In English:IT WORKED! Show the user the values in “COMPREPLY”!
Improvements
This is a great start, and super useful as is, but there is more that could be done:
Going deeper: it’d be nice if I could keep autocompleting deeper into the directory, so
cdx ydir/zdicould autocomplete to~/x/ydir/zdir. This would involve making theopts=line more intelligent.Fuzzy searching: I am great at typos. It’d be nice if I could replace
compgenwith something more intelligent that will ignore small typos.More generic: Being able to match
cdaorcdbto switch to subdirectories of~/apsisor~/breadwould be nice. Writing a script to handle the unique cases when bash fires up would be easily doable. It would, however, require separate autocomplete functions for each named function becuasecompleteonly accepts literal command names.
Troubleshooting/Further Reading
If the above doesn’t work it’s possible your progcomp bash option isn’t set. You can check with:
echo $BASHOPTS | grep -q progcomp && echo "YES" || echo "NO"
By default it’s set, but if for whatever reason it’s not, and you didn’t knowingly do it yourself, you can set it
with shopt -s progcomp. That can be added to your .bashrc.
If that’s not the problem, you will have to debug this the old fashioned way. Use echo "$VARNAME" to see the values
of variables. Finally you may want to read the docs.
I didn’t initially suggest reading the docs because, unfortunately, the documentation for anything contained in the bash man pages is terribly undiscoverable, difficult to search, and not always easy to read 4. All of the above is in the bash man pages. Here’s how you can find the relevant docs:
man bashto open the documentation for bash itself.- Press
/to start searching. - Once you’ve entered your search term hit “Enter” and then use
nandNto search forward and backwards (respectively) for the content you want. - You’ll want the following search terms:
- “Programming Completion” for an overview of completion in bash.
- “compgen” for the function that we use to match input with the search term.
- “complete” for the function used to tell bash to use programmable completion for a given command.
Update: After completing my first draft of this post, Niall pointed me to the far more readable and digestible TLDP.
In Closing
Bash Programmable Autocompletion is powerful, but intimidating at first. Hopefully this post helps you get started. If you build something awesome write it up and ping us on twitter! We’re @ApsisLabs.
Footnotes
-
Couldn’t use an alias, because aliases can’t interpolate arguments, they’re strictly dumb text replacements , and they always include a space at the end. ↩
-
One with a head cold, limited exposure to bash internals like autocomplete, and more important work to accomplish. ↩
-
Man pages, as useful as they are, are dinosaurs from a prehistoric time before hypertext was invented. You can’t follow bolded text as if they were links. ↩