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
}