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?
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
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.
Method 2
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
Method 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
Method 4
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
source=$1
target=$2
common_part=$source
result=
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
result=..
else
result=../$result
fi
done
if [ "$common_part" = / ]; then
# special case for root (no common path)
result=$result/
fi
# since we now have identified the common part,
# compute the non-common part
forward_part=${target#"$common_part"}
# and now stick all parts together
if [ -n "$result" ] && [ -n "$forward_part" ]; then
result=$result$forward_part
elif [ -n "$forward_part" ]; then
# extra slash removal
result=${forward_part#?}
fi
printf '%sn' "$result"
}
You can use this function like so:
source=/foo/bar/something target=/foo/hello/world ln -s "$(relpath "$source" "$target")" "$source"
Method 5
# 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
Method 6
I had to write a bash script to do this reliably (and of course without verifying file existence).
Usage
relpath <symlink path> <source path>
Returns a relative source path.
example:
#/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
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0