17

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?

12 Answers 12

5

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.

4

I need this too and I have come up with a version that uses Bash’s PROMPT_COMMAND variable:

The value of the variable PROMPT_COMMAND is examined just before Bash prints each primary prompt. If PROMPT_COMMAND is set and has a non-null value, then the value is executed just as if it had been typed on the command line.

So I say PROMPT_COMMAND="check_for_local_history" in ~/.bashrc.my, where check_for_local_history is a function of mine that checks if the last executed command was a directory change, and when that’s true, it checks the new current directory for the .bash_history file. If it’s there, use it as the history file.

Here is the complete gist: https://gist.github.com/gurdiga/dac8d2e7eb3056d6b839

3

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!

2

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.

2

To get a local history file for a directory I added the following line to my bash prompt command.

if [ -f .local_history ] ; then tail -1 $HISTFILE >> .local_history ; fi

The command touch .local_history in a directory then gives me a local history file for all commands executed in that directory, without loosing the main history file. Something similar would work in zsh I guess.

1

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.

1
  • Thanks - I'll have a look into this. Might do something close to what I need!
    – Simon
    Jan 14, 2012 at 7:26
1

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.
1

If you want something simple you could have your .history.log file as such:

#!/bin/cat
some
relevant
commands

Then running the file would cat those commands you listed in it.
You could also group those commands in separate files by functionality as convention:

.howto.datadump
.howto.restart

This has the added advantage of working out of the box and not polluting your environment.

1
  • Alternatively you can make use of README.md or create other .md files per topic and point to them from the readme. That's if you have a markdown friendly source control interface.
    – Reno
    Apr 7, 2016 at 13:47
1

There is a shell history extension that allows you to search history and shows you relevant results first based on the current directory, git repository, exit status, and host.

You can install the project here: https://github.com/curusarn/resh
Launch it using CTRL+R (replaces reverse search).
This is how the search application looks like:

screenshot of the search application

Disclaimer: I'm the creator of the project and this is shame-less self-promotion

1
  • does this work well with fzf? I use fzf for searching through commands usually
    – baxx
    Sep 16, 2020 at 13:31
0

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.

1
  • Damn, I was hoping it'd be something simple. Thanks anyway!
    – Simon
    Jan 14, 2012 at 7:26
0

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 
    }
    
0

After working in one area for awhile.

history >hist1.txt then later history >hist2.txt

Sometimes I use the date for the filename. history >hist20180727.txt

This way there is a recent command history for each directory.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .