All of bash history forever and across multiple sessions

  • strict warning: Non-static method view::load() should not be called statically in /hermes/walnaweb12a/b57/moo.greydragoncom/nodsw/sites/all/modules/views/views.module on line 906.
  • strict warning: Declaration of views_handler_argument::init() should be compatible with views_handler::init(&$view, $options) in /hermes/walnaweb12a/b57/moo.greydragoncom/nodsw/sites/all/modules/views/handlers/ on line 744.
  • strict warning: Declaration of views_handler_filter::options_validate() should be compatible with views_handler::options_validate($form, &$form_state) in /hermes/walnaweb12a/b57/moo.greydragoncom/nodsw/sites/all/modules/views/handlers/ on line 607.
  • strict warning: Declaration of views_handler_filter::options_submit() should be compatible with views_handler::options_submit($form, &$form_state) in /hermes/walnaweb12a/b57/moo.greydragoncom/nodsw/sites/all/modules/views/handlers/ on line 607.
  • strict warning: Declaration of views_handler_filter_boolean_operator::value_validate() should be compatible with views_handler_filter::value_validate($form, &$form_state) in /hermes/walnaweb12a/b57/moo.greydragoncom/nodsw/sites/all/modules/views/handlers/ on line 159.
Leeland's picture

The last terminal to exit wins the "who's history is saved" game. Unless you pull a cute Linux stunt or three.

Update: A better solution is now available All bash history revisited: Load time solved PLUS directory history

Shell history is of major use as a memory jogger, short cut finder, and how did I do that answerer. However, the bash linux shell has one annoying feature and that is as each shell exits it overwrites the .bash_history file with its history buffer contents. Meaning only one shell's session command history is saved.

The simplest solution is to only login once to each machine and to set HISTSIZE to be very large. Problem solved. Now get back to work!!

Except if you happen to open two ssh sessions or open more than one terminal window.

BTW this is all about bash, I know this can be done with ksh because I did it there too. It might be possible in other shells just use this as a spring board to the solution.

The trick is to manipulate the way bash keeps history.

How to do this is basically:

  1. Configure history file location to be unique to each tty/pty
  2. Make the history file size large enough to be interesting
  3. Override the history processing to also record commands to a shared location
  4. On login parse in a subset of the shared history file
  5. On disconnect make sure to grab the last command
  6. Profit

Personally I like to know WHEN I did something as much as what I did. So I set HISTTIMEFORMAT which makes this trick a bit harder (and slower when logging in for the parsing).

I'll leave the analysis of what I am doing to you.

My only issue with this is that it takes about 20 seconds between login and getting a prompt due to the heavy lifting. I have looked to see if I could just do this in one massive Hail Marry pass through awk. But, so far haven't found it. The real bottle neck is the multiple calls to date to translate the dates. You can speed this up A LOT by shifting to just using the date in seconds in the ~/.bash_history_all file. I however, want to be able to grep the ~/.bash_history_all and have the results be meaningful. Hence the need for the date translations. So if you can think of a way to pull the translate back stunt fast without shifting away from clean and sure to work bash with linux commands (a la using Perl or Python or Java or ...) I am all ears.

