Getting relative links between two paths

22,406

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

Share:
22,406

Related videos on Youtube

Amelio Vazquez-Reina
Author by

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, 2022

Comments

  • Amelio Vazquez-Reina
    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
      Admin almost 11 years
      You can use symlinks to convert absolute links to relative.
    • Admin
      Admin almost 11 years
      @StephaneChazelas how? Could you post an answer explaining?
  • Stéphane Chazelas
    Stéphane Chazelas almost 11 years
    @user815423426, terdon has answered that part
  • Circonflexe
    Circonflexe almost 7 years
    Thanks for your solution. I found that to make it work in a Makefile I needed to add a pair of double quotes to the line pos=${pos%/*} (and, of course, semicolons and backslashes at the end of everyline).
  • phuclv
    phuclv over 6 years
    I get realpath: unrecognized option '--relative-to=.' :( realpath version 1.19
  • kenorb
    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
    phuclv over 6 years
    no, I'm on Ubuntu 14.04 LTS
  • kenorb
    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 the coreutils.
  • phuclv
    phuclv over 6 years
  • IBBoard
    IBBoard over 6 years
    Note 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
    Jérôme Pouiller about 6 years
    Idea 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)