12

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?

Nathan Long
  • 1,613
  • 1
  • 13
  • 27
  • 3
    Why not redefine `cd`, `pushd`, and `popd`? How many other ways are there to change directory? – jw013 Dec 06 '14 at 05: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 06 '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 08 '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 08 '14 at 14:23
  • @WalterA Why? I'm trying to improve an existing, widely-used tool that modifies the environment based on in-directory configuration. It currently uses `trap...DEBUG` to do this before every bash command (and `preexec` in zsh). That's both wasteful, as it need only run when changing directories, and inaccurate, because running *before* a `cd` means it runs in the wrong directory. It's not a huge deal because it will run again in the new directory before the next command, but it sometimes generates errors messages based on the dir you're leaving, which is confusing. – Nathan Long Dec 08 '14 at 15:32
  • What tool do you keep referring to? Why not use zsh? The DEBUG trap approach is pretty clunky too because there is only one DEBUG trap so if every add-on script that tries to be clever has the same idea they will clobber each other. – jw013 Dec 08 '14 at 15:36
  • @NathanLong *"how many other ways are there to change directory" - I don't know* - You can find out though, because the number of shell builtins is limited and can be found in the manual. – jw013 Dec 08 '14 at 15:38
  • @jw013 I'm referring to [chruby](https://github.com/postmodern/chruby/blob/2e991e4d224409732e2f2b356fbd01a233faaf05/share/chruby/auto.sh#L30). I agree that the `DEBUG` is approach is clunky; I'd love to see something that doesn't need it, but "doesn't hook cd" (which I think means "redefine", because that's what the dominant competing tool does) is a project "anti-feature". And even if I could grep all man pages for "PWD" and trusted that I got the complete set, that's only for my operating system. If the problem can't be solved with my constraints, that's OK, but I have reasons for them. – Nathan Long Dec 08 '14 at 17:50
  • 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 08 '14 at 18:01
  • I dont understant the question, take this script http://paste.fedoraproject.org/157720/ in this script PWD is change yet I am able to run command – Alex Jones Dec 08 '14 at 18:07
  • @jw013 Hmmm. I know that the set of available commands doesn't depend solely on which shell you use. Eg, that the `ls` that comes with OSX is not the same that comes with Linux. OSX doesn't come with `wget`, and Linux doesn't come with `say`. Could an OS-specific program like one of these change directories, or can only shell builtins do that? My knowledge here is fuzzy, which is why I'm trying not to assume too much. – Nathan Long Dec 12 '14 at 14:45
  • Commands external to the shell can never change the shell's working directory so you won't have to worry about those, only built-ins. – jw013 Dec 12 '14 at 15:01
  • @jw013 ah, that makes sense - PWD is a concept that belongs to the shell, so only it can modify it. Thanks for explaining. I think I see why overriding `cd` is a common solution for this in bash, I just don't have that option in this case. I'm starting to think that there's no solution that meets all my constraints, but that would still be useful to know. – Nathan Long Dec 12 '14 at 15:08

3 Answers3

3

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.

Nathan Long
  • 1,613
  • 1
  • 13
  • 27
2

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

Gregor
  • 1,219
  • 10
  • 16
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.

Rui F Ribeiro
  • 55,929
  • 26
  • 146
  • 227
Ali Pandidan
  • 101
  • 1