Is there a idiomatic way of removing elements from PATH-like shell variables?
That is I want to take
PATH=/home/joe/bin:/usr/local/bin:/usr/bin:/bin:/path/to/app/bin:.
and remove or replace the /path/to/app/bin
without clobbering the rest of the variable. Extra points for allowing me put new elements in arbitrary positions. The target will be recognizable by a well defined string, and may occur at any point in the list.
I know I've seen this done, and can probably cobble something together on my own, but I'm looking for a nice approach. Portability and standardization a plus.
I use bash, but example are welcome in your favorite shell as well.
The context here is one of needing to switch conveniently between multiple versions (one for doing analysis, another for working on the framework) of a large scientific analysis package which produces a couple dozen executables, has data stashed around the filesystem, and uses environment variable to help find all this stuff. I would like to write a script that selects a version, and need to be able to remove the $PATH
elements relating to the currently active version and replace them with the same elements relating to the new version.
This is related to the problem of preventing repeated $PATH
elements when re-running login scripts and the like.
- Previous similar question: How to keep from duplicating path variable in csh
- Subsequent similar question: What is the most elegant way to remove a path from the $PATH variable in Bash?
In line with dj_segfault's answer, I do this in scripts that append/prepend environment variables that might be executed multiple times:
Using this same technique to remove, replace or manipulate entries in PATH is trivial given the filename-expansion-like pattern matching and pattern-list support of shell parameter expansion.
suppose
If you want to remove /lib/jvm/java-1.6.0/bin/ do like as below
sed
will take input fromecho $PATH
and replace /lib/jvm/java-1.6.0/bin/: with emptyin this way you can remove
OK, thanks to all responders. I've prepared an encapsulated version of florin's answer. The first pass looks like this:
Still needs error trapping in all the functions, and I should probably stick in a repeated path solution while I'm at it.
You use it by doing a
. /include/path/path_tools.bash
in the working script and calling on of the thereplace-path*
functions.I am still open to new and/or better answers.
For deleting an element you can use sed:
will delete the paths that contain "foo" from the path.
You could also use sed to insert a new line before or after a given line.
Edit: you can remove duplicates by piping through sort and uniq:
Say you have /foo:/some/path:/some/path/dir1:/some/path/dir2:/bar and you want to replace /some/path Then it correctly replaces "/some/path" but leaves "/some/path/dir1" or "/some/path/dir2", as what you would expect.
Related post What is the most elegant way to remove a path from the $PATH variable in Bash?
Addressing the proposed solution from dmckee:
export
as equivalent to setting (or even creating) a global variable - something to be avoided whenever possible.replace-path PATH $PATH /usr
' to do, but it does not do what I would expect.Consider a PATH value that starts off containing:
The result I got (from '
replace-path PATH $PATH /usr
') is:I would have expected to get my original path back since /usr does not appear as a (complete) path element, only as part of a path element.
This can be fixed in
replace-path
by modifying one of thesed
commands:I used ':' instead of '|' to separate parts of the substitute since '|' could (in theory) appear in a path component, whereas by definition of PATH, a colon cannot. I observe that the second
sed
could eliminate the current directory from the middle of a PATH. That is, a legitimate (though perverse) value of PATH could be:After processing, the current directory would no longer be on the PATH.
A similar change to anchor the match is appropriate in
path-element-by-pattern
:I note in passing that
grep -m 1
is not standard (it is a GNU extension, also available on MacOS X). And, indeed, the-n
option forecho
is also non-standard; you would be better off simply deleting the trailing colon that is added by virtue of converting the newline from echo into a colon. Since path-element-by-pattern is used just once, has undesirable side-effects (it clobbers any pre-existing exported variable called$removestr
), it can be replaced sensibly by its body. This, along with more liberal use of quotes to avoid problems with spaces or unwanted file name expansion, leads to:I have a Perl script called
echopath
which I find useful when debugging problems with PATH-like variables:When I run the modified solution on the test code below:
The output is:
This looks correct to me - at least, for my definition of what the problem is.
I note that
echopath LD_LIBRARY_PATH
evaluates$LD_LIBRARY_PATH
. It would be nice if your functions were able to do that, so the user could type:That can be done by using:
This leads to this revision of the code:
The following revised test now works too:
It produces the same output as before.