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.
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
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$//')
Method 2
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.
Method 3
Much simpler one liner.
export PATH=`echo $PATH | tr “:” “n” | grep -v “anaconda” | tr “n” “:”`
Method 4
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.
Method 5
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 "<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="99ead9">[email protected]</a>:$REMOVE_PART:@:@g;<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="aeddee">[email protected]</a>^:(.*):<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="527612">[email protected]</a><a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="192859">[email protected]</a>"')
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:
[email protected]:$REMOVE_PART:@:@g(which replaces:$REMOVE_PART:with a single:)[email protected]^:(.*):[email protected][email protected](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 "<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="ee9dae">[email protected]</a>:$1:@:@g;<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="f281b2">[email protected]</a>^:(.*):<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="537713">[email protected]</a><a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="0a3b4a">[email protected]</a>"
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 🙂
Method 6
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
Method 7
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
}
Method 8
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
-eoption, 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 '<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="700330">[email protected]</a>:/home/wrong/<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="84e0edf6a0c4">[email protected]</a>@g')
Method 9
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"
}
Method 10
It’s possible to use Bash’s regular expressions 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.
[[ 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.
Caveat: This code will work if you have glob-special characters in your directories, but only if you escape them in $dir_to_remove, e.g.
PATH='/foo:directory *with* stars and [brackets]' # <~ unescaped: fine dir_to_remove='directory *with* stars and [brackets]' # <~ must escape
Method 11
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
Method 12
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' ':')
Method 13
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
Method 14
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.
Method 15
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
}
Method 16
path="/home/test/113/abc/mytest.txt" while [[ "$path" == */* ]]; do path=$(echo "$path" | cut -d/ -f2-); done echo "$path"
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0