Alex Rivera | Logout

What is the most elegant way to remove a path from the $PATH variable in Bash?

Asked 2008-12-15T23:19:14.950
142

Or more generally, how do I remove an item from a colon-separated list in a Bash environment variable?

I thought I had seen a simple way to do this years ago, using the more advanced forms of Bash variable expansion, but if so I've lost track of it. A quick search of Google turned up surprisingly few relevant results and none that I would call "simple" or "elegant". For example, two methods using sed and awk, respectively:

PATH=$(echo $PATH | sed -e 's;:\?/home/user/bin;;' -e 's;/home/user/bin:\?;;')
PATH=!(awk -F: '{for(i=1;i<=NF;i++){if(!($i in a)){a[$i];printf s$i;s=":"}}}'<<<$PATH)

Does nothing straightforward exist? Is there anything analogous to a split() function in Bash?

Update:
It looks like I need to apologize for my intentionally-vague question; I was less interested in solving a specific use-case than in provoking good discussion. Fortunately, I got it!

There are some very clever techniques here. In the end, I've added the following three functions to my toolbox. The magic happens in path_remove, which is based largely on Martin York's clever use of awk's RS variable.

path_append ()  { path_remove $1; export PATH="$PATH:$1"; }
path_prepend () { path_remove $1; export PATH="$1:$PATH"; }
path_remove ()  { export PATH=`echo -n $PATH | awk -v RS=: -v ORS=: '$0 != "'$1'"' | sed 's/:$//'`; }

The only real cruft in there is the use of sed to remove the trailing colon. Considering how straightforward the rest of Martin's solution is, though, I'm quite willing to live with it!


Related question: How do I manipulate $PATH elements in shell scripts?

Edit
Report

3 Answers

3

I did write an answer to this here (using awk too). But i'm not sure that's what you are looking for? It at least looks clear to me what it does, instead of trying to fit into one line. For a simple one liner, though, that only removes stuff, i recommend

echo $PATH | tr ':' '\n' | awk '$0 != "/bin"' | paste -sd:

Replacing is

echo $PATH | tr ':' '\n' | 
    awk '$0 != "/bin"; $0 == "/bin" { print "/bar" }' | paste -sd:

or (shorter but less readable)

echo $PATH | tr ':' '\n' | awk '$0 == "/bin" { print "/bar"; next } 1' | paste -sd:

Anyway, for the same question, and a whole lot of useful answers, see here.

answered 2008-12-16T00:13:48.723
1

What makes this problem annoying are the fencepost cases among first and last elements. The problem can be elegantly solved by changing IFS and using an array, but I don't know how to re-introduce the colon once the path is converted to array form.

Here is a slightly less elegant version that removes one directory from $PATH using string manipulation only. I have tested it.

#!/bin/bash
#
#   remove_from_path dirname
#
#   removes $1 from user's $PATH

if [ $# -ne 1 ]; then
  echo "Usage: $0 pathname" 1>&2; exit 1;
fi

delendum="$1"
NEWPATH=
xxx="$IFS"
IFS=":"
for i in $PATH ; do
  IFS="$xxx"
  case "$i" in
    "$delendum") ;; # do nothing
    *) [ -z "$NEWPATH" ] && NEWPATH="$i" || NEWPATH="$NEWPATH:$i" ;;
  esac
done

PATH="$NEWPATH"
echo "$PATH"
answered 2008-12-16T02:53:52.037
0

I took a slightly different approach than most people here and focussed specifically on just string manipulation, like so:

path_remove () {
    if [[ ":$PATH:" == *":$1:"* ]]; then
        local dirs=":$PATH:"
        dirs=${dirs/:$1:/:}
        export PATH="$(__path_clean $dirs)"
    fi
}
__path_clean () {
    local dirs=${1%?}
    echo ${dirs#?}
}

The above is a simplified example of the final functions I use. I've also created path_add_before and path_add_after allowing you insert a path before/after a specified path already in PATH.

The full set of functions are available in path_helpers.sh in my dotfiles. They fully support removing/appending/prepending/inserting at beginning/middle/end of the PATH string.

answered 2012-02-06T01:16:56.410

Your Answer