Working With History in Bash
Yesterday we talked about favorite bash features (on the ##textmate
IRC channel). I figured it was worth posting mine to this blog, they mostly revolve around history, hence the title.
Setup
My shell history collects a lot of complex command invocations which take time to figure out. To ensure that I have access to them at a later time, I have the following 3 lines in my bash init:
export HISTCONTROL=erasedups
export HISTSIZE=10000
shopt -s histappend
The first one will remove duplicates from the history (when a new item is added). For example if you switch between running make
and ./a.out
in a shell, you may later find that the last 100 or so history items is a mix of these two commands. Not very useful.
The second one increase the history size. With duplicates erased, the history already holds a lot more actual information, but I still like to increase the default size of only 1,000 items.
The third line ensures that when you exit a shell, the history from that session is appended to ~/.bash_history
. Without this, you might very well lose the history of entire sessions (rather weird that this is not enabled by default).
History Searching
Now that I have my history preserved nicely in ~/.bash_history
there are a few ways to search it.
Using Grep
The most crude is grep
. You can do:
history|grep iptables
For me (on this particular Linux server) I get:
4599 iptables -N http-block
4600 iptables -A http-block -s 58.60.43.196 -j DROP
4601 iptables -A INPUT -p tcp --dport 80 -j http-block
4602 iptables -L http-block
4603 iptables-save -c
4604 history|grep iptables
I do this often enough to have an alias for history
(which is just h
).
From the output I can either copy/paste the stuff I want, or repeat a given history event. You’ll notice that each history event has a number, you can repeat e.g. event number 4603 simply by running:
!4603
I will write a bit more about referencing history events in History Expansion.
Prefix Searching
Similar to how you can press arrow up for the previous history event, there is a function you can invoke for the previous history event with the same prefix as what is to the left of the insertion point.
This function is called history-search-backward
and by default does not have a key equivalent. So to actually reach this function, I have the following in ~/.inputrc
(or /etc/inputrc
when I control the full system):
"\ep": history-search-backward
This places the function on ⎋P (escape P). So if I want to repeat the iptables-save -c
history event we found in previous section, all I do is type ipt
and hit ⎋P. If it finds a later event with the same prefix, hit ⎋P again to go further back.
This functionality is offered by the readline
library, so if you setup this key, you have access to prefix searching in all commands which use this library.
Incremental Search
It is possible to press ⌃R to do an incremental (interactive) search of the history.
Personally I am not a big fan of this feature, so I will leave it at that :)
Update: The reason I dislike ⌃R is both because the interactive stuff just seems to get in the way (when ⎋P is what I need 99% of the time) and because it fails in cases where I “switch shell”, for example I may do: ssh mm
press return, then instantly type: f⎋P
and again hit return (to execute free -m
on the server called mm
). I enter this before the connection to the server has been fully established, and here ⌃R would have been taken by the local shell, but it is the shell history at the server I want to search.
History Expansion
History Expansion was what we did above when we ran !4603
. It is a DSL for referencing history events and optionally run transformations on these.
Anyone interested in this should run man bash
and search for History Expansion
, but just to give you a feel for what it is, I will reference a subset of the manual and provide a few examples.
Event Designators
First, an event designator starts with !
and then the event we want to reference. This can be:
«n» Reference event number «n».
-«n» Go «n» events back.
! Last line (this is the default).
# Current line.
«text» Last event starting with «text».
?«text» Last event containing «text».
So if we want to re-run our iptables-save -c
we can do: !ipt
.
What’s more useful though is to use history references as part of larger commands.
For example take this example:
% which ruby
/usr/bin/ruby
% ls -l $(!!)
lrwxr-xr-x 1 root wheel 76 30 Oct 2007 /usr/bin/ruby -> ../../System/Library/Frameworks/Ruby.framework/Versions/Current/usr/bin/ruby
Or something like:
% make some_target
(no errors)
% path/to/target some arguments
(no errors)
% !-2 && !-1
Word Designators
In the previous section we referenced entire history events. It is possible to reference just a subset of a history event by appending a :
to the event designator and then the word of interest, the two most useful are:
«n» Reference the «n»’th word.
$ Reference the last word.
So for example we can do:
% mkdir -p /path/to/our/www-files
(no errors)
% chown www:www !$
(no errors)
Here we reference last word of last line. We can also reference stuff on the same line, e.g.:
% cp /path/to/important/file !#:1_backup
To reference the last word of last line one can also press ⎋_
which will immediately insert that word.
Modifiers
To make history substitution even more useful (and harder to remember), one can also add a modifier to the event designator.
The most useful modifiers are in my experience :h
and :t
, these are head and tail respectively or better know as dirname
and basename
.
An example could be:
% ls -l /path/to/some/file
(listing of file)
% cd !$:h
(change directory to that of file)
Brace Expansion
Somewhat related to the backup example where we reference the first argument as !#:1
and append _backup
to this, another approach is bracket expansion.
Anywhere on a command line, one can write {a,b,c}
which will expand to the 3 words a
, b
, and c
. If we include a prefix/suffix, that will be part of each of the expanded words. We can also leave the word in the braces empty, and have it expand to just the prefix/suffix, so for example we can do:
% cp /path/to/important/file{,_backup}
This is functionally equivalent to:
% cp /path/to/important/file !#:1_backup
But lack of hardcoded word number is IMO an improvement.