Bash – How to run a command in bash after any change in $PWD

bashhookpwd

zsh provides some nice hook functions, including chpwd for running a function after the user changes directories.

# zsh only
function greet() { echo 'hi'; }
chpwd_functions+=("greet")
cd .. # hi
pushd # hi
popd  # hi

I'm trying to emulate that in bash.

Constraints:

  • It must work in both interactive and non-interactive shells, which I think means that it can't rely on something like $PROMPT_COMMAND
  • It can't redefine cd, because I want it to work for any command that changes directories (eg, pushd and popd)
  • It must run after the user's command, so trap "my_function" DEBUG doesn't work, unless I can somehow say in there, "first run the $BASH_COMMAND we trapped, then also do this…" I see that I can avoid the automatic running of $BASH_COMMAND if extdebug is enabled and the trap function returns 1, but I don't think I want to force extdebug, and returning 1 for a successful (but modified) command seems wrong.

The last part – "run after the user's command" – is what currently has me stumped. If I can run a function after each command, I can have it check whether the directory has changed since we last checked. Eg:

function check_pwd() {
  # true in a new shell (empty var) or after cd
  if [ "$LAST_CHECKED_DIR" != "$PWD" ]; then
    my_function
  fi
  LAST_CHECKED_DIR=$PWD
}

Am I on the right track, or is there a better way? How can I run a command in bash after the user changes directories?

Best Answer

There's no way that meets these constraints

It looks like there's no way to solve this problem in bash with my constraints. In general, possible solutions are:

  • Override cd, pushd, and popd, so that any command that changes directories will first run the hook function. But this can create problems because 1) the override must be careful to tab complete like the original command and return the same exit code and 2) if more than one tool takes this approach, they can't play well together
  • Override all commands that may be run with the environment changes to first run the hook function. This is hard because there are many such commands
  • trap 'my_function' DEBUG so that every command will run the hook function. This is suboptimal because 1) it runs before every command, 2) it runs before cd, not after 3) there can only be one debug function, so if another tool uses this approach, they can't play well together
  • Redefine $PROMPT_COMMAND to run the hook function first. This is suboptimal because it won't work in non-interactive shells, and because if another tool defines the prompt command, they can't play well together.

In short it seems like the only great solution would be if bash provided something like zshell's chpwd_functions hook, but it seems impossible to simulate that properly.

Related Question