Take the 2-minute tour ×
Super User is a question and answer site for computer enthusiasts and power users. It's 100% free, no registration required.

Has anybody written a bash function to add a directory to $PATH only if it's not already there?

I typically add to PATH using something like:

export PATH=/usr/local/mysql/bin:$PATH

If I construct my PATH in .bash_profile, then it's not read unless the session I'm in is a login session -- which isn't always true. If I construct my PATH in .bashrc, then it runs with each subshell. So if I launch a Terminal window and then run screen and then run a shell script, I get:

$ echo $PATH
/usr/local/mysql/bin:/usr/local/mysql/bin:/usr/local/mysql/bin:....

I'm going to try building a bash function called add_to_path() which only adds the directory if it's not there. But, if anybody has already written (or found) such a thing, I won't spend the time on it.

share|improve this question

17 Answers 17

up vote 65 down vote accepted

From my .bashrc:

pathadd() {
    if [ -d "$1" ] && [[ ":$PATH:" != *":$1:"* ]]; then
        PATH="${PATH:+"$PATH:"}$1"
    fi
}

Note that PATH should already be marked as exported, so reexporting is not needed. Also, this adds the new directory to the end of the path; to put at the beginning, use PATH="$1:$PATH" at the obvious place. Finally, this checks whether the directory exists & is a directory before adding it, which you may not care about.

share|improve this answer
15  
I care. –  Dennis Williamson Sep 12 '09 at 3:30
2  
This gives an invalid PATH if the original PATH is empty. –  bukzor Jul 13 '11 at 18:03
2  
@bukzor I patched it to handle an empty PATH. –  Gordon Davisson Nov 11 '12 at 22:46
3  
I bow before your bash mastery! –  Mark0978 Dec 21 '13 at 3:28
3  
The modifications also mean that your note about adding the path to the front needs to be modified to say: PATH="$1${PATH:+":$PATH"}" –  ELLIOTTCABLE Apr 7 at 11:27

Here's something from my answer to this question combined with the structure of Doug Harris' function. It uses Bash regular expressions:

add_to_path ()
{
    if [[ "$PATH" =~ (^|:)"${1}"(:|$) ]]
    then
        return 0
    fi
    export PATH=${1}:$PATH
}
share|improve this answer

Put this in the comments to the selected answer, but comments don't seem to support PRE formatting, so adding the answer here:

@gordon-davisson I'm not a huge fan of unnecessary quoting & concatenation. Assuming you are using a bash version >= 3, you can instead use bash's built in regexs and do:

pathadd() {
    if [ -d "$1" ] && [[ ! $PATH =~ (^|:)$1(:|$) ]]; then
        PATH+=:$1
    fi
}

This does correctly handle cases where there are spaces in the directory or the PATH. There is some question as to whether bash's built in regex engine is slow enough that this might net be less efficient than the string concatenation and interpolation that your version does, but somehow it just feels more aesthetically clean to me.

share|improve this answer
1  
Comments support formatting using the backtick only but you don't get any decent paragraph control. –  Mark0978 Sep 24 '13 at 21:44

Expanding on Gordon Davisson's answer, this supports multiple arguments

pathappend() {
  for ARG in "$@"
  do
    if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]; then
        PATH="${PATH:+"$PATH:"}$ARG"
    fi
  done
}

So you can do pathappend path1 path2 path3 ...

For prepending,

pathprepend() {
  for ARG in "$@"
  do
    if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]; then
        PATH="$ARG${PATH:+":$PATH"}"
    fi
  done
}

Similar to pathappend, you can do

pathprepend path1 path2 path3 ...

