Let’s say we have some project we are working on. The project involves a number of shell scripts for various tasks, like versioning, building, testing, deploying etc. It is useful to have these scripts in your $PATH
while working on that project. There are several ways to do that. One way is to add the directory containing them to $PATH
, but only for the time we’re working on the project. Another is to use things like npm run
and the scripts
property of package.json
(and I’m fairly sure many other languages feature similar facilities). Recently, I found yet another one, which is pretty useful and extremely cool at the same time. I prefixed all the scripts with the project’s (abbreviated) name, say dw-
. So, assume I have scripts like dw-version.sh
, dw-build.sh
, dw-test.sh
etc. Now I can permanently add the directory containing them to my $PATH
, since I (kind of) namespaced them and they won’t get mixed with any other project’s scripts (nor general use utilities). The only trouble that’s left is that typing dw-
each time is a bit tedious.
Well, Bash to the rescue. (I never thought I’d ever say that.) It turns out that you can bind a key to any Bash command (much like in Emacs!), and also to a sequence of keys (and that works much like keyboard macros). (And to give credit where credit is due, I learned most of what I write here from this very nice article.)
So, the first thing to do is to find the key sequence I can make equivalent to typing dw-
, then learn how to represent it in a bind
invocation, and finally install the binding.
Let’s say I want to use M-d
(Alt+d
in modern speak). I press C-v
M-d
(that is, Ctrl-v Alt+d
), and Bash replies with ^[d
. Now ^[
is a secret code for the escape character (by default Bash treats Alt+x
the same as ESC x
, much like Emacs), so to enter that sequence, you can say \ed
. Now I can say
bind '"\ed":"dw-"'
and M-d
just types dw-
for me. (The quotes on the right-hand side mean that dw-
is a macro – that is, a sequence of characters to simulate typing – and not a name of a Bash function.) I can now press tab
twice to list all my scripts beginning with dw-
.
But there’s even more. After reading the article I started to wonder if Bash could simulate pressing tab
for me, too. And it turns out that yes, it can! I can say
bind '"\ed":"dw-\t\t"'
and now M-d
types dw-
for me and shows me all the autocompletions! (You might also want to use a single \t
so that the completions are not shown at once, but one press of tab
is enough to reveal them.)
Of course, this breaks when I press M-d
with something already typed in the current line. I can live with that, but if you want to get fancy, you can do something like this:
bind '"\ed":"\C-a \e[Ddw-\t\t"'
Now, if you start with typing something and then press M-d
, Bash will move the point to the beginning of line, type a space, move one character left, and only then type dw-
and press tab
twice. (Of course, you could use \C-b
instead of \e[D
.)
I’m definitely not a fan of Bash as a programming language – but I am a fan of the GNU Readline library (which Bash uses). (Serendipitously, when the bulk of this post wa already written, Mickey Petersen of Mastering Emacs fame wrote a pretty long and detailed article about GNU Readline. (By the way, I learned from that very article that instead of putting bind
commands in your ~/.bashrc
file, you can also put the bindings themselves in the ~/.inputrc
file.) Make sure to check out Mickey’s post – it’s also full of useful stuff!