The UNIX/Linux shells evolved on a planet where saving every keystroke and millisecond of time was absolutely essential to survival. As a result, they're chock full of shortcuts, many of them with overlapping functionality, letting the user choose the method that works best for them.
The shell command history is a prime example. Even a newborn knows to use the up and down arrow keys to recall commands, and most toddlers are piping
history
into
less
to perform manual searches for complicated commands they don't feel like recreating. But if you ever have the chance to stare over the shoulder of a grey-bearded shell guru, you'll see that the true masters use several different techniques to pull up commands in the most efficient way. The following report was compiled from the sage advice given by these mysterious wizards
.
Bang!
We'll start with the classic C Shell history syntax. You've no doubt used an exclamation point to re-execute a command from the history list, like !42
, or have used the double exclamation to execute the previous command, !!
. You can get pretty fancy with the available event designators, for instance, !-3
will execute the command three commands ago. And !echo
will execute the most recently run command that began with echo.
This syntax has always scared me, as I could see myself executing a dangerous command without realizing it, especially if I'm in a hurry. But there is a safer way to use it, just include the :p
modifier, which displays the command, but doesn't actually execute it. For instance, if the last command executed was echo Hello
, typing !!:p
would preview the command without running it, so you'd see echo Hello
, instead of Hello
. This allows you to make sure it's the command you intended to recall, and you can simply press the up arrow to execute it.
The !
syntax can be really useful in a grep-the-history-file workflow. Say I grep for the last time I issued an esxcfg-firewall command:
history | grep esxcfg
Output:
203 esxcfg-firewall --openPort 9090,tcp,in,SimpleHTTP
!203:p
Output:
esxcfg-firewall --openPort 9090,tcp,in,SimpleHTTP
Now if I hit the up arrow, the command is put on the prompt, and I can edit it to add a different port, etc.
If you'd like to delve deeper, google c shell command history
Fix Command
Another method for recalling commands from the history list is the bash built-in Fix Command, invoked with fc
. To get the skinny on fc
, bring up the man page for the built-in commands, with man builtins
.
Invoking fc with the -l
option will print the last 16 commands. You can also specify a range of history commands to display, like:
fc -l 208 234
The fc
command can come in real handy when you are trying to recall and edit a whole series of commands. For instance, say you remember adding an alias to your .bashrc file, and you want to add an additional alias using the same series of commands to make sure it was configured properly. You recall using a cp
command first to backup the .bashrc file, and specifying 'cp' as a search string after fc -l
will print the history list beginning with the last occurrence of the command that matches the search string:
fc -l cp
110 cp .bashrc backup_bashrc
111 echo "alias lax='ls -lax'" >> ~/.bashrc
112 cat .bashrc
113 . .bashrc
114 lax
To create an la
alias, invoke fc
with a range of history events to copy them all into the default editor, which is set to vi in the ESX console:
fc 110 114
Running the above command will copy the history events 110 through 114 into a vi editor session, where they can be modified to create the alias for la
. Typing ZZ
in vi will exit the editor and execute all the commands in the buffer: backing up .bashrc, creating the alias, cat'ing the file to see the change, restarting bash, and finally testing the new la
alias. I don't use fc
very often, but for scenarios like this it is a great tool to be familiar with.
vi Command Editing
The next two methods for using the command history rely on functionality provided by the GNU Readline library, a standard set of C functions available to any application, including bash. There are two editing and key-binding modes, emacs and vi. The default mode is emacs, and you can see what mode your shell is in now by typing:
set | grep SHELLOPTS
If you're in the default emacs mode, you'll see emacs in the colon-separated list of options. To change this to vi mode, type:
set -o vi
Now if you check the SHELLOPTS
variable, you'll find emacs has been replaced with vi. I'm a vi kind of guy, so I always add the set option to my .bashrc file, with a command like:
echo "set -o vi" >> ~/.bashrc
And source .bashrc again to get the changes:
. ~/.bashrc
We'll look at how to use the Readline library in vi editing mode with an easy example. I've just grepped the default word file that comes with the service console for every word that begins with 'a' (this word file is not present in ESX 4.0). I'll do the same for the letters 'b' through 'f', and if I execute history
, this is the output:
[admin@esx02 admin]$ history
1 grep ^a /usr/share/dict/words
2 grep ^b /usr/share/dict/words
3 grep ^c /usr/share/dict/words
4 grep ^d /usr/share/dict/words
5 grep ^e /usr/share/dict/words
6 grep ^f /usr/share/dict/words
7 history
Back at the shell prompt, we can search through the history list for the last instance of the grep command. Press Esc
to enter vi command editing mode, and then use a forward slash to search, just like in a normal instance of vi:
/grep
After hitting return, we should find grep ^f /usr/share/dict/words
has been placed on the command line. Pressing n
will iterate through each grep command until the first instance is found -- the grep command for words starting with 'a'. Continuing to press n
will do nothing now, as we've reached the end of the matches for grep. However, pressing N
will now iterate through the grep matches in reverse, working its way to the most recent grep command. This is handy and easy to remember, n
or N
for next, forward or backward through the history list.
Of course, the whole point of accessing the history list with this method is to easily edit the commands before executing them. If we wanted to modify the grep command to find all words starting with 'fox', we can just press Esc
at a bash prompt to enter vi command editing mode, type /grep ^f
to find the history entry where we searched for words starting with f, press i
to enter vi insert mode, edit the command: grep ^fox /usr/share/dict/words
, and press return to execute it. If we want to get every word that starts with ox, we just perform another search, /grep ^fox
, move the cursor over the 'f', and hit x
-- the vi delete character command -- to remove it.
This example is beyond goofy, but if you play around with this method, you'll find it to be very powerful and a huge timesaver.
Ctrl-r
If you press Ctrl-r
in your terminal, you'll get a curious looking prompt:
(reverse-i-search)`':
If you start typing a part of the command you wish to search through the history for, the command will appear after the prompt:
(reverse-i-search)`grep': grep ^f /usr/share/dict/words
This is the most recent grep found while searching back through the history list. If you press Ctrl-r
again, you'll find the one before that:
(reverse-i-search)`grep': grep ^e /usr/share/dict/words
If you hit return, the command will execute. If you hit the left or right arrow keys, the command will be left on the prompt so you can edit it first.
The reverse-i-search
prompt is a bash built-in named reverse-search-history. You can see that by default it is bound to the Ctrl-r
key by issuing a bind -P
command. If you look through the list, you should also see forward-search-history has been bound to Ctrl-s
. The forward-search-history command is a useful companion because if you play with Ctrl-r
, you'll notice that as it iterates through the history list, it remembers where it left off. So if you reverse search all the way to the start of the history file, you then have to cycle back around, even though the command you want was just one command more recent.
But there is a big problem with forward-search-history, it's bound to Ctrl-s
by default, which is also bound to the stty
stop output command, and stty
will intercept it before anything has a chance to see it (if you already pressed Ctrl-s
and your terminal appears dead, just type Ctrl-q
to bring it back to life).
You can view the key bindings used by stty with this command:
stty -a
But these two commands are too handy to let a little key binding issue get in the way, and we can easily add custom key bindings to our .bashrc file:
bind '"\C-f": forward-search-history'
bind '"\C-b": reverse-search-history'
Just edit the ~/.bashrc file with the editor you prefer, add those two lines in, and re-source with: . ~/.bashrc
Now Ctrl-b
searches back through the history file, and Ctrl-f
searches forward, and you can toggle back and forth between them and the search term will remain. Nice!
Share your secrets
Hopefully you found a new trick to play with, and if you have your own history workflow, please leave a comment with the details.
...read more