Here is the code to put into the ${HOME}/.bashrc or ${HOME}/.bashrc.user (sorry for the long lines I didn't want to try to break them up).

There are four files to this solution:

  1. An entry into ${HOME}/.bashrc.user
  2. An awk script in ${HOME}/.bash.parse_history.awk
  3. A sed script in ${HOME}/.bash_history.sed
  4. A bash script append script in ${HOME}/

1. ${HOME}/.bashrc.user

if [[ `tty` != "not a tty" ]] ; then # interactive shell

  # BEG History manipulation section
    export TTY_NAME=`tty|sed -e 's|/dev/||' -e 's|/|_|'`
    export HISTFILESIZE=2000
    export HISTSIZE=2000
    export HISTTIMEFORMAT="[%Y-%m-%d %H:%M:%S] "
    export HISTFILE=$HOME/.bash_history.${TTY_NAME}
    rm -f $HISTFILE

    tail --lines=$(( $HISTFILESIZE + 200 )) ${HOME}/.bash_history_all | sort -u -m -k 4  | tail --lines=$HISTFILESIZE |awk -f ~/.bash.parse_history.awk >> $HISTFILE

    history -r

    if [ -n "$PROMPT_COMMAND" ]; then
        #It is annoying that in PROMPT_COMMAND $HISTCMD is always 1 hence the external script
        export PROMPT_COMMAND="$PROMPT_COMMAND; ${HOME}/ \`history 1\`"
        export PROMPT_COMMAND="${HOME}/ \`history 1\`"

    function save_last_command {
        # Only want to do this once per process
        if [ -z "$SAVE_LAST" ]; then
            export SAVE_LAST="done"
            ${HOME}/ "`history 1`"
            echo "${TTY_NAME} [`date +'%m-%d-%Y_%T'`] # end session $USER@${HOSTNAME}:`tty`" >> ${HOME}/.bash_history_all
            rm -f $HISTFILE
    trap 'save_last_command' EXIT

  # END History manipulation section

2. ${HOME}/.bash.parse_history.awk

#!/usr/bin/awk -f
# Awk script: extract.awk

function extract(str,regexp)
{ RMATCH = (match(str,regexp) ? substr(str,RSTART,RLENGTH) : "")
 #print str " " RSTART " " RLENGTH
 return RSTART }

function after(str,regexp)
{ AMATCH = (match(str,regexp) ? substr(str,RSTART+RLENGTH) : "")
 #print str " " RSTART " " RLENGTH
 return RSTART }

extract($0,"[0-9][0-9]+-[0-9]+-[0-9][0-9]+[_ ][0-9][0-9]:[0-9][0-9]:[0-9][0-9]") {
   gsub(/_/," ", RMATCH)

after($0,"[0-9][0-9]+-[0-9]+-[0-9][0-9]+[_ ][0-9][0-9]:[0-9][0-9]:[0-9][0-9][] ]+") {

 TRANSDATE = "date --date \"" TIME "\" +%s 2>/dev/null"
 #printf ("%s\n#%s\n",CMD,EPOC)
 printf ("#%s\n%s\n",EPOC,CMD)

3. ${HOME}/.bash_history.sed


4. ${HOME}/

#!/usr/bin/env bash


LC=`echo "$*" | sed -f ~/.bash_history.sed | xargs -i -n 1 echo ${TTY_NAME} '{}'`;
if [[ -z `tail -100 ${HISTORY_LOG} | grep -F "$LC" 2>/dev/null` ]] ; then
    echo "${LC}" >> ${HISTORY_LOG}

Do this and all of history goes to ${HOME}/.bash_history_all and looks like this:

$ tail -10 ~/.bash_history_all
pts_0 [2011-10-26 16:29:04] sudo apt-get install `apt-cache search octave | egrep '^octave-' | cut -f1 -d' '`
pts_0 [2011-10-26 16:29:04] octave
pts_0 [2011-10-26 16:29:04] ssh tomcat-vm02
pts_0 [2011-10-26 16:29:04] shutdownvms -a
pts_1 [10-26-2011_17:05:39] grep -r 2000 * --exclude '*.map' --exclude 'uTask*'
pts_4 [2011-10-26 16:29:04] # end session
pts_3 [10-26-2011_17:39:15] # end session
pts_2 [2011-10-26 17:38:36] tail -38 ${HOME}/.bashrc.user
pts_2 [2011-10-26 18:04:01] cat ~/.bash.parse_history.awk
pts_2 [2011-10-26 18:04:30] cat ~/.bash_history.sed 

Finally this also has the nice benefit of being able to leave notes in the history. Just type in a line starting with a hash (#) and it will get saved... Example:

$ # Beginning deployment of production host 3

--- open a new session ----
$ history 3
 5993  [2011-10-26 18:21:40] # end session
 5994  [2011-10-26 18:26:57] # Beginning deployment of production host 3
 5995  [2011-10-26 18:27:04] history 3

Thread Slivers eBook at Amazon