Bash Command Line History
Our speed in the use of the command line can only continue to increase as we become more proficient and learn more. The next item that we learn is the command history in bash. Each command that we have entered at the command line is stored in memory for quick access and persisted in a file, $HOME/.bash_history. With the commands stored persistently they are maintained after logout and even a reboot of the system.
The History Command
The history command used by itself will print the full history for the current user, or we can specify the number of recent commands:
Listing 01: Printing bash history
$ history … $ history 5 42 pwd 43 ls -l ../../etc/hosts 44 history 45 ls /etc 46 history 5
If we wanted, we could include the next history number within our command prompt. This may help us in repeating this or other commands later on. Although, as we will see, there are many ways this can be done:
Listing 02: Setting a simple prompt that includes the next history number
$ PS1="\!\$ " 51$
Repeating Commands By Using The Command Number
From the new prompt we can see that the next command to be entered will be entry number 51 in this current user’s history. This would mean that the command the we just used to set the PS1 variable must have beeen command number 50. If we ever needed to rerun the command, it would be easily accessible with the shortcut !50. Consider the following command sequences:
Listing 03: Recalling a command using the history number
51$ ls 52$ PS1="\u \$ " tux $ ls /etc/hosts /etc/hosts tux $ !50 PS1="\!\$ " 55$
Searching Command History Using CTRL+R
Another amazingly useful feature of the bash history is search. Using CTRL+R we are presented with a prompt to enter text that appears on the line that we which to recall. Using CTRL+R multiple times we are able to scroll to the next match. In the following option I search for hosts as in the filename /etc/hosts and use CTRL+R 3 times to retrieve the entry for the command cat -vet /etc/hosts.
Listing 04: Searching bash history with CTRL+R
CTRL+R reverse-i-search)`hosts': cat -vet /etc/hosts 127.0.0.1^Ilocalhost$ $ # The following lines are desirable for IPv6 capable hosts$ ::1^Iip6-localhost^Iip6-loopback$ fe00::0^Iip6-localnet$ ff00::0^Iip6-mcastprefix$ ff02::1^Iip6-allnodes$ ff02::2^Iip6-allrouters$ ff02::3^Iip6-allhosts$ 127.0.1.1^Iubuntu-bionic^Iubuntu-bionic$ $
Repeating Command Using Bang Bang
The exclamation mark can be used elsewhere within the history. The use of !! re-runs the complete previous entire command line entry. This is useful where we have run a command before realizing that we should have elevated our privileges with sudo. We can run the command again, but this time elevated, using sudo !!:
Listing 05: Using the history !! to repeat commands
$ useradd jimb useradd: Permission denied. useradd: cannot lock /etc/passwd; try again later. $ sudo !! sudo useradd jimb
Control Bash History
We can maintain the bash history to be more useful than the default settings allow. These settings we use are the default in Ubuntu 18.04 LTS Server. If we read the contents of the bash variable HISTCONTROL we can see the current setting. This setting controls which items are added to the command history for the current user in the running shell:
Listing 06: Viewing history settings
$ echo $HISTCONTROL ignoreboth
The ignoreboth setting is equivalent ignorespace:ignoredups:
– ignorespace: Will not add commands that begin with whitespace. This is useful if you want to exclude a command from the history. Any leading whitespace is ignored and does not affect the command itself. The command though will not be recorded in the history. For example, you may want to create a user and then set their password. Using chpasswd instead of the passwd command means that you can provide the password from the command line. If you prepend echo ‘user:password’ | sudo chpasswd with a space the password used will not appear in your own history although it was entered at the command line.
– ignoredups: Duplicate commands that are executed consecutively will only be stored once. So, if you use the same ls command consecutively 3 times it will appear just once in the history.
Listing 07: Excluding sensitive commands from your history with ignorespace
64$ sudo useradd bob 65$ echo 'bob:Password1' | sudo chpasswd 65$ history 2 64 sudo useradd bob 65 history 2 66$
Listing 08: Observing the behavior of ignoredups
70$ cd 71$ ls 72$ cd 73$ ls 74$ ls 74$ ls 74$ history 5 70 cd 71 ls 72 cd 73 ls 74 history 5 75$
Note: The ls command at line 71 is not a duplicate of the ls command at line 73 as they are not executed consecutively. Whereas, as we display the history number within the prompt, we see that the number of the command history entry does not increment as we run ls consecutively from line 73.
Deleting Extra History Entries
As we can observe, it is only the sequentially executed commands that are ignored with ignoredups, other non-sequential entries of the same command can and will appear. To improve on this behavior, we can use the setting of erasedups. With this setting previous occurrences of the exact command line are removed from history so only the most recent execution is stored. We may still want to combine this with the ignorespace that we had before so we use the concatenated value of erasedups:ignorespace
Listing 09: Erasing duplicate entries from history with erasedups
76$ declare -x HISTCONTROL="erasedups:ignorespace" 77$ echo $HISTCONTROL erasedups:ignorespace 78$ ls 71$ cd 69$ history 10 60 sudo useradd bob 61 history 2 62 cd /etc 63 history 6 64 history 5 65 declare -x HISTCONTROL="erasedups:ignorespace" 66 echo $HISTCONTROL 67 ls 68 cd 69 history 10 70$
As we observe the prompt now, we see the history number decreases as we enter the ls command and the cd command that have been executed in various non-consecutive entries in our current history. This is a great way of optimizing our .bash_history file and memory entries so we do not store commands that we do not need. To persist this, we can append the entry to our login script.
Listing 10: Persisting the HISTCONTROL variable
70$ echo 'declare -x HISTCONTROL="erasedups:ignorespace"' >> $HOME/.bashrc 71$ tail -n1 $HOME/.bashrc declare -x HISTCONTROL="erasedups:ignorespace" 72$