share|improve this answer
idempotent_path_prepend ()
{
    PATH=${PATH//":$1"/} #delete any instances in the middle or at the end
    PATH=${PATH//"$1:"/} #delete any instances at the beginning
    export PATH="$1:$PATH" #prepend to beginning
}

When you need $HOME/bin to appear exactly once at the beginning of your $PATH and nowhere else, accept no substitutes.

share|improve this answer

A simple alias like this one below should do the trick:

alias checkInPath="echo $PATH | tr ':' '\n' | grep -x -c "

All it does is split the path on the : character and compare each component against the argument you pass in. grep checks for a complete line match, and prints out the count.

Sample usage:

$ checkInPath "/usr/local"
1
$ checkInPath "/usr/local/sbin"
1
$ checkInPath "/usr/local/sbin2"
0
$ checkInPath "/usr/local/" > /dev/null && echo "Yes" || echo "No"
No
$ checkInPath "/usr/local/bin" > /dev/null && echo "Yes" || echo "No"
Yes
$ checkInPath "/usr/local/sbin" > /dev/null && echo "Yes" || echo "No"
Yes
$ checkInPath "/usr/local/sbin2" > /dev/null && echo "Yes" || echo "No"
No

Replace the echo command with addToPath or some similar alias/function.

share|improve this answer

Here's what I whipped up:

add_to_path ()
{
    path_list=`echo $PATH | tr ':' ' '`
    new_dir=$1
    for d in $path_list
    do
    	if [ $d == $new_dir ]
    	then
    		return 0
    	fi
    done
    export PATH=$new_dir:$PATH
}

Now in .bashrc I have:

add_to_path /usr/local/mysql/bin
share|improve this answer

See How to keep from duplicating path variable in csh? on StackOverflow for one set of answers to this question.

share|improve this answer

Here's mine (I believe it was written years ago by Oscar, the sysadmin of my old lab, all credit to him), its been around in my bashrc for ages. It has the added benefit of allowing you to prepend or append the new directory as desired:

pathmunge () {
        if ! echo $PATH | /bin/egrep -q "(^|:)$1($|:)" ; then
           if [ "$2" = "after" ] ; then
              PATH=$PATH:$1
           else
              PATH=$1:$PATH
           fi
        fi
}

Usage:

$ echo $PATH
/bin/:/usr/local/bin/:/usr/bin
$ pathmunge /bin/
$ echo $PATH
/bin/:/usr/local/bin/:/usr/bin
$ pathmunge /sbin/ after
$ echo $PATH
/bin/:/usr/local/bin/:/usr/bin:/sbin/
share|improve this answer

For prepending, I like @Russell's solution, but there's a small bug: if you try to prepend something like "/bin" to a path of "/sbin:/usr/bin:/var/usr/bin:/usr/local/bin:/usr/sbin" it replaces "/bin:" 3 times (when it didn't really match at all). Combining a fix for that with the appending solution from @gordon-davisson, I get this:

path_prepend() {
    if [ -d "$1" ]; then
        PATH=${PATH//":$1:"/:} #delete all instances in the middle
        PATH=${PATH/%":$1"/} #delete any instance at the end
        PATH=${PATH/#"$1:"/} #delete any instance at the beginning
        PATH="$1${PATH:+":$PATH"}" #prepend $1 or if $PATH is empty set to $1
    fi
}
share|improve this answer
function __path_add(){  
if [ -d "$1" ] ; then  
    local D=":${PATH}:";   
    [ "${D/:$1:/:}" == "$D" ] && PATH="$PATH:$1";  
    PATH="${PATH/#:/}";  
    export PATH="${PATH/%:/}";  
fi  
}  
share|improve this answer

This way works fine:

if [[ ":$PATH:" != *":/new-directory:"* ]]; then PATH=${PATH}:/new-directory; fi
share|improve this answer

Here's an alternative solution that has the additional advantage of removing redundant entires:

function pathadd {
    PATH=:$PATH
    PATH=$1${PATH//:$1/}
}

The single argument to this function is prepended to the PATH, and the first instance of the same string is removed from the existing path. In other words, if the directory already exists in the path, it is promoted to the front rather than added as a duplicate.

The function works by prepending a colon to the path to ensure that all entries have a colon at the front, and then prepending the new entry to the existing path with that entry removed. The last part is performed using bash's ${var//pattern/sub} notation; see the bash manual for more details.

share|improve this answer

My versions are less careful about empty paths and insisting on paths being valid and directories than some posted here, but I do find a large-ish collection of prepend/append/clean/unique-ify/etc. shell functions to be useful for path manipulation. The whole lot, in their current state, are here: http://pastebin.com/xS9sgQsX (feedback and improvements very welcome!)

share|improve this answer

You can use a perl one liner:

appendPaths() { # append a group of paths together, leaving out redundancies
    # use as: export PATH="$(appendPaths "$PATH" "dir1" "dir2")
    # start at the end:
    #  - join all arguments with :,
    #  - split the result on :,
    #  - pick out non-empty elements which haven't been seen and which are directories,
    #  - join with :,
    #  - print
    perl -le 'print join ":", grep /\w/ && !$seen{$_}++ && -d $_, split ":", join ":", @ARGV;' "$@"
}

Here it is in bash:

addToPath() { 
    # inspired by Gordon Davisson, http://superuser.com/a/39995/208059
    # call as: addToPath dir1 dir2
    while (( "$#" > 0 )); do
    echo "Adding $1 to PATH."
    if [[ ! -d "$1" ]]; then
        echo "$1 is not a directory.";
    elif [[ ":$PATH:" == *":$1:"* ]]; then
        echo "$1 is already in the path."
    else
            export PATH="${PATH:+"$PATH:"}$1" # ${x:-defaultIfEmpty} ${x:+valueIfNotEmpty}
    fi
    shift
    done
}
share|improve this answer

Here is a POSIX compliant way.

path_append() {
    for D in "$@"; do
        if [ -d "$D" ]; then
            case ":$PATH:" in
                *:$D:*) ;;
                *)      export PATH=$PATH:$D;;
            esac
        fi
    done
}

path_prepend() {
    for D in "$@"; do
        if [ -d "$D" ]; then
            case ":$PATH:" in
                *:$D:*) ;;
                *)      export PATH=$D:$PATH;;
            esac
        fi
    done
}

It is cribbed from user58982's answer above and mike511's answer here.

share|improve this answer

I slightly modified Gordon Davisson's answer to use the current dir if none is supplied. So you can just do padd from the directory you want do add to your PATH.

padd() {
  current=`pwd`
  p=${1:-$current}
  if [ -d "$p" ] && [[ ":$PATH:" != *":$p:"* ]]; then
      PATH="$p:$PATH"
  fi
}
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.