23

In my .bash_profile I have the following lines:

PATHDIRS="
/usr/local/mysql/bin
/usr/local/share/python
/opt/local/bin
/opt/local/sbin
$HOME/bin"
for dir in $PATHDIRS
do
    if [ -d $dir ]; then
        export PATH=$PATH:$dir
    fi
done

However I tried copying this to my .zshrc, and the $PATH is not being set.

First I put echo statements inside the "if directory exists" function and I found that the if statement was evaluating to false, even for directories that clearly existed.

Then I removed the directory-exists check, and the $PATH was being set incorrectly like this:

/usr/bin:/bin:/usr/sbin:/sbin:
/usr/local/bin
/opt/local/bin
/opt/local/sbin
/Volumes/Xshare/kburke/bin
/usr/local/Cellar/ruby/1.9.2-p290/bin
/Users/kevin/.gem/ruby/1.8/bin
/Users/kevin/bin

None of the programs in the bottom directories were being found or executed.
What am I doing wrong?

jopasserat
  • 5,721
  • 4
  • 31
  • 50
Kevin Burke
  • 61,194
  • 76
  • 188
  • 305

3 Answers3

49

Unlike other shells, zsh does not perform word splitting or globbing after variable substitution. Thus $PATHDIRS expands to a single string containing exactly the value of the variable, and not to a list of strings containing each separate whitespace-delimited piece of the value.

Using an array is the best way to express this (not only in zsh, but also in ksh and bash).

pathdirs=(
    /usr/local/mysql/bin
    …
    ~/bin
)
for dir in $pathdirs; do
    if [ -d $dir ]; then
        path+=$dir
    fi
done

Since you probably aren't going to refer to pathdirs later, you might as well write it inline:

for dir in \
  /usr/local/mysql/bin \
  … \
  ~/bin
; do
  if [[ -d $dir ]]; then path+=$dir; fi
done

There's even a shorter way to express this: add all the directories you like to the path array, then select the ones that exist.

path+=/usr/local/mysql/bin
…
path=($^path(N))

The N glob qualifier selects only the matches that exist. Add the -/ to the qualifier list (i.e. (-/N) or (N-/)) if you're worried that one of the elements may be something other than a directory or a symbolic link to one (e.g. a broken symlink). The ^ parameter expansion flag ensures that the glob qualifier applies to each array element separately.

You can also use the N qualifier to add an element only if it exists. Note that you need globbing to happen, so path+=/usr/local/mysql/bin(N) wouldn't work.

path+=(/usr/local/bin/mysql/bin(N-/))
Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254
  • 11
    +1 - Two additional quick tips useful for `$PATH` manipulation in `zsh` (for anyone passing by): *(1)* to prepend instead of append: `path[1,0]=/path/to/dir`, and *(2)* if you're using globbing to find the dirs, you don't have to use a for loop and instead just do `path+=( $PWD/node_modules/**/bin` ) (also works with *(1)* ). – unthought Feb 18 '13 at 12:18
  • Thanks, I used your suggestion, with path=($^path(N-/)). Now is there a way to ensure that same dirs don't duplicate in $PATH each time I run source ~/.zshrc ? Thanks – stansult Feb 19 '13 at 19:10
  • 2
    @stansult [Is there a way to add a directory to my PATH in zsh only if it's not already present?](http://unix.stackexchange.com/questions/62579/is-there-a-way-to-add-a-directory-to-my-path-in-zsh-only-if-its-not-already-pre) – Gilles 'SO- stop being evil' Feb 20 '13 at 08:02
  • @unthought ***LOVE*** your PRE-pend (`foo[1,0]=bar`) chicanery. _Gotta love_ the obscurity! – Alex Gray Mar 12 '15 at 22:15
5

You can put

 setopt shwordsplit

in your .zshrc. Then zsh will perform world splitting like all Bourne shells do. That the default appears to be noshwordsplit is a misfeature that causes many a head scratching. I'd be surprised if it wasn't a FAQ. Lets see... yup: http://zsh.sourceforge.net/FAQ/zshfaq03.html#l18 3.1: Why does $var where var="foo bar" not do what I expect?

Jens
  • 69,818
  • 15
  • 125
  • 179
3

Still not sure what the problem was (maybe newlines in $PATHDIRS)? but changing to zsh array syntax fixed it:

PATHDIRS=(
/usr/local/mysql/bin
/usr/local/share/python
/usr/local/scala/scala-2.8.0.final/bin
/opt/local/Library/Frameworks/Python.framework/Versions/2.6/bin
/opt/local/Library/Frameworks/Python.framework/Versions/2.7/bin
/opt/local/etc
/opt/local/bin
/opt/local/sbin
$HOME/.gem/ruby/1.8/bin
$HOME/bin)

and

path=($path $dir)
Kevin Burke
  • 61,194
  • 76
  • 188
  • 305