Skip to content

Instantly share code, notes, and snippets.

@gurdiga
Created May 8, 2014 07:01
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save gurdiga/dac8d2e7eb3056d6b839 to your computer and use it in GitHub Desktop.
Save gurdiga/dac8d2e7eb3056d6b839 to your computer and use it in GitHub Desktop.

Per-directory Bash history (w/o aliasing cd)

I use 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.

The source code should be pretty straight forward, but if not, please ask in the comments. Put this in your .bashrc or similar:

# per-directory Bash history
function check_for_local_history {
  local last_command=`history 1`
  local last_command=${last_command:27} # this depends on HISTTIMEFORMAT

  function main {
    if changing_directory; then
      if found_local_history_file; then
        use_history_file $PWD/.bash_history
      else
        use_history_file ~/.bash_history
      fi
    fi
  }

  function changing_directory {
    is_cd_nothing || is_cd_something || is_a_cd_alias
  }

  function is_cd_nothing {
    [ ${#last_command} -eq 2 -a "${last_command:0:2}" = "cd" ]
  }

  function is_cd_something {
    [ ${#last_command} -gt 2 -a "${last_command:0:3}" = "cd " ]
  }

  function is_a_cd_alias {
    local cd_aliases=`alias | grep "='cd " | grep -P -o '[a-z]+(?==)'`
    echo $cd_aliases | grep -q "\<$last_command\>"
  }

  function found_local_history_file {
    [ -e .bash_history ]
  }

  function use_history_file {
    history -w
    history -c
    export HISTFILE=$1
    history -r
  }

  main
}

PROMPT_COMMAND="check_for_local_history"
@gurdiga
Copy link
Author

gurdiga commented Aug 11, 2016

The "production" version is in my .dotfiles repo. 8-)

@MarSoft
Copy link

MarSoft commented Dec 8, 2018

This implementation will unnecessarily re-read "global" history file ~/.bash_history every time you change between non-local-history directories.
Also it won't detect commands like mydir which is equivalent to cd mydir when autocd is active, or recursive aliases, or other non-trivial things.
Hence I think the better way is to remember $PWD in a global variable and re-check it every time, and also don't do history re-reading stuff if new desired $HISTFILE did not change (even when the directory changed).

Here is my implementation, heavily inspired by yours:

_history_last_dir=$PWD
_history_last_file=$HISTFILE

check_for_local_history() {
	# we are executed in PROMPT_COMMAND, after previous command did its action -
	# so PWD is now the new one if the previous command was `cd` or changed directory in other way

	# if dir did not change then do nothing
	[ "$_history_last_dir" == "$PWD" ] && return
	_history_last_dir=$PWD

	# current dir changed, but what about the file to use?
	local new_histfile
	if [ -e .bash_history ]; then
		new_histfile=$PWD/.bash_history
	else
		new_histfile=$HOME/.bash_history
	fi
	[ "$new_histfile" == "$_history_last_file" ] && return
	_history_last_file=$new_histfile

	# desired history file changed, so do our work:
	# write down current history (to old file)
	history -w
	# and clear in-memory history
	history -c
	# now switch to the new file
	if [ -e .bash_history ]; then
		export HISTFILE=$PWD/.bash_history
	else
		export HISTFILE=$HOME/.bash_history
	fi
	# and read history from that new file
	history -r
}

export PROMPT_COMMAND="${PROMPT_COMMAND}${PROMPT_COMMAND:+; }check_for_local_history"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment