I'm a bit surprised that no one has mentioned this yet,
but you can use readlink -f
to convert relative paths to absolute paths,
and add them to the PATH as such.
For example, to improve on Guillaume Perrault-Archambault's answer,
pathappend() {
for ARG in "$@"
do
if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]; then
PATH="${PATH:+"$PATH:"}$ARG"
fi
done
}
becomes
pathappend() {
for ARG in "$@"
do
if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]
then
if ARGA=$(readlink -f "$ARG") #notice me
then
if [ -d "$ARGA" ] && [[ ":$PATH:" != *":$ARGA:"* ]]
then
PATH="${PATH:+"$PATH:"}$ARGA"
fi
else
PATH="${PATH:+"$PATH:"}$ARG"
fi
fi
done
}
1. The Basics — What Good Does This Do?
The readlink -f
command will (among other things)
convert a relative path to an absolute path.
This allows you to do something like
$ cd /path/to/my/bin/dir
$ pathappend .
$ echo "$PATH"
<your_old_path>:/path/to/my/bin/dir
2. Why Do We Test For Being In PATH Twice?
Well, consider the above example.
If the user says pathappend .
from the /path/to/my/bin/dir
directory a second time,
ARG
will be .
.
Of course, .
will not be present in PATH
.
But then ARGA
will be set to /path/to/my/bin/dir
(the absolute path equivalent of .
), which is already in PATH
.
So we need to avoid adding /path/to/my/bin/dir
to PATH
a second time.
Perhaps more importantly,
the primary purpose of readlink
is, as its name implies,
to look at a symbolic link and read out the pathname that it contains
(i.e., points to).
For example:
$ ls -ld /usr/lib/perl/5.14
-rwxrwxrwx 1 root root Sep 3 2015 /usr/lib/perl/5.14 -> 5.14.2
$ readlink /usr/lib/perl/5.14
5.14.2
$ readlink -f /usr/lib/perl/5.14
/usr/lib/perl/5.14.2
Now, if you say pathappend /usr/lib/perl/5.14
,
and you already have /usr/lib/perl/5.14
in your PATH,
well, that’s fine; we can just leave it as it is.
But, if /usr/lib/perl/5.14
isn’t already in your PATH,
we call readlink
and get ARGA
= /usr/lib/perl/5.14.2
,
and then we add that to PATH
.
But wait a minute — if you already said pathappend /usr/lib/perl/5.14
,
then you already have /usr/lib/perl/5.14.2
in your PATH, and, again,
we need to check for that to avoid adding it to PATH
a second time.
3. What’s the Deal With if ARGA=$(readlink -f "$ARG")
?
In case it’s unclear, this line tests whether the readlink
succeeds.
This is just good, defensive programming practice.
If we’re going to use the output from command m
as part of command n (where m < n),
it’s prudent to check
whether command m failed and handle that in some way.
I don’t think it’s likely that readlink
will fail — but, as discussed
in How to retrieve the absolute path of an arbitrary file from OS X
and elsewhere, readlink
is a GNU invention.
It’s not specified in POSIX, so its availability in Mac OS, Solaris,
and other non-Linux Unixes is questionable.
(In fact, I just read a comment that says
“readlink -f
does not seem to work on Mac OS X 10.11.6,
but realpath
works out of the box.”
So, if you’re on a system that doesn’t have readlink
,
or where readlink -f
doesn’t work,
you may be able to modify this script to use realpath
.)
By installing a safety net, we make our code somewhat more portable.
Of course,
if you’re on a system that doesn’t have readlink
(or realpath
),
you’re not going to want to do pathappend .
.
The second -d
test ([ -d "$ARGA" ]
) is really probably unnecessary.
I can’t think of any scenario
where $ARG
is a directory and readlink
succeeds,
but $ARGA
is not a directory.
I just copy-and-pasted the first if
statement to create the third one,
and I left the -d
test there out of laziness.
4. Any Other Comments?
Yeah.
Like many of the other answers here,
this one tests whether each argument is a directory,
processes it if it is, and ignores it if it is not.
This may (or may not) be adequate if you’re using pathappend
only in “.
” files (like .bash_profile
and .bashrc
)
and other scripts.
But, as this answer showed (above),
it’s perfectly feasible to use it interactively.
You will be very puzzled if you do
$ pathappend /usr/local/nysql/bin
$ mysql
-bash: mysql: command not found
Did you notice that I said nysql
in the pathappend
command, rather than mysql
?
And that pathappend
didn’t say anything;
it just silently ignored the incorrect argument?
As I said above, it’s good practice to handle errors.
Here’s an example:
pathappend() {
for ARG in "$@"
do
if [ -d "$ARG" ]
then
if [[ ":$PATH:" != *":$ARG:"* ]]
then
if ARGA=$(readlink -f "$ARG") #notice me
then
if [[ ":$PATH:" != *":$ARGA:"* ]]
then
PATH="${PATH:+"$PATH:"}$ARGA"
fi
else
PATH="${PATH:+"$PATH:"}$ARG"
fi
fi
else
printf "Error: %s is not a directory.\n" "$ARG" >&2
fi
done
}
http://unix.stackexchange.com/questions/4965/keep-duplicates-out-of-path-on-source – Ciro Santilli 新疆改造中心法轮功六四事件 – 2015-08-10T09:41:32.890
If you frame the problem as "only adding if not already there", you're going to be rudely surprised when the day comes when it's important for the inserted item to be at the beginning but it doesn't wind up there. A better approach would be to insert the element, and then remove duplicates, so if the new entry was already there it will be effectively moved to the beginning. – Don Hatch – 2017-11-24T22:48:16.630
See http://stackoverflow.com/questions/273909/how-do-i-manipulate-path-elements-in-shell-scripts for some infrastructure that can help.
– dmckee --- ex-moderator kitten – 2009-09-11T17:49:46.693