Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

Adding a directory to the PATH in Unix/Linux is a very common task. However, what if the directory is already in the path? My goal is to write a shell function (preferably portable) that will add a directory to the front or back of the PATH variable, but only if it's not already there.

Here's what I have (syntax is in zsh / bash):

#-------
# DESC: Adds a directory to the PATH if it's not already in the PATH
# ARGS:
#  1 - The directory to add
#  2 - Which end of PATH to add to.  Use "front" to prepend.
#-------
add2path() {
  if ! echo $PATH | egrep "(^|:)$1(:|\$)" > /dev/null ; then
    if [[ $2 = "front" ]]; then
      PATH="$1:$PATH"
    else
      PATH="$PATH:$1"
    fi
    export PATH
  fi
}

This function works. I have tested it on Ubuntu, Solaris, and FreeBSD. And I have tested it in bash, zsh, and ksh. But I want to make sure that it is as portable (first and foremost), readable, and as efficient as possible.

Remarks

  • I know that the =~ operator would be more readable, but I had trouble with it not working on certain OSes, particularly Solaris.
  • I know I could (and arguably should) use grep -Fq instead of egrep routing to /dev/null, but again this didn't work on certain OSes (Solaris)
    • Ironically, the way to fix this on Solaris is to add /usr/xpg4/bin to the PATH. :)
share|improve this question
    
If you want to write a portable shell function, instead of targeting certain shell interpreters and OSes, you should just write it with pure POSIX syntax and make sure it is executed in a POSIX context (which is the trickiest part). – jlliagre Apr 28 '15 at 16:31

grep is most likely an overkill. After setting IFS=":", the $PATH is conveniently split into words. Then the presence of the directory can be determined in a simple loop

    IFS=":"
    for pathdir in $PATH; do
        if [ $pathdir == $1]; then return; fi
    done

    # Now restore IFS and modify path as needed.
share|improve this answer
    
Won't this be slower, though? In this solution, we're using a script to perform looping whereas the original solution has grep, which runs at the machine level, doing the looping for us. – Sildoreth Apr 28 '15 at 18:32
    
@Sildoreth No. Looping via builtins is fast (at least comparing to a fork/exec of grep invocation). – vnp Apr 28 '15 at 18:53
2  
I like this. But I'd go with the shorter $pathdir == $1 && return – janos Apr 29 '15 at 19:08

Here is one way to make it more portable and robust:

add2path() {
  if ! echo "$PATH" | PATH=$(getconf PATH) grep -Eq '(^|:)'"${1:?missing argument}"'(:|\$)' ; then # Use the POSIX grep implementation
    if [ -d "$1" ]; then # Don't add a non existing directory to the PATH
      if [ "$2" = front ]; then # Use a standard shell test
        PATH="$1:$PATH"
      else
        PATH="$PATH:$1"
      fi
      export PATH
    fi
  fi
}

I have added a test to quit the function with an error should no argument be passed.

PATH=$(getconf PATH) is the portable way to have the POSIX commands first in the PATH. This is solving the issue you had with Solaris where, not to break existing Solaris scripts portability, the first grep command found in the PATH is not POSIX compliant.

Using [[ is not specified by POSIX so it is better to stick with [ to do tests.

$2 should then be quoted to avoid clashes with [ operands.

front being a constant string needs not be quoted.

Excluding non directories to be added looks to me a reasonable approach but is of course an optional extension which you might remove.

share|improve this answer
1  
It's not necessarily bad to add a non-existent directory to the PATH. For instance, say I always want to be able to run from ~/bin, and I have the exact same rc files on many machines (which is the case for me). There might be cases where the ~/bin directory doesn't exist yet. Then if I add it on one machine, I'd have to re-run my rc file to place that directory in the PATH. – Sildoreth Apr 28 '15 at 16:51
    
@Sildoreth Indeed, I have added comments about it in my reply. – jlliagre Apr 28 '15 at 17:02

I don't think you need to export PATH. By the time this script of yours is sourced, PATH should be exported already by startup scripts of the OS. See also this post on Unix SE.

If you don't mind dropping ksh support, then you can replace echo with a here-string:

egrep "(^|:)$1(:|\$)" <<< "$PATH" > /dev/null
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.