Posted in Systems Administration
When first exposed to the .bash_history file most users are appalled that their commands are being stored. Some systems administrators get an evil look in their eyes. Both are highly overrated responses. This file is actually extremely helpful and understanding how to fully utilize bash's history functions are paramount to being able to quickly recall commands and navigate the terminal.
An aside for evil systems administrators: reading .bash_history files is one of the least useful methods of monitoring used on a server for a number of reasons, including that any sufficiently malicious user wouldn't leave their commands behind.
History is maintained both in a file .bash_history and in ram (manipulated with the command history and environment variables). It is important to note that the history contained in ram is only written after a user logs out of his or her session.
Let's take a look at some of the important bash history variables and their explanations:
| Variable | Meaning |
|---|---|
| HISTFILE | This variable contains the location of the history file. When bash is logged out of, the contents of the history command will be written to this file. |
| HISTFILESIZE | The variable contains the maximum number of lines that may be in the history file. When bash writes to the history file, the oldest commands that go over this maximum number will be deleted. |
| HISTCONTROL | This variable contains instructions for what should be ignored when added to the history file. This will be explained further below. |
| HISTSIZE | This variable contains the maximum number of lines that be contained in the in-ram version of history, it acts the same as HISTFILESIZE. |
The HISTCONTROL shell variable has four settings that define a set of standard rules that can used to determine which lines typed into the shell will be recorded into the history file.
| Setting Name | Setting Effect |
|---|---|
| Ignorespace | When this setting is used, any line that begins with one or more spaces will not be added to the history. |
| Ignoredups | When this setting is used lines that match the previous entry in the history are not saved. |
| Ignoreboth | When this setting is used it implies the conditions of both ignoredups and ignorespace. |
| Not Defined | If HISTCONTROL is not defined no conditions will be applied before the line is entered into the history [1]. |
These values can be changed using the export command, followed by the variable to set and its value. We're going to set up a small environment for testing, beforehand lets take a look at the default values.
symkat@symkat:~$ export | grep HIST declare -x HISTCONTROL="ignoreboth" declare -x HISTFILE="/home/symkat/.bash_history" symkat@symkat:~$ echo $HISTSIZE; echo $HISTFILESIZE 500 500 symkat@symkat:~$
Now let's limit the history to ten lines.
symkat@symkat:~$ export HISTSIZE=10 HISTFILESIZE=10 symkat@symkat:~$ echo $HISTSIZE; echo $HISTFILESIZE 10 10 symkat@symkat:~$
Now that we understand the various settings used by bash's history, let's fill it with some lines and then take a look at how we can get to that quick-recall of commands.
symkat@symkat:~$ vim script.pl symkat@symkat:~$ perl script.pl Hello World symkat@symkat:~$ history 1 vim script.pl 2 perl script.pl 3 history symkat@symkat:~$ vim index.html symkat@symkat:~$ history 1 vim script.pl 2 perl script.pl 3 history 4 vim index.html 5 history symkat@symkat:~$
It is important to note the numbers shown before the command line. The first command was given the number 1, while commands after each got incremented by one. Your last command will always have the highest number, while the older the command the lower the number.
The numbering comes into play when we look at the bang bang operator. You can rerun previous commands by addressing them by number, or be âN commands agoâ. Let's take a look:
symkat@symkat:~$ !-2 vim index.html symkat@symkat:~$ !! vim index.html symkat@symkat:~$ !1 vim script.pl symkat@symkat:~$
The first command we used asked bash to run the second to last (-2) command again. By looking at the code example above this we see that the last command was history and the one previous was vim index.html. vim index.html was run again. The !-2 expanded to the command, and was added back to the history file as the last command.
After this we ran Bang-Bang, or !!. This expands to the very last command that was run (shorthand for !-1). Because the âvim script.plâ was expanded and added to the history file, this is the command that was run.
Finally, we addressed the very first command by its actual index number, 1. This ran vim script.
symkat@symkat:~$ history 1 vim script.pl 2 perl script.pl 3 history 4 vim index.html 5 history 6 vim index.html 7 vim script.pl 8 history symkat@symkat:~$
Taking a look at the history command now we notice something. Although vim index.html was run twice in a row, the second time it's not shown. This goes back to the HISTCONTROL setting, which works against expansion with Bang-Bang operators.
It is worth noting that the Bang-Bang operators here do not have to be run on a line exclusively. You can combine them with literal text to have the expansion fill in, so that both the expansion and the literal strings are combined into one line that is executed.
symkat@symkat:~$ echo !! echo history history symkat@symkat:~$ !! manipulation can be fun. echo history manipulation can be fun. history manipulation can be fun. symkat@symkat:~$
The double lines here are the result of the Bang-Bang operator printing the line that was executed before actually executing the line.
Suppose that you would like to take the last argument to the last command and use it in your current line. You can use the Bang-Cash operator.
symkat@symkat:~$ ping 192.168.1.1 PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data. 64 bytes from 192.168.1.1: icmp_seq=1 ttl=63 time=0.243 ms 64 bytes from 192.168.1.1: icmp_seq=2 ttl=63 time=0.228 ms 64 bytes from 192.168.1.1: icmp_seq=3 ttl=63 time=0.250 ms ^C --- 192.168.1.1 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2000ms rtt min/avg/max/mdev = 0.228/0.240/0.250/0.015 ms symkat@symkat:~$ traceroute !$ traceroute 192.168.1.1 traceroute to 192.168.1.1 (192.168.1.1), 30 hops max, 40 byte packets 1 207.99.1.13 (207.99.1.13) 0.568 ms 0.590 ms 0.611 ms 2 192.168.1.1 (192.168.1.1) 0.155 ms 0.149 ms 0.149 ms symkat@symkat:~$ nmap -p22,80,443 -P0 !$ nmap -p22,80,443 -P0 192.168.1.1 Starting Nmap 4.62 ( http://nmap.org ) at 2010-09-15 01:30 PDT Interesting ports on 192.168.1.1: PORT STATE SERVICE 22/tcp open ssh 80/tcp open http 443/tcp open https Nmap done: 1 IP address (1 host up) scanned in 0.070 seconds symkat@symkat:~$
You can complete previous commands by the first few characters of the command as well. The Bang Command operator takes a string and matches it against previous commands executed. The matching is done such that the string is anchored to the beginning of the command, and an implicit * is attached to the end. It searches ordered based on the most recent commands executed.
symkat@symkat:~$ !nm nmap -p22,80,443 -P0 192.168.1.1 Starting Nmap 4.62 ( http://nmap.org ) at 2010-09-15 01:34 PDT Interesting ports on 192.168.1.1: PORT STATE SERVICE 22/tcp open ssh 80/tcp open http 443/tcp open https Nmap done: 1 IP address (1 host up) scanned in 0.021 seconds symkat@symkat:~$
In the situation where you know a part of the string which is not anchored to the beginning, or wish to search another part of the sting (for instance, looking at an IP address you recently nmap'ed or sshed to) you can use the Bang-Question-Mark-String operator.
symkat@symkat:~$ !?192 nmap -p22,80,443 -P0 192.168.1.1 Starting Nmap 4.62 ( http://nmap.org ) at 2010-09-15 01:36 PDT Interesting ports on 192.168.1.1: PORT STATE SERVICE 22/tcp open ssh 80/tcp open http 443/tcp open https Nmap done: 1 IP address (1 host up) scanned in 0.022 seconds symkat@symkat:~$
If you're not too sure about the command you're about to run with a Bang operator, you can always use colon p (:p) to print the line that would be run without executing it. Because the expansion will be added to the history file, you can simply up-arrow-enter if it matches. It is worth noting that :p is a shortcut for ?:p. If you're using substring searching, you will need to use ?:p to have the same effect. If you're using string searches, or by number you can simply use :p.
symkat@symkat:~$ !!:p nmap -p22,80,443 -P0 192.168.1.1 symkat@symkat:~$ !nm:p nmap -p22,80,443 -P0 192.168.1.1 symkat@symkat:~$ !-9:p nmap -p22,80,443 -P0 192.168.1.1 symkat@symkat:~$ !-4:p history symkat@symkat:~$ !?192?:p nmap -p22,80,443 -P0 192.168.1.1 symkat@symkat:~$ !?192:p -bash: !?192:p: event not found symkat@symkat:~$
Although we've been using substring matching against the first part of an argument, it can be used against any portion of the strings in an argument. Searching for 168 would have yielded the exact same results above.
You may run into errors if your commands don't actually match anything in the history.
symkat@symkat:~$ !1 -bash: !1: event not found symkat@symkat:~$ ! -bash: syntax error near unexpected token `newline' symkat@symkat:~$ !!! -bash: !: event not found symkat@symkat:~$
You can delete an entry from the history list by passing the -d switch and the number of the command in the history index to delete it.
symkat@symkat:~$ history 28 echo "History Entries" 29 random 30 some history here 31 history symkat@symkat:~$ history -d 28 symkat@symkat:~$ history 28 random 29 some history here 30 history 31 history -d 28 32 history symkat@symkat:~$ history -d 30 symkat@symkat:~$ history 28 random 29 some history here 30 history -d 28 31 history symkat@symkat:~$
After deleting an entry in history, the number will be replaced with the next command that was run.
You can also clear the history completely with the -c switch.
symkat@symkat:~$ history -c symkat@symkat:~$ history 28 history symkat@symkat:~$
Bash history is extremely powerful and can save a great deal of time that would otherwise be spent with one's finger down on the up-arrow searching for a previous-entered command.
What's your favorite time saver in a terminal?