Take the 2-minute tour ×
Super User is a question and answer site for computer enthusiasts and power users. It's 100% free, no registration required.

I find I do a lot of work on a project in a set directory. Sometimes — a few months down the track — I need to redo something on that project but I can't remember what I did. I use mercurial or git to track changes to files but I want to be able to remember what commands I issued in that directory.

Searching through my shell history is not very informative. I already log everything to my .*_history files, but I want a list of things I did in ~/foo/bar, and not all the other (million) things that I did that week. I probably can't even remember what month I last worked on that particular project.

Does anyone have any ideas how a project directory log file of all shell commands I've used? I'm envisioning a command something like:

workon myproject

... which would set the shell log file to ~/myproject/.history.log, load the previous history from that logfile, and maybe update my prompt to tell me what directory I'm working on (like e.g. vcprompt to provide version control info).

Is there anything out there like this?

share|improve this question
add comment

7 Answers

One trick I use for framework of building sub-packages for a product is to use a sub-shell.

For bash you could create a shell script like this:

#!/bin/bash

export PROJECT_DIRECTORY=$(pwd)

exec bash --rcfile $HOME/.project-bashrc

Then in $HOME/.project-bashrc you put something like this:

source $HOME/.bashrc
export HISTFILE="${PROJECT_DIRECTORY}/.bash_history"
cd "${PROJECT_DIRECTORY}"

This also lets you customize the prompt in the .project-bashrc file, which can be handy.

I'm not sure how to do the same in zsh. You'd have to override the ZDOTDIR variable, I think. But it'd look similar.

Ciao!

share|improve this answer
add comment

In case you haven't figure this out yet: what you are looking for is the excellent virtualenvwrapper package. It is a wrapper around python's virtualenv (go figure) and, while it is commonly referred to when taking about python environments, it is actually a very generic tool that satisfies your use case.

Installation

pip install virtualenvwrapper

or

easy_install virtualenvwrapper

and add initialisation stuff into your shell config (~/.zshrc, ~/.bashrc)

export WORKON_HOME=$HOME/.virtualenvs
export PROJECT_HOME=$WORKON_HOME/projects
export PIP_VIRTUALENV_BASE=$WORKON_HOME
export PIP_RESPECT_VIRTUALENV=true
source /usr/local/bin/virtualenvwrapper.sh

Usage

# create your env
mkvirtualenv my_project

# edit per project hooks
vim ~/.virtualenvs/my_project/bin/{postactivate,preactivate,predeactivate,etc}

# work in this env
workon my_project

You also have generic hooks in ~/.virtualenvs/{postactivate,postdeactivate,etc} that are called every time you workon any_project.

So, for example, having the line export HISTFILE="$VIRTUAL_ENV/bash_history" in the ~/virtualenvs/postactivate hook means that the $HISTFILE variable will be expanded every time to a different project.

share|improve this answer
add comment

Take a look at my logging scripts here. Using one of them, you can keep track of what directory you're in when you issue each command. You can grep the log file for the command or other information. I use the long version at home and at work.

share|improve this answer
    
Thanks - I'll have a look into this. Might do something close to what I need! –  Simon Jan 14 '12 at 7:26
add comment

In bash at least, HISTFILE is only consulted at start of shell instance. The per-dir idea won't work here, unless your 'workon' example above creates a shell instance.

Maybe you can look at something like

alias workon='script ./.history.log'

But script also creates a subshell.

In short, you'll probably need messy levels of subshells to get this to work.

share|improve this answer
    
Damn, I was hoping it'd be something simple. Thanks anyway! –  Simon Jan 14 '12 at 7:26
add comment

I've worked in production software houses where we'd just create new users and groups for various functions. New user accounts specifically for configuration management or software builds with varying levels of visibility into other related functional areas via group membership and ACL, and the command history performed by say, cmmgr would be saved in ~cmmgr/.bash_history, bldmgr related word would be saved in ~bldmgr/.bash_history, etc. Unfortunately, to login, a user must own their login directory. Therefore, a project area was set up on a separate disk altogether. File creator's ownership showed which functional area created the files in the project area, for which the appropriate .bash_history may be examined.

The above approach, however, doesn't give you the granularity that you desire, however, it gives you a skeleton that when combined with group membership, a user could switch groups with newgrp which effectively creates a new shell and environment, and then use one of the approaches given in the other answers to alter the effective ~/.bash_history file when switching hats to say, cmmgr so that a newgrp could manage which ~/.bash_history is used and saved per folder as one enters and exits the new group via the newgrp command. Check out man newgrp. There are some starting off points along these lines in other answers to this question. These should work with the UNIX group paradigm - most of the nuts and bolts are handled by the shell startup and exit routines as controlled by the newgrp invocation. Check out newgrp -l:

NAME
     newgrp -- change to a new group

SYNOPSIS
     newgrp [-l] [group]

DESCRIPTION
     The newgrp utility creates a new shell execution environment with modified real and effective group
     IDs.

     The options are as follows:

     -l      Simulate a full login.  The environment and umask are set to what would be expected if the user
             actually logged in again.
share|improve this answer
add comment

Here I present two variants, but applicable only to the Z Shell.

Variant 1

