6

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.

3 Answers 3

3

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"
1
  • Thank you every one involved in solving this problem and thank you pyrocrasty for this in-depth answer. As a non-shell-expert your additional explanations are very helpful. Your solution works exactly as I hoped (deepest directory with highest precedence and higher precedence than the rest of the path. Perfect.
    – Pipo
    May 17, 2015 at 18:19
1

You can work with bash's PROMPT_COMMAND:

PROMPT_COMMAND='[ -z "$X" ] && X=$PATH; PATH=$X:$(pwd)/node_modules/.bin'

That command is executed every time when the prompt apprears. Therefore, everytime when a command finishes, the PATH variable is changed. An additional directory is added to the end.

If there is no directory ./node_modules/.bin in the current working directory, the path is appended anyway. But, that's not a problem. It is not searched trough (because it doesn't exist).

See the demonstration:

$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
$ PROMPT_COMMAND='[ -z "$X" ] && X=$PATH; PATH=$X:$(pwd)/node_modules/.bin'
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/node_modules/.bin
$ cd test
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/test/node_modules/.bin
$ cd /etc/
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/etc/node_modules/.bin
4
  • This works nearly as I wanted it, but it doesn't work recursively from the most deep directory to the top or am I doing it wrong? Say I'm in foo/bar/baz than no /foo/bar/baz/node_modules/.binand /foo/node_modules/.bin are added.
    – Pipo
    May 16, 2015 at 4:24
  • @Pipo I don't unerstand correctly. Do you want to add 2 folders? So, ./node_modules/.bin and ../../node_modules/.bin? If, yes, then use that PROMPT_COMMAND='[ -z "$X" ] && X=$PATH; PATH=$X:$(pwd)/node_modules/.bin:$(pwd)/../../node_modules/.bin'. If will look a bit strange i the $PATH variable but should work, as desired.
    – chaos
    May 16, 2015 at 11:44
  • Yeah, you're right. I want to add both folders, because Node modules are nested in each other. Thank you, I'll try that.
    – Pipo
    May 17, 2015 at 18:05
  • I just noticed that the ../../ part is hardcoded. I'm sorry, I should have said that my /foo/bar/baz example is just one example. It could really be any directory structure. I'll try @pyrocrasty 's answer now.
    – Pipo
    May 17, 2015 at 18:14
1

I think putting the following into your ~/.bashrc will do what you want.

If the bash environment variable PROMPT_COMMAND is set to some command, that command is executed before bash displays the primary prompt.

In the code below, PROMPT_COMMAND is set to the name of a function, doit. That function checks whether the current working directory has changed and if so it first sets PATH to its original value, then checks for the presence of a subdirectory named node_modules/.bin. If that subdirectory exists, the function appends the names of it and all the subdirectories below it to PATH.

orig_path="$PATH"
prev_pwd=""
doit() {
    if [ "$PWD" != "$prev_pwd" ]; then
        PATH="$orig_path"
        if [ -d "node_modules/.bin" ]; then
            PATH="$PATH$(find $PWD/node_modules/.bin -type d -printf ':%p')"
            # For POSIX compatibility (macOS or other), use:
            # PATH="$PATH$(find $PWD/node_modules/.bin -type d -exec echo -n :{} \;)"
        fi
        prev_pwd="$PWD"
    fi
}
PROMPT_COMMAND=doit

3
  • I'm sorrty, I forgot to mention that I'm using Mac OS X. It seems I can't use -printf there. Besides that I think it should work...
    – Pipo
    May 15, 2015 at 5:34
  • Replacing -printf ':%p' by -exec echo -n :{} \; seems to work, too, but not all echos have -n and I don't have access to a Mac at the moment. If your echo doesn't have -n or the equivalent, you could omit the -n and follow the find with | tr -d '\n' to remove the newlines.
    – garyjohn
    May 15, 2015 at 7:02
  • -n seems to work. This works nearly as I wanted it, but it doesn't work recursively from the most deep directory to the top or am I doing it wrong? Say I'm in foo/bar/baz than no /foo/bar/baz/node_modules/.binand /foo/node_modules/.bin are added.
    – Pipo
    May 16, 2015 at 4:24

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .