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?

  • You can use symlinks to convert absolute links to relative. Jul 31, 2013 at 15:12
  • @StephaneChazelas how? Could you post an answer explaining?
    – terdon
    Jul 31, 2013 at 15:14

7 Answers 7


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.

  • I get realpath: unrecognized option '--relative-to=.' :( realpath version 1.19
    – phuclv
    Feb 8, 2018 at 11:26
  • @LưuVĩnhPhúc You need to use GNU/Linux version of realpath. If you're on macOS, install via: brew install coreutils.
    – kenorb
    Feb 8, 2018 at 11:27
  • no, I'm on Ubuntu 14.04 LTS
    – phuclv
    Feb 8, 2018 at 14:56
  • @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.
    – kenorb
    Feb 8, 2018 at 14:57
  • 1
    Seems realpath on Ubuntu 14.04 is outdated indeed, and the only way is rebuild coreutil manually
    – phuclv
    Feb 9, 2018 at 3:32

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/


  • 1
    Thanks Stephane. This solves the root problem, so I have accepted. That said, it would be great to have something (e.g. a zsh function) that takes two paths (source and target) and outputs the relative path from source to target (no symbolic links here). That would solve the question asked in the title as well. Jul 31, 2013 at 17:23
  • @user815423426, terdon has answered that part Jul 31, 2013 at 17:33

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

There are not any shell builtins to take care of this. I found a solution on Stack Overflow though. I have copied the solution here with slight modifications and marked this answer as community wiki.

relpath() {
    # both $1 and $2 are absolute paths beginning with /
    # $1 must be a canonical path; that is none of its directory
    # components may be ".", ".." or a symbolic link
    # returns relative path to $2/$target from $1/$source


    while [ "${target#"$common_part"}" = "$target" ]; do
        # no match, means that candidate common part is not correct
        # go up one level (reduce common part)
        common_part=$(dirname "$common_part")
        # and record that we went back, with correct / handling
        if [ -z "$result" ]; then

    if [ "$common_part" = / ]; then
        # special case for root (no common path)

    # since we now have identified the common part,
    # compute the non-common part

    # and now stick all parts together
    if [ -n "$result" ] && [ -n "$forward_part" ]; then
    elif [ -n "$forward_part" ]; then
        # extra slash removal

    printf '%s\n' "$result"

You can use this function like so:

ln -s "$(relpath "$source" "$target")" "$source"
# 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

    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

  • 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). Jul 26, 2017 at 8:15
  • 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) Apr 24, 2018 at 8:55

I found it curious that it wasn't mentioned yet: the case when one path is a child of a parent path, there's a simple expansion that doesn't involve forking calls (ex realpath). @Arto mentioned this kindof, but the more complicated and general approach where you have to go up and down.

RELPATH=${CHILD##"$PARENT"} # subdir/grandchilddir/myfile

I had to write a bash script to do this reliably (and of course without verifying file existence).


relpath <symlink path> <source path>

Returns a relative source path.


#/a/b/c/d/e   ->  /a/b/CC/DD/EE       ->  ../../CC/DD/EE
relpath /a/b/c/d/e   /a/b/CC/DD/EE

#./a/b/c/d/e   ->  ./a/b/CC/DD/EE       ->  ../../CC/DD/EE
relpath ./a/b/c/d/e  ./a/b/CC/DD/EE

both get ../../CC/DD/EE

To have a quick test you can run

curl -sL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath | bash -s -- --test

result: a list of test

/a             ->  /                 ->  .
/a/b           ->  /                 ->  ..
/a/b           ->  /a                ->  .
/a/b/c         ->  /a                ->  ..
/a/b/c         ->  /a/b              ->  .
/a/b/c         ->  /a/b/CC           ->  CC
/a/b/c/d/e     ->  /a                ->  ../../..
/a/b/c/d/e     ->  /a/b              ->  ../..
/a/b/c/d/e     ->  /a/b/CC           ->  ../../CC
/a/b/c/d/e     ->  /a/b/CC/DD/EE     ->  ../../CC/DD/EE
./a            ->  ./                ->  .
./a/b          ->  ./                ->  ..
./a/b          ->  ./a               ->  .
./a/b/c        ->  ./a               ->  ..
./a/b/c        ->  ./a/b             ->  .
./a/b/c        ->  ./a/b/CC          ->  CC
./a/b/c/d/e    ->  ./a               ->  ../../..
./a/b/c/d/e    ->  ./a/b/CC          ->  ../../CC
./a/b/c/d/e    ->  ./a/b/CC/DD/EE    ->  ../../CC/DD/EE
/a             ->  /x                ->  x
/a/b           ->  /x                ->  ../x
/a/b/c         ->  /x                ->  ../../x
/a             ->  /x/y              ->  x/y
/a/b           ->  /x/y              ->  ../x/y
/a/b/c         ->  /x/y              ->  ../../x/y
/x             ->  /a                ->  a
/x             ->  /a/b              ->  a/b
/x             ->  /a/b/c            ->  a/b/c
/x/y           ->  /a                ->  ../a
/x/y           ->  /a/b              ->  ../a/b
/x/y           ->  /a/b/c            ->  ../a/b/c
./a a/b/c/d/e  ->  ./a a/b/CC/DD/EE  ->  ../../CC/DD/EE
/x x/y y       ->  /a a/b b/c c      ->  ../a a/b b/c c

BTW, if you want to use this script without saving it and you trust this script, then you have two ways to to this with bash trick.

Import relpath as a bash function by following command. (Require bash 4+)

source <(curl -sSL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath)

or call the script on-the-fly like with curl bash combo.

curl -sSL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath | bash -s -- /a/b/c/d/e /a/b/CC/DD/EE

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .