1. Overview
There can be situations when we may take login sessions of the same Linux user across many terminals. And in such cases, we would like to preserve the sequence of commands fired on different terminals in a sequential and consistent fashion.
In this tutorial, we’ll look at ways to preserve Bash history in multiple terminal windows. First, we’ll understand the default behavior of Bash history. Then, we’ll explore ways to maintain history consistency across multiple sessions. Finally, we’ll arrive at solutions to our problem statement.
2. Bash History and Its Default Behavior
We can check the historical sequence of commands executed on a Linux terminal using the history command:
$ history
To filter out the last n commands from history, we can provide the number as an additional option:
$ history 7
This lists the last 7 entries from the Bash history:
$ history 7
112 ls -lrt
113 cat debug9.sh
114 echo "Hello" >| temp.txt
115 set -o | grep noclobber
116 ls -lrt | tail -2 && cat temp.txt
117 echo $PROMPT
118 history 7
By default, the .bash_history file in the user’s home directory stores the sequence of commands executed on a terminal. But we can change the file path and name by setting a special shell variable $HISTFILE. We’ll see this in action later in the tutorial.
When the Bash shell is loaded interactively on user login, it reads the contents of $HISTFILE into memory. During the shell session, it adds contents to the memory file. When the shell exits it writes backs the contents of memory back to disk on $HISTFILE file.
However, we should note here that there’s a limit on the maximum number of lines that can be written back to the disk. We can tweak this limit by another shell variable $HISTSIZE:
$ echo $HISTSIZE
1000
Here as specified in HISTSIZE, our Bash history can contain a maximum of 1000 entries. Let’s assume that the file already has 900 entries, and during our session runtime, 150 new entries are added. In that case, after Bash exits, the first 50 entries of the file will be lost since we had set HISTFILE to contain only 1000 latest entries.
3. Enabling the File Append Option
Previously, we discussed that when Bash exits, it dumps the history in memory to a disk file. Consequently, the history file is overwritten every time we exit a Bash interactive login. This behavior has implications when we have multiple Bash sessions/terminals active because the HISTFILE would contain only the contents of the last exiting shell.
However, we can enable the history append option by using the shopt builtin:
$ shopt -s histappend
We can add this command to the ~/.bashrc file to prevent overwriting HISTFILE on shell exit.
4. Syncing History File After Each Command
So far, we have solved the issue of maintaining history files on Bash exit. But, we would like the history across all sessions to be in sync in real-time. To tackle this problem, we need to find a way to update the HISTFILE. We can use the -a option of the history command to append the current session history to the contents of the history file:
$ history -a
This gives us an option to append the session history in HISTFILE on demand. But we would like the append to happen after every command execution. To solve this, we can add the history append option to the PROMPT_COMMAND:
PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND$'\n'}history -a"
Bash examines the value of the variable PROMPT_COMMAND before it prints each primary prompt. We can add the above command to the ~/.bashrc file. This ensures that the value of PROMPT_COMMAND is the same for all the terminals.
5. General Solution
We can now add the below entries to ~/.bashrc to solve our main problem:
$ shopt -s histappend
PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND$'\n'}history -a"
Let’s further optimize the solution:
$ shopt -s histappend
PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND$'\n'}history -a; history -c; history -r"
Here, by adding history -c we ensure that we clear all the history entries kept currently in memory. Later by adding history -r we reload the contents of HISTFILE in memory.
Let’s see this into action:
$ tail -2 .bashrc
shopt -s histappend
PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND$'\n'}history -a; history -c; history -r"
$ pstree
init─┬─init───bash───pstree
├─2*[init───bash]
└─{init}
We verified the contents of the .bashrc file and executed the pstree command from, let’s say, Terminal 1. We have logged on to two other terminals also, as we can verify by the output of the pstree command. Let’s now execute something from another terminal (Terminal 2):
$ echo "This one is from Terminal 2"
This one is from Terminal2
We’ll again switch to a new terminal (Terminal 3):
$ echo "Terminal 3 here.. "
Terminal 3 here..
Now we’ll go back to our initial terminal to verify the history command:
$ echo "Checking from Terminal 1" && history 5
Checking from Terminal 1
8 tail -2 .bashrc
9 pstree
10 echo "This one is from Terminal 2"
11 echo "Terminal 3 here.. "
12 echo "Checking from Terminal 1" && history
As we can see from the output, the history is stored in a sequential fashion and is in sync across all the terminals.
6. Separate History File for Each Terminal
Sometimes we may want to keep a separate log of each Bash session. In that case, we can also create a separate history file for each terminal:
$ export HISTFILE=$HOME/.myhistfile.$$
Since $$ holds the process id of the current shell, hence its will create a separate history file for each session. However, in such cases, the reverse-search of commands or viewing history will only show results for the current session (terminal). This method is best suited for preserving session histories for auditing purposes.
7. Conclusion
In this tutorial, we studied ways to preserve Bash history in multiple terminal windows.
In the beginning, we discussed the history feature of the Bash shell and its default behavior. Next, we discussed the approach and solutions to our problem statement. Finally, we also offered a way to create a separate Bash history file for each session.