Zrajm's History Hack

This is an attempt at implementing the history function somewhat differently (and with a greater degree of control). There are a few reasons for this:


  • I noticed that the <elapsed seconds> of EXTENDED_HISTORY is always set to zero when INC_APPEND_HISTORY or SHARED_HISTORY is set. (I would like to have them both, and would be perfectly content with having each command line written to the history when execution finishes, rather that when it begins.)
  • I would like to re-write forced redirects like >|, >!, »| and »! into more harmless non-forced redirects before placing them in the history. More than once have I clumsily clobbered a file when I only intended to append stuff.
  • It would also be nice to not save some particularly dangerous commands ("rm *" comes to mind) to avoid triggering them by mistake. Maybe one could have a list of commands, which, if they're given a glob as argument should not be stored in the history (or be stored with their globs expanded, since that makes them quite a bit more harmless).
  • Also, if it is possible, I would like to save the exit status of each program in my history as well…
  • I wound like to rotate my zsh history based on the time elapsed, so I could keep a week or a month's backlog, rather than a fixed number of commands. – I'd just have zsh adding stuff, and a cronjob trimming off the oldest bits once nightly…
  • …of course, I started small, and tried to implement only the first of these items…


Sooo… I wrote a little thingy using the precmd() and preexec() functions which I thought would be able to do this. Well. I can tell you straight away. THE BELOW STUFF DOESN'T WORK. Well, actually, writing to the history works just fine, but calling fc -RI from precmd() (to import any changes to made to the history into the current shell) fucks up things pretty badly. On the command line the command works just fine, but when run from precmd() it causes some strange things to happen (it empties the history completely, and causes preexec() to run with an empty first argument [which is kinda bad, since it is that first argument that contains the actual command line to save in the history] – yeah, you read it right, the trouble it causes is not in the same function it was called from…).

If anyone knows a way to use fc from within precmd(), or any other way to read stuff from the history file into the history buffer of the current zsh, don't hesitate to contact me (Zrajm). I'm interested. Other opinions (such as alternate ways of accomplishing the above things) are of course also welcome.

setopt NO_SHARE_HISTORY   
setopt NO_EXTENDED_HISTORY
setopt NO_INC_APPEND_HISTORY
  
HISTSIZE=2000                                  # this zsh's history size
SAVEHIST=0                                     # never touch HISTFILE
HISTFILE=~/.zshhistory                         # history file name
zmodload zsh/datetime                          # needed for EPOCHSECONDS


# before showing prompt        
precmd() {                     
    if (( LASTTIME > 0 )); then
        # create a HISTLINE with start time and duration
        # (same as zsh's EXTENDED_HISTORY format)
        local HISTLINE=": $LASTTIME:$(($EPOCHSECONDS-$LASTTIME));$LASTCMD"

        # carefully append HISTLINE to HISTFILE             
        # (using $HISTFILE.lock as a lockfile)              
        local FILE="$HISTFILE"                 # set outfile       
        if [[ -e "$FILE.lock" ]]; then         # if lockfile exists
            FILE="$FILE.tmp"                   #   use tempfile as outfile
        elif [[ -e "$FILE.tmp" ]]; then        # no lockfile, but tempfile
            mv "$FILE.tmp" "$FILE.$$" &&       #   hide tempfile from other
               < "$FILE.$$" >>! "$FILE" &&     #   append it to outfile
               rm "$FILE.$$"                   #   and delete tempfile
        fi                                     #
        echo -E "$HISTLINE" >>! "$FILE"        # append last cmd to outfile

        # if VERBOSE
        if [[ -n $VERBOSE ]] then              # talk a bit
            echo -n '\e[31m'                   #   in red
            echo "LASTTIME=$LASTTIME"          #
            echo "LASTCMD=$LASTCMD"            #
            echo "FILE=$FILE"                  #
            echo -E "$HISTLINE"                #
            echo -n '\e[39m'                   #
        fi                                     #
        unset LASTCMD LASTTIME                 #
    fi                                         #

    # allow history sharing between shells
    # BUGGY! Does not work!
    #fc -RI                                     # read new parts of HISTFILE
}

# before running command                                              
preexec() {                                                           
    LASTCMD="${(pj:\\\n:)${(f)1}}"             # remember command line
    LASTTIME="$EPOCHSECONDS"                   #   and time of execution

    # if VERBOSE
    if [[ -n $VERBOSE ]] then                  # talk a bit
        echo -n '\e[34m'                       #   in red
        local i                                #
        echo "arguments to preexec():"         #   lists preexec's argumens
        for ((i=1; i<=ARGC; i++)) {            #
            echo "    $i: $argv[i]"            #
        }                                      #
        echo "LASTTIME=$LASTTIME"              #
        echo "LASTCMD=$LASTCMD"                #
        echo "FILE=$HISTFILE"                  #
        echo -n '\e[39m'                       #
    fi                                         #
}                                              #
 
examples/zhisthack.txt · Last modified: 2010/01/05 09:20 (external edit)