Example:
absolute="/foo/bar"
current="/foo/baz/foo"
# Magic
relative="../../bar"
How do I create the magic (hopefully not too complicated code...)?
Example:
absolute="/foo/bar"
current="/foo/baz/foo"
# Magic
relative="../../bar"
How do I create the magic (hopefully not too complicated code...)?
Yet another solution, pure
bash
+ GNUreadlink
for easy use in following context:This works in nearly all current Linux. If
readlink -m
does not work at your side, tryreadlink -f
instead. See also https://gist.github.com/hilbix/1ec361d00a8178ae8ea0 for possible updates:Notes:
*
or?
.ln -s
:relpath / /
gives.
and not the empty stringrelpath a a
givesa
, even ifa
happens to be a directoryreadlink
is required to canonicalize paths.readlink -m
it works for not yet existing paths, too.On old systems, where
readlink -m
is not available,readlink -f
fails if the file does not exist. So you probably need some workaround like this (untested!):This is not really quite correct in case
$1
includes.
or..
for nonexisting paths (like in/doesnotexist/./a
), but it should cover most cases.(Replace
readlink -m --
above byreadlink_missing
.)Edit because of the downvote follows
Here is a test, that this function, indeed, is correct:
Puzzled? Well, these are the correct results! Even if you think it does not fit the question, here is the proof this is correct:
Without any doubt,
../bar
is the exact and only correct relative path of the pagebar
seen from the pagemoo
. Everything else would be plain wrong.It is trivial to adopt the output to the question which apparently assumes, that
current
is a directory:This returns exactly, what was asked for.
And before you raise an eyebrow, here is a bit more complex variant of
relpath
(spot the small difference), which should work for URL-Syntax, too (so a trailing/
survives, thanks to somebash
-magic):And here are the checks just to make clear: It really works as told.
And here is how this can be used to give the wanted result from the question:
If you find something which does not work, please let me know in the comments below. Thanks.
PS:
Why are the arguments of
relpath
"reversed" in contrast to all the other answers here?If you change
to
then you can leave the 2nd parameter away, such that the BASE is the current directory/URL/whatever. That's only the Unix principle, as usual.
If you dislike that, please go back to Windows. Thanks.
I took your question as a challenge to write this in "portable" shell code, i.e.
It runs on any POSIX conformant shell (zsh, bash, ksh, ash, busybox, ...). It even contains a testsuite to verify its operation. Canonicalization of pathnames is left as an exercise. :-)
Python's
os.path.relpath
as a shell functionThe goal of this
relpath
exercise is to mimic Python 2.7'sos.path.relpath
function (available from Python version 2.6 but only working properly in 2.7), as proposed by xni. As a consequence, some of the results may differ from functions provided in other answers.(I have not tested with newlines in paths simply because it breaks the validation based on calling
python -c
from ZSH. It would certainly be possible with some effort.)Regarding “magic” in Bash, I have given up looking for magic in Bash long ago, but I have since found all the magic I need, and then some, in ZSH.
Consequently, I propose two implementations.
The first implementation aims to be fully POSIX-compliant. I have tested it with
/bin/dash
on Debian 6.0.6 “Squeeze”. It also works perfectly with/bin/sh
on OS X 10.8.3, which is actually Bash version 3.2 pretending to be a POSIX shell.The second implementation is a ZSH shell function that is robust against multiple slashes and other nuisances in paths. If you have ZSH available, this is the recommended version, even if you are calling it in the script form presented below (i.e. with a shebang of
#!/usr/bin/env zsh
) from another shell.Finally, I have written a ZSH script that verifies the output of the
relpath
command found in$PATH
given the test cases provided in other answers. I added some spice to those tests by adding some spaces, tabs, and punctuation such as! ? *
here and there and also threw in yet another test with exotic UTF-8 characters found in vim-powerline.POSIX shell function
First, the POSIX-compliant shell function. It works with a variety of paths, but does not clean multiple slashes or resolve symlinks.
ZSH shell function
Now, the more robust
zsh
version. If you would like it to resolve the arguments to real paths à larealpath -f
(available in the Linuxcoreutils
package), replace the:a
on lines 3 and 4 with:A
.To use this in zsh, remove the first and last line and put it in a directory that is in your
$FPATH
variable.Test script
Finally, the test script. It accepts one option, namely
-v
to enable verbose output.Sadly, Mark Rushakoff's answer (now deleted - it referenced the code from here) does not seem to work correctly when adapted to:
The thinking outlined in the commentary can be refined to make it work correctly for most cases. I'm about to assume that the script takes a source argument (where you are) and a target argument (where you want to get to), and that either both are absolute pathnames or both are relative. If one is absolute and the other relative, the easiest thing is to prefix the relative name with the current working directory - but the code below does not do that.
Beware
The code below is close to working correctly, but is not quite right.
xyz/./pqr
'.xyz/../pqr
'../
' from paths.Dennis's code is better because it fixes 1 and 5 - but has the same issues 2, 3, 4. Use Dennis's code (and up-vote it ahead of this) because of that.
(NB: POSIX provides a system call
realpath()
that resolves pathnames so that there are no symlinks left in them. Applying that to the input names, and then using Dennis's code would give the correct answer each time. It is trivial to write the C code that wrapsrealpath()
- I've done it - but I don't know of a standard utility that does so.)For this, I find Perl easier to use than shell, though bash has decent support for arrays and could probably do this too - exercise for the reader. So, given two compatible names, split them each into components:
Thus:
Test script (the square brackets contain a blank and a tab):
Output from the test script:
This Perl script works fairly thoroughly on Unix (it does not take into account all the complexities of Windows path names) in the face of weird inputs. It uses the module
Cwd
and its functionrealpath
to resolve the real path of names that exist, and does a textual analysis for paths that don't exist. In all cases except one, it produces the same output as Dennis's script. The deviant case is:The two results are equivalent - just not identical. (The output is from a mildly modified version of the test script - the Perl script below simply prints the answer, rather than the inputs and the answer as in the script above.) Now: should I eliminate the non-working answer? Maybe...
I needed something like this but which resolved symbolic links too. I discovered that pwd has a -P flag for that purpose. A fragment of my script is appended. It's within a function in a shell script, hence the $1 and $2. The result value, which is the relative path from START_ABS to END_ABS, is in the UPDIRS variable. The script cd's into each parameter directory in order to execute the pwd -P and this also means that relative path parameters are handled. Cheers, Jim