viernes, marzo 01, 2013

"Extended" history in bash


I've found a nice article on http://jeetworks.org/node/80 that I'm going to reproduce here, to have a copy in case the original page ever disappears (I hope he doesn't mind while I include authorship, original link, etc):



Supplementary Command History Logging in Bash: Tracking Working Directory, Date/Times, etc.







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() { # From: https://gist.github.com/jeetsukumaran/2202879/raw/16e92b805cbcf69ca18e5033c0f26bfebb165efe/loghistory.sh
# 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 "stuff"
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 "stuff"
[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
Further Reading

User comments:



I tried added what you



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'


minor modification required



#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\-\: ]*\] }

Thanks for this nice article!


No hay comentarios: