
The last terminal to exit wins the "who's history is saved" game. Unless you pull a cute Linux stunt or three.
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:
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:
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}/.bash_history_all_append.sh \`history 1\`"
else
export PROMPT_COMMAND="${HOME}/.bash_history_all_append.sh \`history 1\`"
fi
function save_last_command {
# Only want to do this once per process
if [ -z "$SAVE_LAST" ]; then
export SAVE_LAST="done"
${HOME}/.bash_history_all_append.sh "`history 1`"
echo "${TTY_NAME} [`date +'%m-%d-%Y_%T'`] # end session $USER@${HOSTNAME}:`tty`" >> ${HOME}/.bash_history_all
rm -f $HISTFILE
fi
}
trap 'save_last_command' EXIT
# END History manipulation section
#################################fi
#!/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)
TIME = 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][] ]+") {
CMD = AMATCH
}
{
#printf("TIME=%s,CMD=%s\n",TIME,CMD)
TRANSDATE = "date --date \"" TIME "\" +%s 2>/dev/null"
TRANSDATE | getline EPOC
#printf ("%s\n#%s\n",CMD,EPOC)
printf ("#%s\n%s\n",EPOC,CMD)
}
s/^\s*[[:digit:]]*\s*// s/\\/\\\\/g s/"/\\"/g s/'/\\'/g
#!/usr/bin/env bash
HISTORY_LOG=${HOME}/.bash_history_all
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}
fi
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 lartra@dtvm01.recondynamics.com:/dev/pts/4
pts_3 [10-26-2011_17:39:15] # end session lartra@dtvm01.recondynamics.com:/dev/pts/3
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 lartra@dtvm01.recondynamics.com:/dev/pts/2 5994 [2011-10-26 18:26:57] # Beginning deployment of production host 3 5995 [2011-10-26 18:27:04] history 3
Comments
Post new comment