Wednesday, January 11, 2006
Better Bashing
Bash is a big part of my life. I spend a huge part of my day interacting with my shell at the command prompt. For years now, Bash has been my shell of choice. That's not to say it's perfect, however. There have been a few nagging deficiencies in Bash that I have been meaning to remedy, and I finally got around to doing just that today. (Relevant config file contents are at the bottom of this post.)
One thing that's long annoyed me in Bash is that history searches only show the next match (whether forward or reverse, incremental or not). I want to be able to scroll back through all commands in my history that match a search. It turns out that there are a couple readline builtins that accomplish this: history-search-forward and history-search-backward. (See 'bind -l' for a list of readline commands, by the way.) They are not incremental, but they work well if you know the first few characters of the command, which is the common case for me. I bound these two commands to my up and down arrows using my .inputrc and now the arrow keys scroll through commands in my history that match the first few characters I type at the prompt. It's pretty handy.
The next feature I've always wanted in my shell is the equivalent of Ctrl-o and Ctrl-i in vim. Basically it's like the Back and Forward buttons on your web browser. Now, I know most of you are thinking that pushd and popd already get me there, but those commands behave somewhat differently. Read the Bash manual for details. What I want is to use cd to change directories, then use Ctrl-o and Ctrl-i to jump back and forward along my directory navigation history. I was going to somehow build this on top of pushd and popd, but I decided to just write my own system instead using a couple of arrays for the stacks. Here is the code, which I've included in my .bash_profile:
forward_size=0
back_size=0
declare -a back_dirs
declare -a forward_dirs
back_dir()
{
if [ $back_size = 0 ]; then
return;
fi
back_size=$((back_size-1));
forward_dirs[$forward_size]=`pwd`;
forward_size=$((forward_size+1));
builtin cd ${back_dirs[$back_size]};
}
forward_dir()
{
if [ $forward_size = 0 ]; then
return;
fi
forward_size=$((forward_size-1));
back_dirs[$back_size]=`pwd`;
back_size=$((back_size+1));
builtin cd ${forward_dirs[$forward_size]};
}
cd()
{
wd=`pwd`;
builtin cd "$@";
if [ $? = "0" ]; then
back_dirs[$back_size]=$wd;
back_size=$((back_size+1));
forward_size=0;
fi
}
Using my new version of cd and binding my new functions back_dir and forward_dir to some keys (I ended up using Ctrl-f and Ctrl-b, also vi-inspired) I now have the behavior I was looking for. Here are the contents of my .inputrc:$include /etc/inputrc
"\M-[B": history-search-forward
"\M-[A": history-search-backward
"\C-b": "back_dir^M"
"\C-f": "forward_dir^M"
"\M-i": kill-whole-line
Note that I also bound Meta-i to kill-whole-line, which is too useful of a command to be unbound (yet it is unbound by default). There is probably a much simpler way of doing this, but this was easy enough and it works great. Try it out and let me know what you think.
posted by David Shoemaker @ 6:38 PM 4 comments

4 Comments:
Anonymous said...
Doesn't ctrl-c kill the whole line?
Anonymous said...
Hey David,
I have been looking for something similar for awhile now (and too lazy to write it since bash isn't my thing).
On my setup I have to put the code into ~/.bashrc instead of ~/.bash_profile (even though .bash_profile calls .bashrc??).
I also had to delete the ^M characters at the end of back_dir and forward_dir to get things working. DOS newline??
Anyways, thanks for the code.
Thanks,
Wilson
David Shoemaker said...
anonymous - AFAIK, ctrl-c does not kill the whole line with default bash key bindings. Instead it seems to cancel input and start a new line, which is similar and accomplishes the same thing, really.
Wilson - the ^M characters probably won't copy correctly from the web browser. That is actually one character (ctrl-M), not two. ctrl-M is usually the equivatlent of carriage return, which is what I'm using it for here. I don't want to have to press 'Enter' to execute my forward_dir and back_dir commands.
Anonymous said...
I'm posting this comment from Jamie's new motorola q phone. Pretty sweet, eh?
Post a Comment