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
    
See stackoverflow.com/questions/273909/… for some infrastructure that can help. –  dmckee Sep 11 '09 at 17:49

13 Answers 13

up vote 52 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
11  
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
    
This doesn't work correctly if $1 is the final entry in the path before calling pathadd. Because $1 doesn't exist within $PATH with a : before and a : after it, it's not found and $1 is appended to the path. It will only add the $1 to the path a second time, though. –  Neil Apr 5 '13 at 21:38
3  
I bow before your bash mastery! –  Mark0978 Dec 21 '13 at 3:28

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
    
This worked for me only using $1 instead of ${1} –  Andrei Sep 5 '12 at 19:25
    
@Andrei: Yes, the braces are unnecessary in this instance. I'm not sure why I included them. –  Dennis Williamson Sep 5 '12 at 19:31

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
    
This puts the addition at the end. It is often desirable to add to the beginning in order to override existing locations. –  Dennis Williamson Nov 20 at 21:02
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
    
Thanks, that's a nice elegant solution, but I found that I had to do PATH=${PATH/"... rather than PATH=${PATH//"... in order to get it to work. –  Mark Booth Oct 25 '13 at 11:01
    
The double-slash form should match any number of matches; the single slash only matches the first (search for "Pattern substitution" in the bash man page). Not sure why it didn't work... –  andybuckley Oct 27 '13 at 23:21
    
This fails in the unusual case that $1 is the only entry (no colons). The entry becomes doubled. –  Dennis Williamson Nov 20 at 21:05
    
It also deletes too aggressively as pointed out by PeterS6g. –  Dennis Williamson Nov 20 at 21:13

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
    
Using "grep -x" is probably faster than the loop I put in my bash function. –  Doug Harris Sep 11 '09 at 18:04

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
    
This is great! I made one small change. For the 'pathprepend' function, it's convenient to read the arguments in reverse, so you can say, for example, pathprepend P1 P2 P3 and end up with PATH=P1:P2:P3. To get this behavior, change for ARG in "$@" do to for ((i=$#; i>0; i--)); do ARG=${!i} –  ishmael Aug 15 at 21:07

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

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

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

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

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

This way works fine:

if [[ ":$PATH:" != *":/new-directory:"* ]]; then PATH=${PATH}:/new-directory; 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.