82

I'm trying to compile wxWidgets using MingW, and I have cygwin in my path, which seems to conflict. So I would like to remove /d/Programme/cygwin/bin from the PATH variable and I wonder if there is some elegant way to do this.

The naive approach would be to echo it to a file, remove it manually and source it, but I bet there is better approach to this.

3
  • 3
    Many techniques are listed here: stackoverflow.com/questions/370047/…
    – slm
    Commented Jan 11, 2014 at 14:41
  • @slm and the most popular technique there seems to be what OP calls the "naive approach"
    – Scriddie
    Commented Oct 12, 2022 at 9:39
  • My solution doesn't use pattern (so no need to escape) and also doesn't remove subdirectories like others: export PATH="$(echo "${PATH}" | tr ':' '\n' | grep -v -x -F '/d/Programme/cygwin/bin' | tr '\n' ':')"; PATH="${PATH%:}"
    – ale5000
    Commented Jul 7 at 20:17

18 Answers 18

67

There are no standard tools to "edit" the value of $PATH (i.e. "add folder only when it doesn't already exists" or "remove this folder"). You can execute:

export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games

that would be for the current session, if you want to change permanently add it to any .bashrc, bash.bashrc, /etc/profile - whatever fits your system and user needs. However if you're using BASH, you can also do the following if, let's say, you want to remove the directory /home/wrong/dir/ from your PATH variable, assuming it's at the end:

PATH=$(echo "$PATH" | sed -e 's/:\/home\/wrong\/dir$//')

So in your case you may use

PATH=$(echo "$PATH" | sed -e 's/:\/d\/Programme\/cygwin\/bin$//')
4
  • 6
    If the path in question is at the beginning of the PATH variable, you need to match the colon at the end. This is an annoying caveat which complicates easy generic manipulations of PATH variables.
    – Graeme
    Commented Jan 11, 2014 at 13:58
  • 19
    When dealing with so many slashes I prefer to change the regex delimiter / with something like |: PATH=$(echo "$PATH" | sed -e 's|:/d/Programme/cygwin/bin$||') to prevent all the escaping. Commented Mar 17, 2016 at 12:06
  • This answer explains how to make the pattern more flexible so it works regardless of whether it's at the end of the path or not: superuser.com/a/1117805 This would yield PATH=$(echo "$PATH" | sed -e 's/:\/d\/Programme\/cygwin\/bin\(:\|$\)//')
    – Cole
    Commented Mar 12, 2021 at 5:54
  • 2
    Good answer... for those like me that have fat fingers, I would recommend creating a "PRACTICEPATH" to avoid potential grief. Other than that, I think the answer might be improved by adding a "verify" step at the end to make sure "all is well" before you have to find out "the hard way".
    – Seamus
    Commented Jun 15, 2022 at 0:52
36

Much simpler one liner.

export PATH=`echo $PATH | tr ":" "\n" | grep -v "anaconda" | tr "\n" ":"`

8
  • 3
    That's the only solution that i can internalize and remember without having to search for this question on SO again Commented Jun 12, 2020 at 18:45
  • 2
    This adds a : at the end of PATH every time the command is used, because grep appends an EOL (\n) to its output. Solve this by piping the output of grep through perl -pe 'chomp if eof'.
    – knia
    Commented Feb 19, 2021 at 14:44
  • Let's use the whole swiss army knife to solve a simple problem! Commented Feb 23, 2021 at 0:15
  • 1
    add another grep -v '^$' to remove empty ones.
    – John Jiang
    Commented Apr 1, 2021 at 0:07
  • 3
    To get rid of the trailing colon, you can also pipe the output to xargs, like so: echo $PATH | tr ":" "\n" | grep -v '/usr/local/bin' | xargs | tr ' ' ':'
    – BLuFeNiX
    Commented Sep 15, 2021 at 23:12
31

In bash:

