Is there a hook in Bash to find out when the cwd changes?
Asked Answered
S

2

21

I am usually using zsh, which provides the chpwd() hook. That is: If the cwd is changed by the cd builtin, zsh automatically calls the method chpwd() if it exists. This allows to set up variables and aliases which depend on the cwd.

Now I want to port this bit of my .zshrc to bash, but found that chpwd() is not recognized by bash. Is a similar functionality already existing in bash? I'm aware that redefining cd works (see below), yet I'm aiming for a more elegant solution.

function cd()
{
    builtin cd $@
    chpwd
}
Spirit answered 18/7, 2010 at 16:12 Comment(4)
Why is function cd not elegant?Wolters
I like your solution, looks clean!Contrapuntist
Similar question on Unix & Linux. Your solution is the same that I'd use, I don't see why you consider it inelegant.Smitty
@user123444555621: Well, for one, using function cd isn't elegant because it only handles cd. If you use pushd or popd the directory changes, you have to wrap them as well. Not an insurmountable obstacle, but having zsh's chpwd hook means you just define that, and don't need to exhaustively determine all possible ways the working directory could change, while still not running (potentially expensive) code every time a prompt is displayed the way PROMPT_COMMAND does.Tahiti
E
14

You would have to use a DEBUG trap or PROMPT_COMMAND.

Examples:

trap chpwd DEBUG        # calls the function before each command

PROMPT_COMMAND=chpwd    # calls the function after each command

Note that the function defined in PROMPT_COMMAND is run before each prompt, though, even empty ones.

Eva answered 18/7, 2010 at 16:20 Comment(10)
a small example snippet would be great.Scup
@marcioAlmada: I added examples.Eva
Great tips; things to note: the DEBUG trap executes before every simple command, which means: (a) if your command line is a list - e.g., :; :, the trap command executes for each command in that list; and (b) if you also set $PROMPT_COMMAND, the command(s) defined there trigger the trap as well.Marseillaise
As for $PROMPT_COMMAND: aside from its command executing only once per command line, its advantage is that you can APPEND to it so as to avoid conflicts with other code; for instance, OSX predefines it for its own purposes; here's a safe command to append to it (function someFunc in this example): PROMPT_COMMAND="$(read newVal <<<"$PROMPT_COMMAND"; echo "${newVal%;}; someFunc;")"Marseillaise
@mklement0: Why not PROMPT_COMMAND="$PROMPT_COMMAND; someFunc"?Eva
Because $PROMPT_COMMAND may already end in ;, possibly with trailing whitespace (in fact, OSX does just that: "update_terminal_cwd; "); if you blindly append ; in that event, you'll end up with two ; in sequence, which causes a syntax error. My command prevents that.Marseillaise
I didn't consider the obvious alternative: the problem goes away if you PREPEND your command and unconditionally terminate it with ;, because a single trailing ; is not a syntax error: PROMPT_COMMAND="someFunc; $PROMPT_COMMAND" will work even if $PROMPT_COMMAND is not yet set.Marseillaise
@mklement0: I like your idea. Note that you could also modify traps using string manipulation to add commands to an existing one. A simplistic example: trap foo SIGUSR1; t=$(trap -p SIGUSR1); t="${t//\' /; bar\' }";t=${t//\'/}; $tEva
Thanks, but that didn't work for me, presumably because indirect quoting doesn't work unless you use eval; here's what worked: trap foo DEBUG; t=$(trap -p DEBUG); sq="'"; t="${t//\' /; bar$sq }"; eval "$t" - either way, given the required complexity (and the use of eval - though using an array-based approach could bypass that - at the expense of adding to the complexity), this highlights how much simpler modifying $PROMPT_COMMAND is.Marseillaise
A bonus of using PROMPT_COMMAND=chpwd or a variation of, compared to zsh, is that when the terminal just opens chpwd hook on zsh does not fire but PROMPT_COMMAND does fire.Joost
E
5

A better solution could be defining a custom chpwd hook.

There's not a complete hook system designed in Bash when compared with other modern shells. PROMPT_COMMAND variable is used as a hook function, which is equivalent to precmd hook in ZSH, fish_prompt in Fish. For the time being, ZSH is the only shell I've known that has a chpwd hook builtin.

PROMPT_COMMAND

If set, the value is interpreted as a command to execute before the printing of each primary prompt ($PS1).

https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Bash-Variables

chpwd Hook in Bash

A trick is provided to setup a chpwd equivalent hook in Bash based on PROMPT_COMMAND.

# create a PROPMT_COMMAND equivalent to store chpwd functions
typeset -g CHPWD_COMMAND=""

_chpwd_hook() {
  shopt -s nullglob

  local f

  # run commands in CHPWD_COMMAND variable on dir change
  if [[ "$PREVPWD" != "$PWD" ]]; then
    local IFS=$';'
    for f in $CHPWD_COMMAND; do
      "$f"
    done
    unset IFS
  fi
  # refresh last working dir record
  export PREVPWD="$PWD"
}

# add `;` after _chpwd_hook if PROMPT_COMMAND is not empty
PROMPT_COMMAND="_chpwd_hook${PROMPT_COMMAND:+;$PROMPT_COMMAND}"

Usage

# example 1: `ls` list directory once dir is changed
_ls_on_cwd_change() {
  ls
}

# append the command into CHPWD_COMMAND
CHPWD_COMMAND="${CHPWD_COMMAND:+$CHPWD_COMMAND;}_ls_on_cwd_change"

# or just use `ls` directly
CHPWD_COMMAND="${CHPWD_COMMAND:+$CHPWD_COMMAND;}ls"

Source: Create chpwd Equivalent Hook in Bash from my gist.

Effluent answered 20/7, 2019 at 3:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.