Linux – change the PATH dynamically based on the cwd

bashenvironment-variableslinuxpathshell

I would like to change my $PATH environment variable dependent on my current working directory.

Say I'm in /foo/bar/baz and I have the directories /foo/node_modules/.bin and /foo/bar/baz/node_modules/.bin. I would like to add every possible ./node_modules/.bin recursively to $PATH.

But when I cd into a different directory (like /foo/bar), I want my original, clean $PATH to be restored, and then to start to look for ./node_modules/.bin recursively again.

(I want to solve my own question from npm's issue tracker: Can we add locally installed packages to PATH, too?)

Note: I'm on a Mac, but interested in a general solution.

Best Answer

Introduction

If I understand you correctly, you want to add any directories "$X/node_modules/.bin" where $X is the $PWD or any of its ancestors.

The script at the end of this post should give the behaviour you want. You need to source it in every session where you want it. If you name the file augment_path.sh, then adding this line to your .bashrc should be sufficient:

source augment_path.sh

Discussion

I think garyjohn has the basic approach right, but he's searching all descendents rather than all ancestors.

The $PROMPT_COMMAND variable allows you to specify a command to be executed each time the prompt is displayed. I've added a $PROMPT_COMMAND_OLD variable to allow the original $PROMPT_COMMAND to be restored

It's probably not necessary, but for good form I add a $LAST_WD variable and test to avoid recomputing the path when the directory hasn't changed. You can remove all three lines containing LAST_WD if you like.

The augment_path function scans from $PWD upwards, looking for the target directories in each ancestor and adding any it finds to the path.

  • They are placed in the path in order, so the deepest such directory will take precedence if there are any conflicts. I assume this is the desired behaviour. If not, change

    PATH_ADDITION="$PATH_ADDITION:$resolved_target"
    

    to

    PATH_ADDITION="$resolved_target:$PATH_ADDITION"
    
  • However, these directories will all take precedence over the rest of the path. If you want the rest of the path to take precedence, change:

    PATH="$PATH_ADDITION:$RAW_PATH"
    

    to

    PATH="$RAW_PATH:$PATH_ADDITION"
    

Script

RAW_PATH="$PATH"
LAST_WD=`pwd`

augment_path() {
    target="node_modules/.bin"
    if [ "$PWD" = "$LAST_WD" ]; then return 0; fi;
    PATH_ADDITION=""
    scandir="$PWD"
    until [ "$scandir" = "" ]; do
    resolved_target="$scandir"/"$target"
    if [ -d "$resolved_target" ]; then
        PATH_ADDITION="$PATH_ADDITION:$resolved_target"
    fi
    scandir="${scandir%/*}"
    done 
    PATH="$PATH_ADDITION:$RAW_PATH"
    LAST_WD=`pwd`
}

PROMPT_COMMAND_OLD="${PROMPT_COMMAND%; augment_path}"
PROMPT_COMMAND="$PROMPT_COMMAND_OLD; augment_path"
Related Question