directory_to_remove=/d/Programme/cygwin/bin
PATH=:$PATH:
PATH=${PATH//:$directory_to_remove:/:}
PATH=${PATH#:}; PATH=${PATH%:}

If you don't use an intermediate variable, you need to protect the / characters in the directory to remove so that they aren't treated as the end of the search text.

PATH=:$PATH:
PATH=${PATH//:\/d\/Programme\/cygwin\/bin:/:}
PATH=${PATH#:}; PATH=${PATH%:}

The first and third line are there to arrange for every component of the search path to be surrounded by :, to avoid special-casing the first and last component. The second line removes the specified component.

A more robust version that eliminates successive directory entries from the path, such as baz:foo:foo:bar:

function path_remove {
  PATH=":$PATH:"
  PATH=${PATH//":"/"::"}
  PATH=${PATH//":$1:"/}
  PATH=${PATH//"::"/":"}
  PATH=${PATH#:}; PATH=${PATH%:}
} 

The second line doubles the colons and the forth line reverts them back to single colons.

2
  • Thanks @Gilles, your answer prompted me to come up with my own solution, which only requires three manipulations of PATH rather then four. *8')
    – Mark Booth
    Commented Jun 23, 2016 at 11:36
  • This unfortunately doesn't eliminate successive directory entries from the path, i.e. baz:foo:foo:bar removing foo becomes baz:foo:bar. This is because the colon on both sides of the pattern matches baz[:foo:]foo:bar and because the last match ended with the colon, it doesn't pick up with the next :foo:. Commented Oct 14, 2020 at 15:21
20

After considering other options presented here, and not fully understanding how some of them worked I developed my own path_remove function, which I added to my .bashrc:

function path_remove {
  # Delete path by parts so we can never accidentally remove sub paths
  if [ "$PATH" == "$1" ] ; then PATH="" ; fi
  PATH=${PATH//":$1:"/":"} # delete any instances in the middle
  PATH=${PATH/#"$1:"/} # delete any instance at the beginning
  PATH=${PATH/%":$1"/} # delete any instance in the at the end
}

and tested with

function path_remove_test {(
  PATH=$1
  path_remove $2
  if [ "$PATH" != "$3" ] ; then echo "$1" - "$2" = "$PATH" != "$3" ; fi
)}

path_remove_test startpath:midpath:endpath startpath midpath:endpath
path_remove_test startpath:midpath:endpath midpath startpath:endpath
path_remove_test startpath:midpath:endpath endpath startpath:midpath
path_remove_test somepath:mypath/mysubpath  mypath somepath:mypath/mysubpath 
path_remove_test path path ""

This ended up pretty close to Gilles' solution but wrapped up as a bash function which could be easily used on the command line.

It has the advantages that as a bash function it works like a program without needing to be a program on the path, and it doesn't require any external programs to run, just bash string manipulation.

It appears pretty robust, in particular it doesn't turn somepath:mypath/mysubpath into somepath/mysubpath:if you run path_remove mypath, which was a problem I had with my previous path_remove function.

An excellent explanation of how bash string manipulation works can be found at the Advanced Bash-Scripting Guide.

2
  • 1
    This method fails when the path to be removed is the only one in $PATH. Add if [ $PATH = $1 ]; then PATH=""; fi;
    – Albercoc
    Commented Mar 19, 2021 at 15:48
  • I've never seen that function syntax before. It might be simpler to write path_remove_test() (...) [note the parens] which will still preserve $PATH in the outer shell. Commented Feb 7, 2023 at 17:30
9

So, combining the answers from @gilles and @bruno-a (and a couple of other sed tricks) I came up with this one-liner, which will remove (every) REMOVE_PART from PATH, regardless of whether it occurs at the beginning, middle or end of PATH

PATH=$(REMOVE_PART="/d/Programme/cygwin/bin" sh -c 'echo ":$PATH:" | sed "s@:$REMOVE_PART:@:@g;s@^:\(.*\):\$@\1@"')

It's a bit unwieldy, but it's nice to be able to do it in one hit. The ; is used to join together the two separate sed commands:

  • s@:$REMOVE_PART:@:@g (which replaces :$REMOVE_PART: with a single :)
  • s@^:\(.*\):\$@\1@ (which strips off the leading and trailing colons we added with the echo command)

And along similar lines, I've just managed to come up with this one-liner for adding a ADD_PART to the PATH, only if the PATH doesn't already contain it

PATH=$(ADD_PART="/d/Programme/cygwin/bin" sh -c 'if echo ":$PATH:" | grep -q ":$ADD_PART:"; then echo "$PATH"; else echo "$ADD_PART:$PATH"; fi')

Change the last part to echo "$PATH:$ADD_PART" if you want to add ADD_PART to the end of PATH instead of to the start.

...

...or to make this even easier, create a script called remove_path_part with the contents

echo ":$PATH:" | sed "s@:$1:@:@g;s@^:\(.*\):\$@\1@"

and a script called prepend_path_part with the contents

if echo ":$PATH:" | grep -q ":$1:"; then echo "$PATH"; else echo "$1:$PATH"; fi

and a script called append_path_part with the contents

if echo ":$PATH:" | grep -q ":$1:"; then echo "$PATH"; else echo "$PATH:$1"; fi

make them all executable, and then call them like:

  • PATH=$(remove_path_part /d/Programme/cygwin/bin)
  • PATH=$(prepend_path_part /d/Programme/cygwin/bin)
  • PATH=$(append_path_part /d/Programme/cygwin/bin)

Neat, even if I say so myself :-)

1
  • I like the suggestion, especially the idea with the scripts.
    – Devolus
    Commented Jan 13, 2015 at 9:06
8

This method handles the starting/ending path delimiter:

echo $PATH | sed 's/:/\n/g' | grep -v <path description> | xargs | tr ' ' ':'

Explanation:

echo the $PATH var into sed which replaces ':' with newlines

grep the output and remove the lines matching our query

xargs the output to replace newlines with spaces

tr the output to replace spaces with colons

xargs handles the trimming of trailing values

chain some greps to remove a bunch of paths

2
3

To complete/improve the accepted answer from Tushar, you can:

  • avoid having to escape the slashes in the PATH by using non-slash delimiters
  • omit the -e option, as per the sed man page: "If no -e, --expression, -f, or --file option is given, then the first non-option argument is taken as the sed script to interpret."
  • use the g (global) flag to remove all occurrences

In the end, it gives something like this:

PATH=$(echo "$PATH" | sed 's@:/home/wrong/dir$@@g')
3

Below are revised code from Greg Tarsa's solution. Only bash build-in commands are used here. Thus, it will save a lots of fork() system calls.

# Calling example:
#   append_to PATH "/usr/local/bin"

function remove_from()
{
    local path="${1}"
    local dir="${2}"
    local -a dirs=()
    local old_ifs="${IFS}"
    IFS=":"
    set -- ${!path}
    while [ "$#" -gt "0" ]
    do
        [ "${1}" != "${dir}" ] && dirs+=("${1}")
        shift
    done
    eval "export ${path}=\"${dirs[*]}\""
    IFS="${old_ifs}"
}

function append_to()
{
    remove_from "${1}" "${2}"
    [ -d "${2}" ] || return
    if [ -n "${!1}" ]
    then
        eval "export ${1}=\"${!1}:${2}\""
    else
        eval "export ${1}=\"${2}\""
    fi
}

function prepend_to()
{
    remove_from "${1}" "${2}"
    [ -d "${2}" ] || return
    if [ -n "${!1}" ]
    then
        eval "export ${1}=\"${2}:${!1}\""
    else
        eval "export ${1}=\"${2}\""
    fi
}
3

It's possible to use Bash's globbing and string manipulation to remove the path from the list.

PATH=:$PATH:
while [[ $PATH = *":$dir_to_remove:"* ]]; do
    PATH=${PATH//":$dir_to_remove:"/:}
done
# Trim off ":" from the beginning and end.
PATH=${PATH#:}
PATH=${PATH%:}

The while loop technique will eliminate repeated entries too. I.e. When removing foo from a PATH of foo:foo:bar, on first while loop iteration, it'll be :foo:bar: and the second, it'll be :bar: and the loop will exit and the colons will be trimmed.

[[ is also a nice feature of Bash because it's faster than [ and it lets you use regex and globs without escaping.

Using only Bash's internal commands will greatly speed up the execution time.

2

It is an interesting exercise to write a bash function to remove a directory from a path variable.

Here are some functions I use in my .bash* files to append/prepend directories to paths. They have the virtue of removing duplicate entries, if any, and work with any kind of colon separated path variable (PATH, MANPATH, INFOPATH, ...). the remove_from function removes the directory.

# {app,pre}pend_to path-var-name dirpath
# remove_from path-var-name dirpath
#
# Functions to manipulate a path-style variable.  {app,pre}pend_to
# both remove any other instances of dirname before adding it to
# the start or end of the path-var-name variable.
#
# Calling example:
#   append_to PATH "/usr/local/bin"
#
# Uses eval to allow target path varname to be passed in.
function remove_from() {
  # add surrounging colons
  eval tmp_path=":\$${1}:"
  # if dir is already there, remove it
  (echo "${tmp_path}" | grep --silent ":${2}:") &&
    tmp_path=`echo "$tmp_path" | sed "s=:${2}:=:=g"`
  # remove surrounding colons
  tmp_path=`echo "$tmp_path" | sed 's=^:==; s=:$=='`
  eval export $1=\"$tmp_path\"
}
function append_to() {
  remove_from "$1" "$2"  # clean the path contents
  eval export $1=\"\$${1}:$2\"
}
function prepend_to() {
  remove_from "$1" "$2"  # clean the path contents
  eval export $1=\"${2}:\$$1\"
}
1

Oneliner bash built-in:

DIR=/dir/to/remove
PATH=${PATH/${PATH/#$DIR:*/$DIR:}/}${PATH/${PATH/*:$DIR*/:$DIR}/}
  • Uses recursive shell parameter expansion to remove colon before or after removed directory
  • Pattern has two halves. One half is always empty, other provide reduced path.
  • Removes just one instance of $DIR from $PATH
1
  • Thats interesting, it will be helpful if you provide an explanation. ¿What do you mean does not work for repetition?
    – Msegade
    Commented Nov 24, 2021 at 15:50
0

The current answers don't solve my similar problem in that I need to remove multiple paths. All these paths are sub-directories of a single directory. In that case, this one-liner works for me: (suppose the pattern is cygwin, i.e., removing all paths that contains cygwin)

pattern=cygwin; export PATH=$(echo $PATH|tr ':' '\n'|sed "\#${pattern}#d" |tr '\n' ':')
0

I use Stephen Collyer's bash_path_funcs, described in Linux Journal way back in 2000:

https://www.linuxjournal.com/article/3645
https://www.linuxjournal.com/article/3768
https://www.linuxjournal.com/article/3935   

The addpath function adds an entry to a path only if it is not there in the first place. delpath -n deletes all non-existent directories from a path.

You can get the pathfunc.tgz file from https://web.archive.org/web/20061210054813/http://www.netspinner.co.uk:80/Downloads/pathfunc.tgz

0

In zsh, that would just be:

path=( ${path:#/d/Programme/cygwin/bin} )

As there, $path is an array mapped to $PATH like in csh/tcsh and ${array:#pattern} is the operator to expand to the elements of the array except those matching the pattern.

See also:

path[(r)/d/Programme/cygwin/bin]=()

Or:

unset 'path[(r)/d/Programme/cygwin/bin]'

To remove the first element that matches the pattern (here a fixed string). Replace r with R to remove the last matching element instead. r for reverse subscripting.

0

I wrote a simple python script that can solve your problem, i also use it when i face the same problem :

import os 

def path_update ( path_r ) :
    # get the path environnement value
    path = os.environ.get("PATH")

    # remove the specific path
    path_update = path.split(":")
    path_update = [ i for i in path_update if i != path_r ]
    path_update = ":".join(path_update)

    # update of the path environnement
    os.environ["PATH"] = path_update
    print("Successfull path update ! ! ! ")

if __name__ == "__main__" :
    path_to_remove = "/dir1/dir2/dir3/dir4/dir5/app"
    path_update( path_to_remove)

The only thing you would have to do is just updating the variable path_to_remove to the one you want to remove, save and execute the script.

-1

fzf and similar tools make TUI more user-friendly than GUI.

function path_remove {
  local dirs=""
  if dirs="$(fzf --multi <<< "${PATH//:/$'\n'}")"; then
    local path=":$PATH"
    while IFS= read -r line; do
      path="${path/":$line"/}"
    done <<< "$dirs"
    export PATH="${path#:}"
  fi
}
-1

export PATH=$(echo $PATH | tr ":" "\n" | grep -v "/wrong/dir/path" | tr "\n" ":" | sed 's/:$//g' | sed -e '$a')

2
  • As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.
    – Community Bot
    Commented Apr 4, 2023 at 5:43
  • 1
    Consider explaining what your command does. Note that your grep command would remove all paths that contain /wrong/dir/path (i.e. it would remove /usr/sbin if you use grep -v '/sbin'). Also, it would be shorter to use paste -d : -s - than that last tr and sed.
    – Kusalananda
    Commented Apr 4, 2023 at 6:17
-2
path="/home/test/113/abc/mytest.txt"
while [[ "$path" == */* ]]; do path=$(echo "$path" | cut -d/ -f2-); done
echo "$path"
3
  • On which system have you your problem? $PATH is in upper case. Commented Apr 11, 2021 at 14:08
  • 1
  • This could be optimized into path=$(basename "$path") or path=${path##*/}. Note that the question asks about removing a component of the :-delimited list in $PATH. Your answer does not address this.
    – Kusalananda
    Commented Apr 11, 2021 at 15:23

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .