Getting relative links between two paths
Solution 1
You could use the symlinks
command to convert absolute paths to relative:
/tmp$ mkdir -p 1/{a,b,c} 2
/tmp$ cd 2
/tmp/2$ ln -s /tmp/1/* .
/tmp/2$ ls -l
total 0
lrwxrwxrwx 1 stephane stephane 8 Jul 31 16:32 a -> /tmp/1/a/
lrwxrwxrwx 1 stephane stephane 8 Jul 31 16:32 b -> /tmp/1/b/
lrwxrwxrwx 1 stephane stephane 8 Jul 31 16:32 c -> /tmp/1/c/
We've got absolute links, let's convert them to relative:
/tmp/2$ symlinks -cr .
absolute: /tmp/2/a -> /tmp/1/a
changed: /tmp/2/a -> ../1/a
absolute: /tmp/2/b -> /tmp/1/b
changed: /tmp/2/b -> ../1/b
absolute: /tmp/2/c -> /tmp/1/c
changed: /tmp/2/c -> ../1/c
/tmp/2$ ls -l
total 0
lrwxrwxrwx 1 stephane stephane 6 Jul 31 16:32 a -> ../1/a/
lrwxrwxrwx 1 stephane stephane 6 Jul 31 16:32 b -> ../1/b/
lrwxrwxrwx 1 stephane stephane 6 Jul 31 16:32 c -> ../1/c/
References
Solution 2
Try using realpath
command (part of GNU coreutils
; >=8.23), e.g.:
realpath --relative-to=/foo/bar/something /foo/hello/world
If you're using macOS, install GNU version via: brew install coreutils
and use grealpath
.
Note that both paths need to exist for the command to be successful. If you need the relative path anyway even if one of them does not exist then add the -m switch.
For more examples, see Convert absolute path into relative path given a current directory.
Solution 3
I think this python solution (taken from this SO answer) is also worth mentioning. Add this to your ~/.zshrc
:
relpath() python -c 'import os.path, sys;\
print os.path.relpath(sys.argv[1],sys.argv[2])' "$1" "${2-$PWD}"
You can then do, for example:
$ relpath /usr/local/share/doc/emacs /usr/local/share/fonts
../doc/emacs
Solution 4
# Prints out the relative path between to absolute paths. Trivial.
#
# Parameters:
# $1 = first path
# $2 = second path
#
# Output: the relative path between 1st and 2nd paths
relpath() {
local pos="${1%%/}" ref="${2%%/}" down=''
while :; do
test "$pos" = '/' && break
case "$ref" in $pos/*) break;; esac
down="../$down"
pos=${pos%/*}
done
echo "$down${ref##$pos/}"
}
This is the simplest and most beautiful solution to the problem.
Also it should work on all bourne-like shells.
And the wisdom here is: there is absolutely no need for external utilities or even complicated conditions. A couple prefix/suffix substitutions and a while loop is all you need to get just about anything done on bourne-like shells.
Also available via PasteBin: http://pastebin.com/CtTfvime
Related videos on Youtube
![Amelio Vazquez-Reina](https://i.stack.imgur.com/ilsZ4.jpg?s=256&g=1)
Amelio Vazquez-Reina
I'm passionate about people, technology and research. Some of my favorite quotes: "Far better an approximate answer to the right question than an exact answer to the wrong question" -- J. Tukey, 1962. "Your title makes you a manager, your people make you a leader" -- Donna Dubinsky, quoted in "Trillion Dollar Coach", 2019.
Updated on September 18, 2022Comments
-
Amelio Vazquez-Reina almost 2 years
Say I have two paths:
<source_path>
and<target_path>
. I would like my shell (zsh) to automatically find out if there is a way to represent<target_path>
from<source_path>
as a relative path.E.g. Let's assume
<source_path>
is/foo/bar/something
<target_path>
is/foo/hello/world
The result would be
../../hello/world
Why I need this:
I need like to create a symbolic link from
<source_path>
to<target_path>
using a relative symbolic link whenever possible, since otherwise our samba server does not show the file properly when I access these files on the network from Windows (I am not the sys admin, and don't have control over this setting)Assuming that
<target_path>
and<source_path>
are absolute paths, the following creates a symbolic link pointing to an absolute path.ln -s <target_path> <source_path>
so it does not work for my needs. I need to do this for hundreds of files, so I can't just manually fix it.
Any shell built-ins that take care of this?
-
Admin almost 11 yearsYou can use
symlinks
to convert absolute links to relative. -
Admin almost 11 years@StephaneChazelas how? Could you post an answer explaining?
-
Stéphane Chazelas almost 11 years@user815423426, terdon has answered that part
-
Circonflexe almost 7 yearsThanks for your solution. I found that to make it work in a
Makefile
I needed to add a pair of double quotes to the linepos=${pos%/*}
(and, of course, semicolons and backslashes at the end of everyline). -
phuclv over 6 yearsI get
realpath: unrecognized option '--relative-to=.'
:( realpath version 1.19 -
kenorb over 6 years@LưuVĩnhPhúc You need to use GNU/Linux version of
realpath
. If you're on macOS, install via:brew install coreutils
. -
phuclv over 6 yearsno, I'm on Ubuntu 14.04 LTS
-
kenorb over 6 years@LưuVĩnhPhúc I'm on
realpath (GNU coreutils) 8.26
where the parameter is present. See: gnu.org/software/coreutils/realpath Double check that you're not using an alias (e.g. run as\realpath
), and how you can install the version from thecoreutils
. -
phuclv over 6 yearsSeems realpath on Ubuntu 14.04 is outdated indeed, and the only way is rebuild coreutil manually
-
IBBoard over 6 yearsNote that
--relative-to=…
expects a directory and DOES NOT check. That means that you end up with an extra "../" if you request a path relative to a file. -
Jérôme Pouiller about 6 yearsIdea is not bad but, in current version plenty of cases don't work:
relpath /tmp /tmp
,relpath /tmp /tmp/a
,relpath /tmp/a /
. In add, you forget to mention that all paths must be absolute AND canonicalized (i.e./tmp/a/..
does not work)