10

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?

  • 3
    Why not redefine cd, pushd, and popd? How many other ways are there to change directory? – jw013 Dec 6 '14 at 5:02
  • @jw013 this code is intended to go into an open source project where the maintainer has specifically listed not redefining cd as a principle. – Nathan Long Dec 6 '14 at 12:40
  • Also - "how many other ways are there to change directory" - I don't know, which is another reason I'd prefer not to rely on listing them explicitly. – Nathan Long Dec 8 '14 at 14:15
  • Why do you want to do this? Your function might brake a lot of programs (C, Java, ..). In scripts I use MYBIN=$( cd -P -- "$(dirname -- "$(command -v -- "$0")")" && pwd -P ) Please do not change the trusted Unix commands. – Walter A Dec 8 '14 at 14:23
  • 1
    @NathanLong that's only for my operating system. Operating system seems irrelevant here. Does OS matter? It's the shell that matters in your question, and you seem to be specifically asking about bash, which works pretty much the same on all operating systems that it runs on. – jw013 Dec 8 '14 at 18:01
2

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

1
+100

If the maintainer doesn't like you changing the definition for cd, another option would be to redefine all the commands which use this in-directory environment:

for c in cmd1 cmd2 cmd3 cmd4 cmd5 ; do
    eval "$c() { check_pwd ; command $c \"\$@\" ; }"
done

This would be quite efficient because the PWD hook and in-directory config would only be processed when required.

In your example check_pwd function, I might change:

my_function

to:

my_function "$PWD"

in order to pass in the new cwd (might be more modular, testable).

  • "redefine all the commands which use this in-directory environment" Unfortunately, I can't predict that. – Nathan Long Dec 8 '14 at 18:46
0

Install inotify-tools according to your distribution.

inotifywait -emodify,create,delete -m /path/to/directory | while read line; do service httpd reload; done

In my example the following command will restart httpd if anything changes (Modify,Create,Delete) in the specified directory.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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