Getting relative links between two paths
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.
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
- symlinks man page
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 '%s\n' "$result"
}
You can use this function like so:
source=/foo/bar/something
target=/foo/hello/world
ln -s "$(relpath "$source" "$target")" "$source"