This is what I first thought of when reading your question title. With this variant you can switch between two history modes with ALT+h: Global or local, where the latter switches to a per-directory history automatically upon chdir. The global history accumulates all issued commands.

  • Demonstration

    ~ source src/cd_history
    ~ echo global history                                                
    global history
    ~ mkdir foo bar                                                      
    ~ [ALT-h]
    ~                         note the indicator for local history ->   +
    ~ cd foo                                                            +
    ~/foo echo local history in foo                                     +
    local history in foo
    ~/foo fc -l                                                         +
        1  echo local history in foo
    ~/foo cd ../bar                                                     +
    ~/bar echo local history in bar                                     +
    local history in bar
    ~/bar fc -l                                                         +
        1  echo local history in bar
    ~/bar cd ../foo                                                     +
    ~/foo fc -l                                                         +
        1  echo local history in foo
        3  cd ../bar
    ~/foo [ALT-h]                                           
    ~/foo fc -l                                                          
       55  source src/cd_history
       64  echo global history
       65  mkdir foo bar
       66  cd foo
       70  echo local history in bar
       72  cd ../foo
       73  echo local history in foo
       74  cd ../bar
    ~/foo  
  • The script (to be included in ~/.zshrc or to be sourced)

    # set options for shared history
    setopt prompt_subst
    setopt share_history
    setopt hist_ignorealldups
    
    # define right prompt as an indicator if local (i.e. per directory) history is enabled
    RPS1=' ${HISTLOC}'
    export HISTLOC=''
    
    # configure global history file and global/local history size
    export HISTGLOBAL=$HOME/.zsh_history
    touch $HISTGLOBAL
    export HISTSIZE=2000
    export SAVEHIST=2000
    
    # define wrapper function and key binding to switch between globel and per-dir history
    function my-local-history()
    {
      if [[ -z $HISTLOC ]]; then
        HISTLOC='+'
        chpwd
      else
        HISTLOC=''
        export HISTFILE=$HISTGLOBAL
        fc -A $HISTFILE
        fc -p $HISTFILE $HISTSIZE $SAVEHIST
      fi
      zle reset-prompt
    }
    zle -N my-local-history
    bindkey "^[h"    my-local-history
    
    # install hook function which is called upon every directory change
    chpwd () {
      if [[ ! -z $HISTLOC ]]; then
        fc -A $HISTGLOBAL $HISTSIZE $SAVEHIST
        export HISTFILE=$PWD/.zsh_history
        fc -p $HISTFILE $HISTSIZE $SAVEHIST
        touch $HISTFILE
      fi 
    }
    

Variant 2

Thinking over it again, it seems that per-dir history is too fine grained and you also describe more a per-project history in your question body. So I came up with another variant with a workon function to switch between projects. Every project has its own history file ~/.zsh_projhistory_[name].

  • Demonstration

    ~ source src/proj_history 
    ~ echo global history                                                                    
    global history
    ~ [ALT-h]                                                              [use workon]
    ~ workon foo                                                           [use workon]
    ~ echo command in project foo                                         [on proj foo]
    command in project foo
    ~ fc -l                                                               [on proj foo]
        1  echo command in project foo
    ~ workon bar                                                          [on proj foo]
    ~ echo another proj, named bar                                        [on proj bar]
    another proj, named bar
    ~ fc -l                                                               [on proj bar]
        1  echo another proj, named bar
    ~ workon foo                                                          [on proj bar]
    ~ fc -l                                                               [on proj foo]
        1  echo command in project foo
        3  workon bar
    ~ [ALT-h]                                                             [on proj foo]
    ~
    ~ fc -l                                                                                   
       31  echo global history
       36  echo another proj, named bar
       38  workon foo
       39  echo command in project foo
       40  workon bar
    ~ ls -1 .zsh_*
    .zsh_history
    .zsh_projhistory_bar
    .zsh_projhistory_foo
  • The script (to be included in ~/.zshrc or to be sourced)

    # set options for shared history
    setopt prompt_subst
    setopt share_history
    setopt hist_ignorealldups
    
    # define right prompt as an indicator if local (i.e. per directory) history is enabled
    RPS1=' ${HISTLOC}'
    export HISTLOC=''
    
    # configure global history file and global/local history size
    export HISTGLOBAL=$HOME/.zsh_history
    touch $HISTGLOBAL
    export HISTFILE=$HISTGLOBAL
    export HISTSIZE=2000
    export SAVEHIST=2000
    
    # define wrapper function and key binding to switch between globel and per-dir history
    function my-local-history()
    {
      if [[ -z $HISTLOC ]]; then
        if [[ -z $HISTLOC ]]; then
          HISTLOC='+'
          [[ -z "$HISTPROJ" ]] && HISTLOC='[use workon]' || workon "$HISTPROJ"
        fi
      else
        HISTLOC=''
        export HISTFILE=$HISTGLOBAL
        fc -A $HISTFILE
        fc -p $HISTFILE $HISTSIZE $SAVEHIST
      fi
      zle reset-prompt
    }
    zle -N my-local-history
    bindkey "^[h"    my-local-history
    
    # function to change project
    workon () {
      if [[ -z "$1" ]]; then
        echo Usage: workon [project name]
        return 1
      fi
      export HISTPROJ="$1"
      if [[ ! -z $HISTLOC ]]; then
        fc -A $HISTGLOBAL $HISTSIZE $SAVEHIST
        export HISTFILE=$HOME/.zsh_projhistory_"$HISTPROJ"
        fc -p "$HISTFILE" $HISTSIZE $SAVEHIST
        touch "$HISTFILE"
        HISTLOC="[on proj $HISTPROJ]"
      fi 
    }
    
share|improve this answer
add comment

You might be interested in a plugin I wrote called directory-history for zsh.
Look here: https://github.com/tymm/directory-history

Although it doesn't really fit your workon myproject workflow, it should perfectly fit your needs.

It creates a history which includes directories.
When you search through your history, you will first get commands from the directory you are in.
If there are no commands left for that directory it will fall back to a global history and suggest commands used in other directories.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.