Can I hook into the cd command?

6,805

Solution 1

You could add this function to your .bashrc or other startup file (depending on your shell).

cd() {      
   if [ "$1" = "public_html" ]; then
      echo "current dir is my dir"
   fi
   builtin cd "$1"
}

Solution 2

With ksh or bash (or zsh):

cd() {
  builtin cd "$@" || return
  [ "$OLDPWD" = "$PWD" ] || case $PWD in
      (*/public_html) echo do something
    esac
}

With zsh:

chpwd()
  case $PWD in
    (*/public_html) echo do something
  esac

(chpwd is a hook function that is called whenever the current working directory changes (by way of cd, pushd, popd...)).

Solution 3

Wrapping cd, an existing command, is not recommended.

A more universal solution would be defining a custom chpwd hook in Bash. (According to tags of this question, I assume you're using Bash)

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}"

Since we're detecting PWD change directly, the solution works with cd, pushd, and popd.

Note: The main difference between our chpwd implementaion in Bash and the chpwd in ZSH is, PROMPT_COMMAND is not supported in a non-interactive Bash shell.

Usage

_public_html_action() {
  if [[ $PWD == */public_html ]]; then
    # actions
  fi
}

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

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

For anyone want an answer for ZSH. Use chpwd hook in ZSH. Don't define chpwd() function directly. More detail here.

Solution 4

I am not a bash expert but I would take @UVV's answer and modify it just a little so that it does this:

  • instead of checking for public_html, I'd just check for some hook-script file in the target dir $1, say, cd_hook.sh.
  • if that hook-script exists, run it, and move on with the cd

This seems more generic because you'd then be able to apply the cd-hook to any directory if you so choose, by just adding a cd_hook.sh in the said directory.

Share:
6,805

Related videos on Youtube

TMH
Author by

TMH

Updated on September 18, 2022

Comments

  • TMH
    TMH over 1 year

    I'm trying to set something up on a server I run, when ever I cd into a public_html folder 95% of the time there's a few commands I will always run to check certain things.

    Is there anyway I can hook into cd so if the directory is a public_html, it will automatically run the commands for me?

    If I can't hook into the cd command, are there any other things I could do to achieve the outcome I'm after?

    I'm running CentOS 5.8.

  • TMH
    TMH over 9 years
    Ooh, this looks interesting. I've added this to my .bash_profile, and added a echo "Testing..." just above the if, but nothings output. Do I need do something to apply these changes?
  • UVV
    UVV over 9 years
    @TomHart If you didn't start a new session then you have to source the file into your current session by . ~/.bash_profile
  • UVV
    UVV over 9 years
    @TomHart it seems there's no /bin/cd in my CentOS, so this solution might not going to work
  • TMH
    TMH over 9 years
    I've just noticed that now. I found this question which states why unix.stackexchange.com/questions/116955/where-is-cd-located. Still a good solution for system that actually have a /bin/cd though :).
  • UVV
    UVV over 9 years
    @TomHart Actually I fixed the function using your link ;)
  • TMH
    TMH over 9 years
    That's teamwork :P. I'll be able to get it working great now. Thanks :).
  • Stéphane Chazelas
    Stéphane Chazelas over 9 years
    Does not work if you cd -P public_html or cd ~/public_html or has unexpected behaviour if you can't cd into public_html.
  • Stéphane Chazelas
    Stéphane Chazelas over 9 years
    .bash_profile is for session settings, not for customising bash (for which there's ~/.bashrc)
  • Stéphane Chazelas
    Stéphane Chazelas over 8 years
    It seems generic, but you're introducing a security vulnerability. Someone or something only needs to plant a hook-script in /tmp for instance to make you run any code the next time you cd there. You'd want at least to make sure that the hook-script is owned by you, not a symlink, not writable by anyone other than you, that the directory it's in is only writable by you... See Keeping history per working directory (cf. per shell session) for a safer approach.
  • lost-and-found
    lost-and-found over 8 years
    Thanks for the security insight @StéphaneChazelas. I have to admit I didn't even think about it.
  • Duong LV
    Duong LV over 5 years
    I found this question while doing just that: if I cd somewhere, check for a .bashcd file and run it if it exists.