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
}