Supplementary Command History Logging in Bash: Tracking Working Directory, Date/Times, etc.
Introduction
Here is a way to create a secondary shell history log (i.e., one that supplements the primary "~/.bash_history") that tracks a range of other information, such as the working directory, hostname, time and date etc. Using the "HISTTIMEFORMAT" variable, it is in fact possible to store the time and date with the primary history, but the storing of the other information is not as readibly do-able. Here, I present an approach based on this excellent post on StackOverflow.
The main differences between this approach and the original is:
- I remove the option to log the extra information to the primary history file: I prefer to keep this history clean.
- I add history number, host name, time/date stamp etc. to the supplementary history log by default.
- I add field separators, making it easy to apply '
awk' commands.
The (Supplementary) History Logger Function
First, add or source the following to your "~/.bashrc":
_loghistory() { # Detailed history log of shell activities, including time stamps, working directory etc. # # Based on 'hcmnt' by Dennis Williamson - 2009-06-05 - updated 2009-06-19 # (http://stackoverflow.com/questions/945288/saving-current-directory-to-bash-history) # # Add this function to your '~/.bashrc': # # Set the bash variable PROMPT_COMMAND to the name of this function and include # these options: # # e - add the output of an extra command contained in the histentrycmdextra variable # h - add the hostname # y - add the terminal device (tty) # n - don't add the directory # t - add the from and to directories for cd commands # l - path to the log file (default = $HOME/.bash_log) # ext or a variable # # See bottom of this function for examples. # # make sure this is not changed elsewhere in '.bashrc'; # if it is, you have to update the reg-ex's below export HISTTIMEFORMAT="[%F %T] ~~~ " local script=$FUNCNAME local histentrycmd= local cwd= local extra= local text= local logfile="$HOME/.bash_log" local hostname= local histentry= local histleader= local datetimestamp= local histlinenum= local options=":hyntel:" local option= OPTIND=1 local usage="Usage: $script [-h] [-y] [-n|-t] [-e] [text] [-l logfile]" local ExtraOpt= local NoneOpt= local ToOpt= local tty= local ip= # *** process options to set flags *** while getopts $options option do case $option in h ) hostname=$HOSTNAME;; y ) tty=$(tty);; n ) if [[ $ToOpt ]] then echo "$script: can't include both -n and -t." echo $usage return 1 else NoneOpt=1 # don't include path fi;; t ) if [[ $NoneOpt ]] then echo "$script: can't include both -n and -t." echo $usage return 1 else ToOpt=1 # cd shows "from -> to" fi;; e ) ExtraOpt=1;; # include histentrycmdextra l ) logfile=$OPTARG;; : ) echo "$script: missing filename: -$OPTARG." echo $usage return 1;; * ) echo "$script: invalid option: -$OPTARG." echo $usage return 1;; esac done text=($@) # arguments after the options are saved to add to the comment text="${text[*]:$OPTIND - 1:${#text[*]}}" # add the previous command(s) to the history file immediately # so that the history file is in sync across multiple shell sessions history -a # grab the most recent command from the command history histentry=$(history 1) # parse it out histleader=`expr "$histentry" : ' *\([0-9]* \[[0-9]*-[0-9]*-[0-9]* [0-9]*:[0-9]*:[0-9]*\]\)'` histlinenum=`expr "$histleader" : ' *\([0-9]* \)'` datetimestamp=`expr "$histleader" : '.*\(\[[0-9]*-[0-9]*-[0-9]* [0-9]*:[0-9]*:[0-9]*\]\)'` histentrycmd=${histentry#*~~~ } # protect against relogging previous command # if all that was actually entered by the user # was a (no-op) blank line if [[ -z $__PREV_HISTLINE || -z $__PREV_HISTCMD ]] then # new shell; initialize variables for next command export __PREV_HISTLINE=$histlinenum export __PREV_HISTCMD=$histentrycmd return elif [[ $histlinenum == $__PREV_HISTLINE && $histentrycmd == $__PREV_HISTCMD ]] then # no new command was actually entered return else # new command entered; store for next comparison export __PREV_HISTLINE=$histlinenum export __PREV_HISTCMD=$histentrycmd fi if [[ -z $NoneOpt ]] # are we adding the directory? then if [[ ${histentrycmd%% *} == "cd" || ${histentrycmd%% *} == "jd" ]] # if it's a cd command, we want the old directory then # so the comment matches other commands "where *were* you when this was done?" if [[ -z $OLDPWD ]] then OLDPWD="$HOME" fi if [[ $ToOpt ]] then cwd="$OLDPWD -> $PWD" # show "from -> to" for cd else cwd=$OLDPWD # just show "from" fi else cwd=$PWD # it's not a cd, so just show where we are fi fi if [[ $ExtraOpt && $histentrycmdextra ]] # do we want a little something extra? then extra=$(eval "$histentrycmdextra") fi # strip off the old ### comment if there was one so they don't accumulate # then build the string (if text or extra aren't empty, add them with some decoration) histentrycmd="${datetimestamp} ${text:+[$text] }${tty:+[$tty] }${ip:+[$ip] }${extra:+[$extra] }~~~ ${hostname:+$hostname:}$cwd ~~~ ${histentrycmd# * ~~~ }" # save the entry in a logfile echo "$histentrycmd" >> $logfile || echo "$script: file error." ; return 1 } # END FUNCTION _loghistory
Activating the Logger
Then you need to set this function to execute on every command by adding it to your "$PROMPT_COMMAND" variable, so you need the following entry in your "~/.bashrc":
export PROMPT_COMMAND='_loghistory'
There are a number of options that the logging function takes, including the adding terminal information, the adding of arbitrary text or the execution of a function or function(s) that generate appropriate text. See the function documentation for more info.
Add Some Useful Aliases
Add the following to your "~/.bashrc":
# dump regular history log alias h='history' # dump enhanced history log alias hh="cat $HOME/.bash_log" # dump history of directories visited alias histdirs="cat $HOME/.bash_log | awk -F ' ~~~ ' '{print $2}' | uniq"
Checkout the Results! The 'histdirs' command is very useful to quickly list, select (via copy and pasting) and jumping back to a directory.
$ h 14095 [2011-11-23 15:36:20] ~~~ jd nuim 14096 [2011-11-23 15:36:21] ~~~ ll 14097 [2011-11-23 15:36:23] ~~~ git status 14098 [2011-11-23 15:36:33] ~~~ jd pytb 14099 [2011-11-23 15:36:36] ~~~ git status 14100 [2011-11-23 15:36:53] ~~~ git rm --cached config/* 14101 [2011-11-23 15:37:00] ~~~ git pull 14102 [2011-11-23 15:37:11] ~~~ e .gitignore 14103 [2011-11-23 15:37:28] ~~~ git status 14104 [2011-11-23 15:37:35] ~~~ e .gitignore 14105 [2011-11-23 15:37:44] ~~~ git status 14106 [2011-11-23 15:38:10] ~~~ git commit -a -m "removed tracking of libtool specific files" 14107 [2011-11-23 15:38:12] ~~~ git pushall 14108 [2011-11-23 15:50:38] ~~~ ll build_c/ 14109 [2011-11-23 15:53:16] ~~~ cd 14110 [2011-11-23 15:53:18] ~~~ ls -l 14111 [2011-11-23 16:00:12] ~~~ cd Documents/Projects/Phyloinformatics/DendroPy/dendropy 14112 [2011-11-23 16:00:15] ~~~ ls -l 14113 [2011-11-23 16:00:22] ~~~ cd dendropy/ 14114 [2011-11-23 16:00:24] ~~~ vim *.py $ hh [2011-11-23 15:36:20] ~~~ /Users/jeet ~~~ jd nuim [2011-11-23 15:36:21] ~~~ /Users/jeet/Documents/Projects/Phyloinformatics/nuim ~~~ ll [2011-11-23 15:36:23] ~~~ /Users/jeet/Documents/Projects/Phyloinformatics/nuim ~~~ git status [2011-11-23 15:36:33] ~~~ /Users/jeet/Documents/Projects/Phyloinformatics/nuim ~~~ jd pytb [2011-11-23 15:36:36] ~~~ /Users/jeet/Documents/Projects/Phyloinformatics/pytbeaglehon ~~~ git status [2011-11-23 15:36:53] ~~~ /Users/jeet/Documents/Projects/Phyloinformatics/pytbeaglehon ~~~ git rm --cached config/* [2011-11-23 15:37:00] ~~~ /Users/jeet/Documents/Projects/Phyloinformatics/pytbeaglehon ~~~ git pull [2011-11-23 15:37:11] ~~~ /Users/jeet/Documents/Projects/Phyloinformatics/pytbeaglehon ~~~ e .gitignore [2011-11-23 15:37:28] ~~~ /Users/jeet/Documents/Projects/Phyloinformatics/pytbeaglehon ~~~ git status [2011-11-23 15:37:35] ~~~ /Users/jeet/Documents/Projects/Phyloinformatics/pytbeaglehon ~~~ e .gitignore [2011-11-23 15:37:44] ~~~ /Users/jeet/Documents/Projects/Phyloinformatics/pytbeaglehon ~~~ git status [2011-11-23 15:38:10] ~~~ /Users/jeet/Documents/Projects/Phyloinformatics/pytbeaglehon ~~~ git commit -a -m "removed tracking of libtool specific files" [2011-11-23 15:38:12] ~~~ /Users/jeet/Documents/Projects/Phyloinformatics/pytbeaglehon ~~~ git pushall [2011-11-23 15:50:38] ~~~ /Users/jeet/Documents/Projects/Phyloinformatics/pytbeaglehon ~~~ ll build_c/ [2011-11-23 15:53:16] ~~~ /Users/jeet/Documents/Projects/Phyloinformatics/pytbeaglehon ~~~ cd [2011-11-23 15:53:18] ~~~ /Users/jeet ~~~ ls -l [2011-11-23 16:00:12] ~~~ /Users/jeet ~~~ cd Documents/Projects/Phyloinformatics/DendroPy/dendropy [2011-11-23 16:00:15] ~~~ /Users/jeet/Documents/Projects/Phyloinformatics/DendroPy/dendropy ~~~ ls -l [2011-11-23 16:00:22] ~~~ /Users/jeet/Documents/Projects/Phyloinformatics/DendroPy/dendropy ~~~ cd dendropy/ [2011-11-23 16:00:24] ~~~ /Users/jeet/Documents/Projects/Phyloinformatics/DendroPy/dendropy/dendropy ~~~ vim *.py $ histdirs /Users/jeet /Users/jeet/Documents/Projects/Phyloinformatics/nuim /Users/jeet/Documents/Projects/Phyloinformatics/pytbeaglehon /Users/jeet /Users/jeet/Documents/Projects/Phyloinformatics/DendroPy/dendropy /Users/jeet/Documents/Projects/Phyloinformatics/DendroPy/dendropy/dendropy
feed
Comments
3 comments posted#need to allow spaces before the nubmers in bash history
histleader=`expr "$histentry" : ' *\([0-9]* \[[0-9]*-[0-9]*-[0-9]* [0-9]*:[0-9]*:[0-9]*\]\)'`
histentrycmd=${histentry# *[0-9]* \[[0-9\-\: ]*\] }
I tried added what you suggested above and it does not seem to log to the default log file. Doesn't seem like anything is even logging. Even tried adding the location of the logfile and still nothing.
export PROMPT_COMMAND='_loghistory -e -h -y -t'
And it works for me. Have you tried just:
export PROMPT_COMMAND=_loghistory
And are you sure that the above command is being sourced into your current shell AND you have your history activated?
Post new comment